面向对象:如何从一些实现中选择
我是一位体面的程序员,但我是一位面向对象的新手(我被培养成一名优秀的Pascal和C工程师)。 我发现特别棘手的是选择其中一种方法来实现同样的目标。 对C ++来说尤其如此,因为它的强大功能可以让你做任何你喜欢的事情,甚至是可怕的事情(我想这里的权力/责任格言是适当的)。
我认为这可能会帮助我运行一个我正在与社区共同努力的特定案例,以便了解人们如何做出这些选择。 我在寻找的是与我的具体案例相关的建议,也是更一般的指针(不是双关语)。 开始:
作为练习,我正在开发一个简单的模拟器,其中“几何表示”可以有两种类型:“圆”或“多边形”。 然后模拟器的其他部分将需要接受这些表示,并可能以不同的方式处理它们。 我已经想出了至少四种不同的方式来做到这一点。 每个的优点/缺点/折衷有哪些?
答:函数重载
将Circle
和Polygon
声明为不相关的类,然后重载每个需要几何表示的外部方法。
B:铸造
声明一个enum GeometricRepresentationType {Circle, Polygon}
。 声明一个抽象的GeometricRepresentation
类,并从中继承Circle
和Polygon
。 GeometricRepresentation
有一个由Circle
和Polygon
实现的虚拟GetType()
方法。 然后,方法使用GetType()
和switch语句将GeometricRepresentation
转换为适当的类型。
C:不确定一个合适的名字
在B中声明一个枚举类型和一个抽象类。 在这个类中,还创建函数Circle* ToCircle() {return NULL;}
和Polygon* ToPolygon() {return NULL;}
。 每个派生类然后重载相应的函数,并返回this
。 这是否仅仅是动态铸造的重新发明?
D:一起束它们
将它们实现为具有枚举成员的单个类,以指示对象是哪种类型。 该类有可以存储这两种表示的成员。 它然后由外部方法不调用(例如傻功能GetRadius()
上的多边形或GetOrder()
上的圆圈)。
以下是我教OO学生的一些设计规则(拇指):
1)任何时候你会试图创建一个枚举来跟踪对象/类中的某个模式,你可以(可能更好)为每个枚举值创建一个派生类。
2)当你写一个关于对象(或其当前状态/模式/任何)的if语句时,你可以(可能更好)做一个虚函数调用来执行一些(更抽象的)操作,其中原始的else-sub-statement是派生对象的虚函数的主体。
例如,而不是这样做:
if (obj->type() == CIRCLE) {
// do something circle-ish
double circum = M_PI * 2 * obj->getRadius();
cout << circum;
}
else if (obj->type() == POLY) {
// do something polygon-ish
double perim = 0;
for (int i=0; i<obj->segments(); i++)
perm += obj->getSegLength(i);
cout << perim;
}
做这个:
cout << obj->getPerimeter();
...
double Circle::getPerimeter() {
return M_PI * 2 * obj->getRadius();
}
double Poly::getPerimeter() {
double perim = 0;
for (int i=0; i<segments(); i++)
perm += getSegLength(i);
return perim;
}
在上面的例子中,很明显“更抽象”的概念是周界。 这并不总是如此。 有时甚至没有一个好名字,这是很难“看到”的原因之一。 但是,您可以将任何if语句转换为虚函数调用,其中“if”部分由函数的虚拟部分替换。
在你的情况下,我绝对同意Avi的答案,你需要一个基类/接口类和圆和多边形的派生子类。
很可能你会在Polygon
和Circle
之间有共同的方法。 例如,我会在一个名为Shape
的接口下将它们结合起来(例如,用java编写,因为它在语法上更加清晰,但如果我编写c ++示例,这就是我会用到的),自从我编写c ++以来,这只是一段时间) :
public interface Shape {
public double getArea();
public double getCentroid();
public double getPerimiter();
}
并且Polygon
和Circle
实现了这个接口:
public class Circle implements Shape {
// Implement the methods
}
public class Polygon implements Shape {
// Implement the methods
}
你会得到什么:
您始终可以将Shape
视为具有某些属性的基本对象。 将来可以添加不同的Shape
实现,而无需更改用Shape
执行某些操作的代码(除非您将具有特定于新Shape
某些内容)
如果您的方法完全相同,则可以用抽象类替换接口并实现它们(在C ++接口中只是一个抽象类,没有实现)
最重要的是(我正在摸索子弹#1) - 你会享受多态的力量。 如果你使用枚举来声明你的类型,如果你想添加新的形状,你将有一天不得不在代码中改变很多地方。 然而,对于新类的工具形状,你不需要改变任何东西。
阅读C ++教程以获取基础知识,并阅读Stroustrup的“The C ++编程语言”等内容,以便学习如何使用该语言。
不要相信人们告诉你,你必须独立于语言学习OOP。 肮脏的秘密在于,每种语言都理解为OOP,在某些情况下,它甚至不是非常相似,所以拥有坚实的基础(例如Java)对于C ++来说并不是很有帮助; 它到目前为止,语言只是没有班级。 此外,C ++明确地是一种多范式语言,包括程序,面向对象和泛型编程。 你需要学习如何有效地结合。 它被设计用于最大的性能,这意味着一些低位的东西显示出来,让许多性能相关的决定交给程序员,其他语言不提供选项。 C ++有一个非常广泛的泛型算法库,学习使用这些是课程所需的部分。
从小处着手,所以在一两年的时间里,你可以对第一次尝试的天真情绪轻松一笑,而不是把头发拉出来。
不要为了“效率”而烦恼,除非有令人信服的理由不使用虚拟成员函数。 抓住引用和const
。 获得一个对象设计是非常困难的,不要指望第一个(或第五个)尝试成为最后一个。
上一篇: Object Orientation: How to Choose from a Number of Implementations