面向对象设计


23

假设您具有以下条件:

     +--------+     +------+
     | Animal |     | Food |
     +-+------+     +----+-+
       ^                 ^
       |                 |
       |                 |
  +------+              +-------+
  | Deer |              | Grass |
  +------+              +-------+

Deer从继承AnimalGrass继承Food

到目前为止,一切都很好。Animal物体可以吃Food物体。

现在,让它混合一点。让我们添加一个Lion继承自的Animal

     +--------+     +------+
     | Animal |     | Food |
     +-+-----++     +----+-+
       ^     ^           ^
       |     |           |
       |     |           |
  +------+ +------+     +-------+
  | Deer | | Lion |     | Grass |
  +------+ +------+     +-------+

现在我们有一个问题,因为Lion可以吃两DeerGrass,但Deer不会 FoodAnimal

不使用多重继承,也不使用面向对象的设计,您如何解决这个问题?

仅供参考:我使用http://www.asciiflow.com创建ASCII图。


14
建模现实世界迟早通常是一个问题,因为总会发生一些奇怪的事情(例如飞鱼,鱼还是鸟?但是企鹅是鸟,不能飞也不能吃鱼)。@Ampt所说的听起来很合理,动物应该吃一些东西。
罗伯·范德维尔 Rob van der Veer)2013年

2
我认为动物应该继承食物。如果有东西想吃狮子,就抛出InvalidOperationException。
拉尔夫·查平

4
@RalphChapin:各种各样的东西都吃狮子(秃鹰,虫子等)。我认为动物和食物是人为的区分,因为它们的范围不够广(所有动物最终都是其他动物的食物),它们将被打破。如果您对“ LivingThing”进行分类,则只需要处理食用非生物的植物(矿物质等)的边缘情况,拥有LivingThing.Eat(LivingThing)就不会有任何问题。
撒旦小狗

2
分享您的研究成果对所有人都有帮助。告诉我们您尝试过的内容以及为什么它不能满足您的需求。这表明您已花时间尝试自我帮助,这使我们免于重复显而易见的答案,并且最重要的是,它可以帮助您获得更具体和相关的答案。另请参见“ 如何问”
2013年

9
帝国时代III游戏已经回答了这个问题。ageofempires.wikia.com/wiki/List_of_Animals实施Deer和Gazelle实施IHuntable,Sheep和Cow是IHerdable(可由人类控制),而Lion仅实施IAnimal,这并不意味着任何这些接口。AOE3支持查询特定对象(类似于instanceof)所支持的接口集,从而允许程序查询其功能。
rwong

Answers:



13

OO只是一个模仿现实世界的隐喻。但是隐喻只能走得那么远。

通常,没有正确的方法在OO中建模。解决特定问题的正确方法是特定域中,即使域对象相同,如果更改问题,也不应期望它工作良好。

我认为这是大多数比较常见的误解。。学生在第一年就拥有。OO不是一个通用的解决方案,它只是解决某些问题的一个不错的工具,可以合理地对您的域进行建模。

我没有回答这个问题,正是因为我们缺少域信息。但是考虑到以上几点,您也许可以设计出满足您需求的产品。


3
+1 OO是一种工具,而不是一种宗教。
mouviciel 2013年

我同意,如果这个问题继续改变和发展,可能没有完美的解决方案。在当前状态下,此问题是否缺少域信息以提供解决方案?
Michael Irey

您是否认真考虑将OP建模为现实世界?通过示例来表示关系模型。
Basilevs

@Basilevs实际上是一种暗示,因为他提到了动物在现实生活中的行为。需要关注的是,为什么需要在IMO程序中说明这种行为。就是说,我很高兴提出一些可能的设计。
DPM

10

