我对使用C ++中的接口类有疑问,但不知道它的名称以及如何搜索它。希望你能帮助我,亲切。
我只想用一个简单的例子来讲述我的问题。
我有5种不同的可能对象,例如三角形,正方形,矩形,五角形和六边形。
所有这些对象都有共同的属性,所以我将有一个接口类 Shape 。
现在,我想要的是:我将拥有一个Shape类对象,并希望能够将其用作其他5个对象之一,因为在运行时选择它。
所以我做了类似下面的事情:
class Shape
{
public:
virtual int getArea()=0;
virtual void setWidth(int w)
{
width = w;
}
virtual void setHeight(int h)
{
height = h;
}
protected:
int width;
int height;
};
class Triangle: public Shape
{
public:
int getArea()
{
return (m_Width * m_Height)/2;
}
};
class Rectangle: public Shape
{
public:
int getArea()
{
return (m_Width * m_Height);
}
};
使用时,我想只创建 Shape 的对象,并使用其中一个派生类对其进行初始化。所以,从那时起,我希望它的行为就像这个对象的实例一样:
void main(){
Shape* shape;
std::string shapeType;
std::cout<<"Enter Shape Type: triangle or rectangle."<<std::endl;
std::cin>>shapeType;
if (shapeType == "triangle")
shape = new Triangle();
else if (shapeType == "rectangle")
shape = new Rectangle();
shape->setWidth(5);
shape->setHeight(7);
std::cout<<shape->getArea()<<std::endl;
}
到此为止没问题。问题从这里开始。这些派生类可能具有不同的属性,方法。当我将这些方法vs添加到他们自己的类时, shape 对象无法访问它(正确)。可以使用的另一种方法是将新的派生对象转换为 shape 对象,如:
Triangle* triangle = (Triangle*)shape;
// now I can access own attributes of Triangle object.
但是你可能不这么认为这不是一个好方法。除此之外,我只知道一种方法迫使我将所有这些属性写入 Shape 类并在需要的派生类中实现它们,如果不需要其他类,则将其实现为空。
你对这个问题有什么好的解决方案吗?我确信会有,但我对这个科目没有多少经验,所以希望你有一个符合我想要的解决方案。
提前致谢。
答案 0 :(得分:2)
首先,您的Shape
不是接口,而是基本实现类。将它们分开。
Interface是一个抽象类,只有纯虚方法和虚析构函数:
struct IShape
{
virtual int getArea() =0;
virtual void setWidth(int w) =0;
virtual void setHeight(int h) =0;
// without virtual destructor you cannot
// properly destroy object through IShape pointer
virtual ~IShape() {}
};
现在让我们写基础实现:
class CShapeBaseImpl : public IShape
{
public:
void setWidth(int w) override { width_ = w; }
void setHeight(int h) override { height_ = h; }
protected:
int width_ = 0;
int height_ = 0;
};
在大多数情况下,您希望创建一些具体对象,并仅通过它的界面来处理它。通常是通过abstract factory pattern:
完成的std::unique_ptr<IShape> CreateTriangle( int a, int b, int c )
{
auto triangle = std::make_unique<Triangle>(a,b,c);
// there you can work with it as Triangle
return triangle;
}
CreateTriangle
返回后,最好忘记它是Triangle
。它现在只有IShape
。
答案 1 :(得分:1)
如果您的类型为Shape
,则表示您打算将其用作Shape
。如果您打算使用其中一个特定子类,则使用该特定类型。
如果您仍然希望拥有存储的泛型类型但仍想使用某些特定操作,那么您需要将指针向下转换为带有static_cast
的子类指针,但您需要知道哪种类型存储在那里或使用dynamic_cast
,在这两种情况下都会闻起来像是糟糕的设计。
您应该能够使用虚拟功能执行所有多态操作,或者根据情况和需求,您可以使用其他设计模式,例如double dispatch。
答案 2 :(得分:1)
所有动物都在吠叫吗?
考虑像
这样的层次结构-> animal
-> dog
-> great_dane
-> chiwawa
-> fish
-> tuna
-> shark
有些人认为,如果你有animal*
- s的集合并且你想让他们吠叫,animal
必须声明一个抽象的virtual void bark()=0
来适当地实施great_dane
和chiwawa
(第一个cout << "BARK"
,第二个cout << "bark"
),而fish
只会将其作为“无所作为”{ EM>”。
如果你想知道某些东西是什么吠叫,那就问问听吧。没有声音?它没有。
还有人(包括我自己)认为这样的方式将导致animal
宣布 zoo 的所有经文(包括那些尚未存在的物种)从而使动物类成为一种“上帝对象”(整个宇宙的最终知识所有者),但并非如此。
对于这些人,bark()
属于dog
,并且要求动物吠叫基本上没有意义,除非你在此之前检查它实际上是一只狗。
在那之后,它将适当地咆哮它的种族。
要检查动物是否是狗,给定animal* myanimal
,您只需转换为dog* mydog = dynamic_cast<dog*>(myanimal)
mydog
是否为空?对不起:那不是狗:不要这样对待它。坏事可能发生。 fish
- es 如果你觉得演员太丑陋和频繁,你可以在animal
中实现一个辅助函数:
template<class T>
T* animal::as_a() { return dynamic_cast<T*>(this); }
这样你就可以拥有
auto mydog = myanimal->as_a<dog>();
if(mydog) mydog->bark();
现在,将动物变成形状,将狗变成长方形,将鱼变成三角形!
答案 3 :(得分:0)
如果属性与派生的ONLY相关,但与基础无关,则无意在基础中实现它。例如。属性isEquilateral()
应该在Triangle类中实现,但不能在Shape基类中实现。
所以你可以做的是使用在每个派生类中重新实现的Type()
属性,给出派生对象类型(三角形,矩形等......)
然后静态施法:
Triangle *triangle = static_cast<Triangle*>(shape);
这样的事情:
if (shape->Type() == Shape::Triangle){
Triangle *triangle = static_cast<Triangle*>(shape);
bool eq = triangle->isEquilateral();
...
}
或者简单地说:
Triangle *triangle = static_cast<Triangle*>(shape);
if (triangle){
bool eq = triangle->isEquilateral();
...
}
但我更喜欢第一种方法。
答案 4 :(得分:0)
我相信您需要使用指向基类的指针来存储Object。因此,您使用Shape*
来存储new Triangle()
,并且您应该能够访问必需的函数和变量。这是基于记住如何在矢量中执行此操作,详细更好here,但我不明白为什么它不会传输。
Shape* shape;
shape = new Triangle();
//shape->foo accesses Triangle methods
它要么是,要么使用动态/静态强制转换在运行时将其设置为正确的类型,尽管这意味着您需要检查它是什么类型才能进行正确的转换(所以可能有一个识别由每个子类设置的基类中的字符串或其他内容)
答案 5 :(得分:-3)
这绝对不会像你解释的那样起作用。它不是遗产的感觉。你应该读一下heraditing。基本上是它的以下规则:如果你创建一个下属你可以使用父亲的funcs和属性,如果他们是公共的或受保护的