我认为有一种方法可以客观地定义“好”和“坏”面向对象的设计技术,作为一个社区,我们可以确定它们是什么。这是一项学术活动。如果认真和坚定,我相信它对整个社会都有很大的好处。社区将受益于我们都可以指出的一个地方,“这种技术是'好'或'坏',我们应该或不应该使用它,除非有特殊情况。”
为此,我们应该关注面向对象的原则(而不是功能,基于集合或其他类型的语言)。
我不打算接受一个答案,而是希望答案能够为最终收集作出贡献,或者是对问题的理性辩论。
我意识到这可能存在争议,但我相信我们可以解决问题。大多数每条规则都有例外,我相信这是分歧的结果。我们应该发表声明,然后注意反对者的相关例外和反对意见。
我想尝试定义“好”和“坏”:
“好” - 这项技术将首次运作并成为持久解决方案。以后很容易更改,并将快速支付其实施的时间投资。它可以一致地应用,并且将来可以由维护程序员轻松识别。总的来说,它有助于在产品的整个使用寿命期间提供良好的功能并降低维护成本。
“糟糕” - 这种技术可能在短期内起作用,但很快就会成为一种负担。随着时间的推移,很难改变或变得更加困难。初始投资可能很小或很大,但很快就会成为不断增长的成本,最终成为沉没成本,必须不断拆除或解决。它主观上被应用并且不一致,并且可能是未来维护程序员可能意外或不易识别的。总的来说,它有助于最终增加维护和/或操作产品的成本并抑制或防止产品的变化。通过抑制或防止变革,它不仅仅是直接成本,而是机会成本和重大责任。
作为我认为好的贡献的例子,我想提出一个“好”的原则:
[简短说明]
[代码或其他类型的示例]
[解释这个原则阻止的问题]
[为什么,何时以及何时使用这一原则?]
[我什么时候不会使用这个原则,或者它实际上可能在哪里有害?]
[注意社区中任何反对意见或反对意见]
答案 0 :(得分:3)
有一些很好理解的原则可能是一个很好的起点:
研究现有的设计模式以找到它们背后的原则也是一个好主意,最重要的是(通常)更喜欢构图而不是继承。
答案 1 :(得分:1)
关注点分离
虽然可以通过从实用程序类继承来获得功能,但在许多情况下,可以使用所述类的成员来获取所有功能。
Boost.Noncopyable是一个缺少复制构造函数或赋值运算符的C ++类。它可以用作基类来防止子类被复制或分配(这是常见的行为)。它也可以用作直接成员
转换它:
class Foo : private boost::noncopyable { ... };
对此:
class Foo {
...
private:
boost::noncopyable noncopyable_;
};
Java引入了synchronized
关键字作为一个习惯用法,允许任何对象以线程安全的方式使用。这可以在其他语言中镜像,以便为任意对象提供互斥。一个常见的例子是数据结构:
class ThreadsafeVector<T> : public Vector<T>, public Mutex { ... };
相反,这两个类可以聚合在一起。
struct ThreadsafeVector<T> {
Vector<T> vector;
Mutex mutex;
}
继承经常被滥用作为代码重用机制。如果除了Is-A关系之外的任何东西都使用继承,则整体代码清晰度会降低。
随着更深的链,mixin基类大大增加了“死亡钻石”场景的可能性,其中子类最终继承了mixin类的多个副本。
支持多重继承的任何语言。
mixin类提供或要求重载成员的任何情况。在这种情况下,继承通常意味着Is-Implemented-In-Terms-Of关系,并且聚合是不够的。
此转换的结果可能会导致公开成员(例如MyThreadSafeDataStructure
可能公开访问Mutex
作为组件。
答案 2 :(得分:1)
我认为简短的回答是“好的”OO设计在变化下是健壮的,任何需求变化的代码破坏最少。如果你考虑所有通常的规则,他们都倾向于相同的结论。
困难在于你无法在没有背景的情况下评估设计的“优点”;我相信,它是一个定理,对于任何模块化来说,需求的变化会使破损最大化,导致每个方法都触及每个类。
如果您想严格遵守它,您可以开发一系列“变更案例”并按概率顺序对它们进行排序,以便最大限度地减少最高概率变化的破损。
在大多数情况下,一些发展良好的直觉有很大帮助:特定于设备或平台的事物往往会发生变化,业务规则和业务流程往往会发生变化,而算术的实现则很少发生变化。 (不,如您所想,从来没有。例如,考虑一个可能会或可能不会使用平台支持的BCD算法的业务系统。)