您希望将动物进一步细分为子类(或至少在您所做的事情有意义的范围内)。假设您正在使用看起来像基本动物和两种食物(植物和肉类)的东西,那么使用食肉动物和食草动物来进一步定义动物并使它们分开是有意义的。这是我为您准备的。

             +----------------+                   +--------------------+
             |    Animal      |                   |      Food          |
             |----------------|<--+Interfaces+--->|--------------------|
             |                |                   |                    |
             +----------------+                   +--------------------+
                +           +                       +                 +
                |           |    Abstract Classes   |                 |
                |           |        |          |   |                 |
                v           v        v          v   v                 v
   +-----------------+  +----------------+     +------------+      +------------+
   |   Herbivore     |  |  Carnivore     |     |   Plant    |      |   Meat     |
   |-----------------|  |----------------|     |------------|      |------------|
   |Eat(Plant p)     |  |Eat(Meat m)     |     |            |      |            |
   |                 |  |                |     |            |      |            |
   +-----------------+  +----------------+     +------------+      +------------+
            +                    +                    +                   +
            |                    |                    |                   |
            v                    v                    v                   v
   +-----------------+  +----------------+     +------------+      +------------+
   |  Deer           |  |   Lion         |     |  Grass     |      |  DeerMeat  |
   |-----------------|  |----------------|     |------------|      |------------|
   |DeerMeat Die()      |void Kill(Deer) |     |            |      |            |
   +-----------------+  +----------------+     +------------+      +------------+
                                 ^                    ^
                                 |                    |
                                 |                    |
                              Concrete Classes -------+

如您所见,它们都公开了eat方法,但是他们所吃的却改变了。狮子现在可以杀死一只鹿,这只鹿可以死亡并返回DeerMeat,OP最初的问题是如何允许狮子吃一头鹿而不吃草,而无需设计整个生态系统就可以解决。

当然,这很快就会引起人们的兴趣,因为鹿也可以被视为一种肉,但是为了简单起见,我将在鹿下创建一个称为kill()的方法,该方法返回鹿肉,并将其作为扩展肉类的具体类别。


然后,Deer会公开IMeat接口吗?
Dan Pichelman

Meat不是接口,而是抽象类。我添加了我将如何为您实现的功能
-Ampt

Eat(Plant p)并且Eat(Meat m)都违反了LSP。
TulainsCórdova'13

@ user61852怎么样?我故意没有在动物界面中公开Eat,这样每种类型的动物都可以拥有自己的eat方法。
Ampt入渗

1
TCWL(太复杂,会泄漏)。问题是分散的和紧急的,您的解决方案是静态的,集中的和预定义的。TCWL。
图兰斯·科尔多瓦

7

我的设计是这样的:

  1. 食物被声明为界面;有一个IFood接口和两个派生接口:IMeat和IVegetable
  2. 动物实施IMeat,蔬菜实施IVegetable
  3. 动物有两个后代,食肉动物和Hebivores
  4. 食肉动物的Eat方法接收IMeat实例
  5. 草食动物的Eat方法接收一个IVegetable实例
  6. 狮子来自食肉兽
  7. 鹿来自草食动物
  8. 草来自蔬菜

因为动物实施IMeat,而鹿是(草食动物)动物,所以可以吃IMeat的狮子(食肉动物)也可以吃鹿。

鹿是草食动物,因此可以食用草,因为它实现了IVegetable。

食肉动物不能吃IVegeable,草食动物不能吃IMeat。


1
我在这里看到很多枚举类型使用继承来约束被继承的类型什么都没有实现。您已经扩展了类型系统中的模型,该模型对代码的可用性没有任何价值
Jimmy Hoffa 2013年

请记住,杂食动物不像人类,猿和熊那样存在。
TulainsCórdova'13

那么您如何补充狮子和鹿都是哺乳动物呢?:-)
johannes

2
@JimmyHoffa这些被称为“标记接口”,是对接口的完全有效使用。需要对它进行代码审查,以确定使用是否合理,但是有很多用例(例如在这种情况下,狮子试图吃掉草会抛出NoInterface异常)。标记接口(或缺少标记接口)用于预知如果使用不支持的参数调用方法将引发的异常。
rwong

1
@rwong我了解这个概念,之前从未听说过它正式化;只是我的经验一直是我一直在使用的代码库使它们变得更复杂且更难维护。但是,也许我的经验只是人们错误地使用它们的地方。
吉米霍法2013年

