我将借此机会对面向对象的哲学提出一些相关但不同的想法,作为一种评论请求。如果管理员认为适合关闭,我可能会将其改为博客文章。但我在问问题,所以我认为这是一个合适的社区维基。
假设我有abstract class Bird
。假设我们在这里采用了相当典型的OO理念,Bird
是一个能够维持和改变状态的对象。这个简单模型的一个明显的状态示例是我们的Bird
是否正在飞行。
因此,Bird
有一个方法fly()
。
还是吗?我在介绍性书籍中看到了这个非常简单的例子,在我看来,这是一种巧妙的误导。如果您正在维护状态,则不需要告诉对象继续处于给定状态;它只是。这就是对象的作用。实际上,您希望Bird
拥有方法takeoff()
。
假设我们在某个时刻从另一个类Bird
创建了我们的Egg
对象。在某些时候,我们可能会调用一个返回类型为Egg.hatch()
的方法Bird
。假设我们编码到接口,Egg
知道它是什么类型的鸟,并且来自hatch()
的返回实例知道它也是什么类型的鸟。但我们无法真正说出Egg
和Bird
之间存在任何共同点。我们真的在说我们希望这些类的特定实现能够引用某种BirdArchetype
的实例来表示该物种的所有实例的特征,因此Egg
和{{ 1}}独立但始终如一地了解自己的种类?这种关系有没有众所周知的模式?
确保鸡蛋改变状态以使其不再能够孵化,或者做蛋通常做的大部分其他事情的责任是谁的责任?
哲学上,我们是否想要引入易碎对象的概念?也许Bird
应编码,以便在尝试执行任何多次更改其状态的事情时抛出异常。由调用代码来跟踪客户端对象的生命周期。
我们还能用鸡蛋做什么?也许我们可以做饭。如果是我,我会对此进行编码,以便在一个鸡蛋上调用Egg
'打破'cook()
实例并返回一个新对象Egg
的实例。告诉FriedEgg
到Bird
有何不同?建议起飞使它成为一种根本不同的鸟是荒谬的。然而,推理是一样的;一只飞翔的鸟(通常)不能做鸟类做的大部分其他事情,因为它很忙。可以说,takeoff()
是可以破坏的,因为当它处于飞行状态时,调用其他一些方法是行不通的。
我认为这里唯一真正的区别是Bird
可以变成 un - 破碎。热力学可逆性的概念对编程非常简单的模型是否真的至关重要?
答案 0 :(得分:2)
也许在对象中有变异状态是完全错误的; - )
不应Bird.takeoff()
返回FlyingBird
land()
吗? (对于没有飞到陆地的鸟来说显然没有意义。)
同样,对于一个鸡蛋:
egg = Egg()
incubatedEgg = egg.incubate()
bird = incubatedEgg.hatch()
但是,操作的返回类型可能并不总是限于一个值。例如,在孵化期间,胚胎可能会死亡。
egg = Egg()
egg.incubate() ->
if "still alive" (aliveEgg) ->
aliveEgg.hatch()
else "died in incubation" (deadEgg) ->
deadEgg.discard()
快乐的哲学思考: - )
答案 1 :(得分:2)
这就是为什么我并不特别关心对现实世界进行建模并使用它来解释/定义OO哲学。
除非你正在构建一个模拟器(当人们看到模拟器环境时,这些问题的答案会立即变得明显),否则很容易陷入杂草并使自己迷惑。
答案 2 :(得分:2)
“飞”合理地意味着“开始飞行!” (我会说它在自然语言中更频繁地使用 )。您没有 将其解释为“维持飞行!”这就是为什么我们合理地使用“飞”和“土地”(用自然英语)作为反义词。 (Evidence for the skeptical)
您假设鸡蛋是在您的模型中创建的 ex nihilo 。这不是一个合理的假设。这只鸟来自一只鸡蛋,但鸡蛋来自一只鸟。假设一个合理的鸡蛋建模目的,那就是模拟鸟类的延时生育。这意味着幼鸟是由另外两只鸟的有性繁殖形成的。幼鸟的生成载体是鸡蛋。鸡蛋不需要知道它里面是什么鸟;那只鸟是由父母创造的,包裹在一个通用的蛋中(可能具有基于母亲的可变特性。
换句话说,就这个问题而言,你的模型并不是很完善。
鸡蛋有责任看到它不再孵化(检查孵化功能)。请参阅下面对“命令”的讨论。
“易碎”的概念可能对你的模型来说是多余的。您是否关注鸡蛋的易碎性或孵化率?如果破坏是您尝试建模的数据的重要部分(例如,如果模型中的任何内容基本上取决于蛋的破碎,那么就要对其进行建模。无论哪种方式,孵化都没有'当鸡蛋破裂时会发生这种情况,当卵孵化时就会发生这种情况。孵化的一个后果正在破裂,而不是相反。
egg.cook()似乎错误的原因是因为它。动词方法应该以对象为主体。你的陈述“egg.cook()”告诉鸡蛋做饭,这很少是我们打算在鸡蛋的命令中使用动词“cook”。你可能真的想要一个cook.cook(食物) - 其中“食物”arg是任何来自食物的东西(或者更好,有角色(la Moose)“isCookable”。
请记住,编程语言是命令式语言;审讯,劝诫和其他“自然语言”功能在编程语言中固有地错位,即使我们通过命令实现某种版本(我们通过命令模仿询问;“告诉我你的状态!”是对疑问句的必要解释“你的州是什么?“)
一个好的程序在满足所有功能规范的同时从其模型中删除尽可能多的信息。也就是说,您有一个规范(最好以测试套件的形式)。无意义的信息是传递任何测试所不需要的信息。因此,您一次实现一个功能,当您通过规范量化该属性的测试时,引入鸡蛋的可烹饪性(再次,阅读:测试)。如果您实现了对象的可破坏性并意识到您可以在没有它的情况下完成相同的测试,那么通过删除它来改进程序。现在,一个非常聪明的程序员能够以可扩展的方式构建他的模型 - 也就是说,可以在生成新的需求规范时轻松添加新属性。
另请注意,规格可能不正确。如果您无法判断某些内容是否多余,则相对于该程序的某个方面,规范(读取:测试)可能不正确或不完整。
答案 3 :(得分:0)
我认为这不是OO哲学的真正含义。在我看来,你将业务逻辑与设计模式混合在一起。
为了解决您的一般问题,如果您有一个方法可以更改对象的状态,这可能会导致另一个方法根据这个新的(已更改)状态抛出异常,它是希望有一个状态检查方法,这样你就不必每次都明确地处理异常。
在你的情况下,假设你做了egg.fry()
(另一方面,一个鸡蛋不能自己炒。所以也许你需要一个Cook
类,以egg
为参数fry
方法并返回FriedEgg
个实例),之后您执行了egg.hatch()
。第二个调用必须返回异常。您应该提供状态检查方法,而不是强迫您的类的用户将该调用放在显式try...catch
块中。像isFried()
或isHatchable()
之类的东西。然后代替:
try {
egg.hatch();
}
catch(UnhatchableException e) {
...
}
你有:
if(egg.isHatchable()) {
egg.hatch();
}
因此调用代码负责检查并查看对象所处的状态,然后才能执行可能抛出异常的操作。
现在假设您有一个Cook
类和一个名为fry
的方法,并且您Cook.fry(egg)
返回了一个FriedEgg
实例,如果您调用{{1}会发生什么}} 在上面?常识会告诉你,煎蛋不能孵化任何东西!
在这种情况下,您应该有一个hatch()
接口Egg
(即一个可以孵化的蛋)和LiveEgg
都实现FriedEgg
接口。然而,差异在于Egg
中hatch()
方法的实施;它必须抛出一个FriedEgg
,因为你不能孵出煎蛋。
建模大多数现实场景(如汽车,动物等)的问题在于,有时它们无助于充分解释关系。这是因为OO概念非常抽象。
无论如何,希望这有帮助。
答案 4 :(得分:0)
确保鸡蛋改变状态以使其不再能够孵化,或者做蛋通常做的大部分其他事情的责任是谁的责任?
这取决于你是想要保留模型中的蛋残留物还是认为蛋没了。
如果是前者,hatch()
方法将私有hatched
标志设置为true,并且依赖于它的任何egg的方法都是无阴影的(包括hatch()
本身),请检查标志和失败(失败是通过返回代码还是引发异常,取决于)。后者可能适用于某些设计,但实际上并不需要。
还是吗?我在介绍性书籍中看到了这个非常简单的例子,在我看来,这是一种巧妙的误导。如果你保持状态,你不需要告诉一个物体继续处于一个给定的状态;它只是。这就是对象的作用。所以,你真的希望Bird有一个方法起飞()。
我认为你的问题是使用不精确的单词,而不是软件开发。 fly()
解决方案的作者在“起飞”的意义上使用“飞行”动词,例如作为“起飞”的同义词。这是否是100%有效的英语用法是高于我作为ESL开发人员的薪水等级,但我当然同意“起飞”是一个明显不那么模糊的方法名称。
......所以蛋和鸟都知道他们自己的那种,独立但始终如一?这种关系有没有众所周知的模式?
我不确定是否有正式的“模式”,但是在通用的“Bird”类中实现了一个egg构造函数(或者如果你带着工厂去工厂)将鸟类型传递给新的“Egg”对象并且反之亦然,“蛋”对象将其鸟类传递给“鸟”的构造函数(或工厂)是非常广泛使用的。
我们还能用鸡蛋做什么?也许我们可以做饭。如果是我,我会对此进行编码,以便在egg上调用cook()'打破'Egg实例并返回一个新对象的实例,FriedEgg。如何告诉Bird起飞()
正如一位评论者已经指出的那样,上面是一个OO设计问题 - 鸡蛋不能自己做饭。因此,您不能孤立地对鸡蛋实施“烹饪”方法(尽管您的模型可能需要一些“设置状态为熟”方法,如果没有其它原因使其无法进行操作。但是,FriedEgg的事情需要是通过Cook对象的“cook_egg”方法构建(如果需要) - 这也称为“煮熟的状态”。
答案 5 :(得分:0)
似乎更像是命名问题。例如,在java中,您可以将鸟的状态查询为“getFly()”,您可以将其称为“fly()”,就像python一样。在这里,你告诉它要像你改变鸟的状态一样起飞,这对代码的读者来说有点模棱两可。
在鸡蛋的情况下,我认为鸡蛋处理的鸟类种类没有任何问题。至于谁来处理鸡蛋是否有孵化,你可以在鸡蛋内部参考孵出的鸟。一个鸡蛋实际孵化,所以我可以看看它是否有孵化,所以我不会在孵化器中粘上一个鸡蛋壳几个月,期待鸡肉蹦出来。在我看来,由调用代码来确定谁已经孵化了。如果一只蛋孵出一只鸟,那么可以在鸡蛋内部保留一只鸟的参考,如果这有助于保持你的程序井井有条。我认为这对使用该课程的任何人都很方便。
没有理由你不能让鸡蛋也处于油炸状态,任何孵化它的企图都会失败。
对于不同物体的荒谬性,飞行中的鸟是不同的鸟而不是接地鸟,这很难证明。在我们的脑海中,我们可能会认为它是同一只鸟,但就面向对象的编程而言,如果满足程序的需要,我们当然可以使它们成为不同的对象。鸟更像是鸡蛋还是飞机?
至于你的鸟在飞行中是否可以破碎,是鸟的错吗?如果我的狗在它的狗窝里,如果我告诉它去取球,会不会被打破?