C++ 基类应该有属性吗?
在另一个社区,我有人建议在基类中“永远”不要有属性。相反,它应该只有纯虚函数,并让派生类负责为所有这些方法提供定义 我的看法是“这不是经验法则”。我举了一个简单的例子:C++ 基类应该有属性吗?,c++,oop,C++,Oop,在另一个社区,我有人建议在基类中“永远”不要有属性。相反,它应该只有纯虚函数,并让派生类负责为所有这些方法提供定义 我的看法是“这不是经验法则”。我举了一个简单的例子: class Shape { protected: double Area; double Perimeter ; public: Shape():Area(0.0), Perimeter(0.0){} double getArea(){return
class Shape
{
protected:
double Area;
double Perimeter ;
public:
Shape():Area(0.0), Perimeter(0.0){}
double getArea(){return Area;}
double getPerimeter (){return Perimeter;}
virtual void setArea () = 0 ;
virtual void setPerimeter () = 0 ;
};
class Rectangle : public Shape
{
private :
double length ;
double breadth ;
public:
Rectangle ():length(0.0),breadth(0.0){}
Rectangle (int _length, int _breadth):length(_length),breadth(_breadth){}
void setArea ()
{
Area = length * breadth ;
}
void setPerimeter ()
{
Perimeter = 2 * (length + breadth) ;
}
} ;
在上面给出的示例中,我觉得任何形状都有以下属性'Area'和'permiture',因此,如果我们不在类中提供这些属性,那么形状类将不会是真实世界形状的适当表示形式
请让我知道你对此的看法
编辑:
我要说的第一件事是,我的代码确实很幼稚,没有太多的意义。我之前想我会加一句话,说这只是一个例子,这篇文章的重点是要知道“基类是否应该永远不应该有属性”。但后来我想我也会得到一些好的设计建议,我确实得到了很多:)
说到这个问题,在下面的所有帖子中,我明白这不是经验法则,而是设计选择(这正是我想要强调的)。话虽如此,我也承认,如果这些属性可以作为面积=长度*宽度来计算(或派生),那么最好不要在基类中包含属性。
非常感谢您的所有答案(我希望我可以选择接受多个答案)。在基类中包含变量是完全可以接受的。但是,许多语言不允许这样做,因此基类只能具有虚拟函数。这可能就是你听到这个的原因
在这个问题上有很多不同的观点,但是在C++中,我认为基类存储数据是很常见的。
< P>在基类中没有数据会要求派生类中不相同的代码重复。防止代码重复是面向对象设计内置的概念之一。回答您的问题。。。。是的,在基类中使用适用于所有子类的属性。例如,如果您知道形状(多边形!)始终具有长度和宽度,请将它们用作基类的属性 我不明白为什么要为创建的形状(多边形)的每个实现重新创建这些…我想如果你可以将圆的周长称为周长,你可以使用shape类。我认为这个建议来自于风格问题。将一种协议(接口)类作为只发布类的方法的基础被认为是更干净的。实现应该尽可能对类客户机隐藏。如果还将数据存储在基类中,则实现类不能自由选择存储数据的最佳方式。这样基类就限制了实现的可能性。也许有一个实现想要计算一个allready存储值,这样就不需要空间,但是Interitor无法释放(非动态)分配的内存
因此,作为一份简历,我想说,这取决于你是否“只编写”了一个东西,或者你是否想创建一个庞大的库来进行10年的维护,这需要一种更干净的方法。我不同意你的例子。当然,一个矩形有一个面积和一个周长,当你可以在getter中动态计算它们时,将它们与长度和宽度分开存储是没有多大意义的。既然可以提供更好的界面,为什么还要强迫人们调用这些“Set”函数来获得准确的结果呢 我会使
形状
成为一个纯粹的界面:
struct Shape
{
virtual ~Shape(); // probably need this
virtual double getArea () = 0 ;
virtual double getPerimeter () = 0 ;
};
class Rectangle : public Shape
{
double length ;
double breadth ;
public:
Rectangle ():length(0.0),breadth(0.0){}
Rectangle (int _length, int _breadth):length(_length),breadth(_breadth){}
double getArea ()
{
return length * breadth ;
}
double getPerimeter ()
{
return 2 * (length + breadth) ;
}
};
我觉得任何形状都有以下属性“面积”和“周长”,因此如果我们不在类中提供这些属性,则形状类将不是“真实世界形状”的适当表示
我也不同意这一点。Shape类应该有一个公共API,提供对其区域和周长的访问。它是否有包含这些值的字段是一个实现细节,与它是否向外部世界表示形状无关
比如说,为外部世界提供一个好的接口是类设计的85%。另外15%是确保适合调用方的接口实际上是可行的。使内部实现细节遵循与外部接口相同的模式,以至于函数返回的对象的每个属性都必须存储在字段中,这不仅是低优先级的,而且是毫无意义的
如果你有一个圆,你可能会想到getRadius
和getDiameter
函数,但是你不会将它们存储在不同的字段中,尽管事实上圆当然有半径和直径
现在,如果这不是一个矩形,而是一个计算面积和周长很慢的非常复杂的形状,那么您可能希望在对象中缓存面积和周长(并且在定义参数更改时重新计算它们或将缓存标记为过时)。但这是复杂形状的一个特殊属性,与矩形等简单形状无关,因此不属于基类形状。基类应该只包含对所有派生类通用的东西,或者至少对足够多的派生类通用,这样您就可以像对所有派生类通用一样(并覆盖或忽略它们不适用的子类中的那些东西)。在您的示例中,报告面积和周长的功能对于所有形状都是通用的。在数据成员中存储面积和周长的事实不需要对所有形状都通用,在矩形的情况下,它会占用额外的内存并需要额外的内存
class shape
{
public:
~shape(){}
virtual double area(void) const = 0;
virtual double perimeter(void) const = 0;
};
class square : public shape
{
public:
square(double pSide) : mSide
{}
double area(void) const
{
return mSide * mSide;
}
double perimeter(void) const
{
return 4 * mSide;
}
private:
double mSide;
};
template <typename Shape>
class shape_attribute_cache : public shape
{
public:
// provide shape interface
double area(void) const
{
return cached_area();
}
double perimeter(void) const
{
return cached_perimeter();
}
protected:
shape_attribute_cache() : mArea(0), mPerim(0), mChanged(false) {}
~shape_attribute_cache(){} // not to be used publically
double cached_area(void) const { update_area(); return mArea; }
void cached_area(double pArea) { mArea = pArea; }
double cached_perimeter(void) const { update_perimeter(); return mPerim; }
void cached_perimeter(double pPerim) { mPerim = pPerim; }
void changed(void) { mAreaChanged = true; mPerimChanged = true; }
private:
void update_area(void)
{
if (!mAreaChanged) return;
// delegate to derived shapes update method
static_cast<Shape*>(this)->update_area();
mAreaChanged = false;
}
void update_perimeter(void)
{
if (!mPerimChanged) return;
// delegate to derived shapes update method
static_cast<Shape*>(this)->update_perimeter();
mPerimChanged = false;
}
double mArea;
double mPerim;
bool mAreaChanged;
bool mPerimChanged;
};
// use helper implementation
class expensive_shape : public shape_attribute_cache<expensive_shape>
{
public:
// ...
void some_attribute(double pX)
{
mX = pX;
changed(); // flag cache is bad, no longer callers responsibility
}
private:
// ...
void update_area(void)
{
double newArea = /* complicated formula */;
cached_area(newArea);
}
void update_perimeter(void)
{
double newPerim = /* complicated formula */;
cached_perimeter(newPerim);
}
};
class Shape
{
private:
double Area;
double Perimeter ;
public:
Shape(double the_area, double the_perimeter): Area(the_area), Perimeter(the_perimeter){}
double getArea() const {return Area;}
double getPerimeter () const {return Perimeter;}
};
class Rectangle : public Shape
{
private :
double length ;
double breadth ;
public:
Rectangle (int _length, int _breadth):Shape(length * breadth, 2 * (length + breadth)), length(_length),breadth(_breadth){}
} ;
class base {
public:
base(int x);
virtual ~base();
inline int x() const { return x_; }
// virtual interface declared here
private:
int x_;
};
class derived : public base { ... };
class base {
public:
virtual ~base();
virtual int x() const = 0;
// ...
};
class base_with_static_x : public base {
public:
base_with_static_x(int);
virtual ~base_with_static_x();
inline int x() const { return x_; }
// ...
private:
int x_;
};
class derived : public base_with_static_x { ... };