5

动物可以吃的食物实际上并不构成一个等级,在这种情况下,自然不可避免地无法遵循简单的面向对象建模(请注意,即使做到了,动物也必须继承食物,因为它是食物)。

关于动物可以吃哪些食物的知识不能完全适用于这两个类别,因此仅提及食物层次结构中的某个成员并不足以告诉您可以吃什么。

这是多对多的关系。这意味着每次添加动物时,都需要弄清楚它可以吃什么,而每次添加食物时,都需要弄清楚可以吃什么。是否需要进一步开发结构取决于您要建模的动物和食物。

多重继承也不能很好地解决这个问题。您需要某种动物可以吃的东西或可以吃食物的动物的收藏。


就像他们对regex所说的“我有问题,所以我使用正则表达式,现在有两个问题”,MI的思路是“我有问题,所以我使用了MI,现在有99个问题”。 d尽管您知道这里可以吃到什么食物,但您却在这里po戳,这实际上简化了模型。依赖反转FTW。
Jimmy Hoffa

1

我将从不同的角度解决问题:OOP与行为有关。在您的情况下,是否Grass有某些行为是孩子的Food?因此,在您的情况下,将没有Grass类,或者至少不会从中继承它Food。另外,如果您需要强制谁可以在编译时吃东西,是否需要Animal抽象就值得怀疑。同样,食肉动物吃草虽然不是为了维持生计,但并不罕见。

因此,我将其设计为(不打扰ASCI艺术):

IEdible具有property Type,它是肉,植物,car体等的枚举。(这不会经常更改并且没有任何特定行为,因此无需将此模型建模为hiearchy)。

Animal使用方法CanEat(IEdible food)Eat(IEdible food),这是合乎逻辑的。然后,特定的动物可以检查何时可以在给定的情况下食用给定的食物,然后再食用该食物以获得食物/做其他事情。另外,我将食肉动物,草食动物,杂食动物类建模为策略模式,而不是动物层次结构的一部分。


1

TL; DR:根据上下文进行设计或建模。

我认为您的问题很困难,因为它缺乏您要解决的实际问题的背景。您有一些模型和一些关系,但是缺少工作所需的框架。没有上下文,建模和隐喻就无法很好地工作,从而为多种解释敞开了大门。

我认为将精力集中在如何使用数据上会更有成效。一旦有了数据使用模式,就可以更轻松地回溯到模型和关系应该是什么。

例如,更详细的要求将需要不同的对象关系:

  • 支持Animals eatFoodGastroliths
  • 支持Chocolate作为PoisonDogs,但不Humans

如果我们开始练习如何为简单的关系建模,那么食物接口可能是最好的。如果那是总和,那么系统中的关系如何就可以了。但是,仅有一些其他要求或关系会极大地影响在较简单情况下起作用的模型和关系。


我同意,但这只是一个小例子而已,它并没有试图为世界建模。例如,您可以有一个吃掉轮胎和车牌的鲨鱼。您可以使用可以吃任何种类的对象的方法来制作父抽象类,而Food可以扩展此abstact类。
hagensoft

@hagensoft:同意。有时候我确实会被带走,因为我经常看到开发人员基于他们立即抓住的隐喻进行建模,而不是研究如何消耗和使用数据。他们嫁给了一个基于最初想法的OO设计,然后试图迫使问题适合他们的解决方案,而不是使他们的解决方案适合问题。
Dietbuddha

1

ECS的继承架构方法:

An entity is a collection of components.
Systems process entities through their components.

Lion has claws and fangs as weapons.
Lion has meat as food.
Lion has a hunger for meat.
Lion has an affinity towards other lions.

Deer has antlers and hooves as weapons.
Deer has meat as food.
Deer has a hunger for plants.

Grass has plant as food.

伪代码:

lion = new Entity("Lion")
lion.put(new Claws)
lion.put(new Fangs)
lion.put(new Meat)
lion.put(new MeatHunger)
lion.put(new Affinity("Lion"))

