这对我来说总是个挑战——我是违反正确的面向对象设计(例如选项1),还是使用看似违反现实世界的实现(例如选项2)呢?
现实可能是塑造或演化设计的好起点,但将OO设计建模到现实中总是一个错误。
OO设计关乎接口、实现它们的对象以及它们之间的互动(它们之间传递的消息)。接口是两个组件、模块或软件子系统之间的契约协议。OO设计有许多特性,但对我来说最重要的特性是
可替换性。如果我有一个接口,那么实现代码最好遵守它。但更重要的是,如果实现被替换,那么新实现也最好遵守它。最后,如果实现是多态的,则多态实现的各种策略和状态也最好遵守它。
例1
在数学中,一个正方形
是一个矩形。听起来从类矩形继承类正方形是个好主意。你这样做了,但结果却很糟糕。为什么?因为客户的期望或信念被违背了。宽度和高度可以独立变化,但是正方形违反了这个约定。我有一个长宽为(10,10)的矩形,我将宽度设置为20。现在我
认为我有一个长宽为(20,10)的矩形,但实际上实例是一个长宽为(20,20)的正方形实例,我作为客户将会遇到一些
真正的大惊喜。所以现在我们违反了
最小惊讶原则。
现在你有了有缺陷的行为,这导致客户端代码变得复杂,需要使用if语句来解决有缺陷的行为。你可能还会发现你的客户端代码需要RTTI来解决有缺陷的行为,通过测试具体类型来检查是否为正方形实例(我有一个对矩形的引用,但我必须检查它是否真的是一个正方形实例)。
示例2
在现实生活中,动物可以是肉食动物或草食动物。在现实生活中,肉类和蔬菜是食品类型。因此,您可能认为将类Animal作为不同动物类型的父类是一个好主意。您还认为将FoodType父类分别用于Meat类和Vegetable类是一个好主意。最后,您的类Animal支持一个名为
eat()的方法,该方法接受一个FoodType作为形式参数。
一切都编译通过、静态分析通过并链接。您运行程序。当动物的子类型(比如草食动物)收到一个属于Meat类的FoodType实例时,会发生什么?欢迎来到协变和逆变的世界。这是许多编程语言面临的问题。对于语言设计者来说,这也是一个有趣而具有挑战性的问题。
总之...
那么你该怎么办呢?从你的问题域、用户故事、用例和需求开始。让它们驱动设计。让它们帮助你发现需要建模为类和接口的实体。当您这样做时,您会发现最终结果并不基于现实。
查看Martin Fowler的《分析模式》(Analysis Patterns),在其中你会看到是什么驱动了他的面向对象设计。这主要基于他的客户(医疗人员、金融人员等)如何执行其日常任务。它与现实存在重叠,但并不是基于或由现实驱动的。