deer = new Entity("Deer")
deer.put(new Antlers)
deer.put(new Hooves)
deer.put(new PlantHunger)

grass = new Entity("Grass")
grass.put(new Plant)

Nature是一个system遍历这些实体的,通过通用查询功能查找它们具有哪些组件。Nature会导致渴望肉食的实体使用其武器攻击以肉类为食物的其他实体,除非它们与该实体有亲缘关系。如果攻击成功,则该实体将以其受害者为食,这时受害者将变成失去肉类的尸体。Nature会导致对植物有饥渴的实体以以植物为食物(如果存在)的实体为食。

Nature({lion, deer, grass})

Nature(entities)
{
    for each entity in entities:
    {
       if entity.has("MeatHunger"):
           attack_meat(entity, entities.with("Meat", exclude = entity))
       if entity.has("PlantHunger"):
           eat_plants(entity, entites.with("Plant", exclude = entity))
    }
}

也许我们想扩展Grass对太阳光和水的需求,并希望将太阳光和水引入我们的世界。但是Grass,它不能像没有那样直接找到它们mobilityAnimals可能还需要水,但由于有水,他们可以主动寻找水mobility。不断扩展和更改此模型而无需级联整个设计的破坏是非常容易的,因为我们只是添加了新组件并扩展了系统的行为(或系统数量)。


0

不使用多重继承,也不使用面向对象的设计,您如何解决这个问题?

像大多数事情一样,这取决于

这取决于您所看到的“这个问题”。

  • 这是一个普遍的实现问题,例如,如何在您选择的平台中“解决”缺少多重继承的问题?
  • 是否仅针对此特定情况存在设计问题,例如,如何对动物也是食物这一事实进行建模?
  • 领域模型是否存在哲学问题,例如,“食物”和“动物”是否对预期的实际应用有效,必要且足够分类?

如果您询问一般的实施问题,答案将取决于您环境的功能。IFood和IAnimal接口可以使用实现两个接口的EdibleAnimal子类来工作。如果您的环境不支持接口,则只需让Animal从Food继承即可。

如果您要问这个特定的设计问题,只需让Animal从Food继承即可。这是可能可行的最简单的方法。

如果您要问这些设计概念,答案很大程度上取决于您打算对模型进行的处理。如果它是用于吃狗的视频游戏,甚至是用于跟踪动物园喂食时间表的应用程序,那么它就足够了。如果是针对动物行为模式的概念模型,则可能有点浅。


0

继承应该用于永远不变的东西,并且不能改变。草并不总是食物。例如,我不吃草。

对于某些动物,草起着食品的作用


它只是一个抽象。如果需要,那么您可以创建更多的部门来扩展Plant抽象类,并使人类食用“ HumanEatablePlants”之类的抽象类,它将人类所食用的植物分组为具体类。
hagensoft

0

您刚刚遇到了OO的基本限制。

面向对象与分层结构配合得很好。但是,一旦您摆脱了严格的层次结构,抽象就无法正常工作。

我了解有关变态成分等的全部知识,这些成分可用来克服这些限制,但它们笨拙,更重要的是会导致晦涩难懂且难以遵循代码。

关系数据库的发明主要是为了摆脱严格的层次结构的限制。

举个例子,草也可以是建筑材料,纸的原材料,服装材料,杂草或农作物。

鹿可以是宠物,家畜,动物园动物或受保护物种。

狮子也可以是动物园动物或受保护物种。

生活并不简单。


0

不使用多重继承,也不使用面向对象的设计,您如何解决这个问题?

什么问题? 这个系统做什么?在您回答之前,我不知道可能需要什么类。您是否正在尝试用食肉动物,食草动物和植物为生态模型建模,以将物种种群预测到未来?您是否要让计算机播放20个问题?

在定义任何用例之前,开始设计很浪费时间。当大约十人的团队开始使用软件通过图片来制作航空公司的OO模型时,我已经看到了这种荒谬的极端。他们从事了两年的建模工作,没有考虑到实际的业务问题。最终,客户厌倦了等待,并要求团队解决实际问题。所有这些建模都是完全没有用的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.