面向对象编程 - 类设计混乱

时间:2009-11-16 07:52:29

标签: oop class-design

我试图围绕面向对象的编程。

我的理解是我们有对象,所以我们可以设计我们的程序来镜像现实生活中的对象。

让我们采用类层次结构:

class Fruit {
    void Eat() {

    }
}

class Apple extends Fruit {

}

显然,如果Eat()是虚拟的,你可以多态地使用Fruit。但这有意义吗?水果不能自己吃!

是否应该将水果对象传递给具有Eat()功能的人类对象?

我想弄清楚正确的思考方式。一般来说,编程对象与现实生活对象的接近程度如何?

13 个答案:

答案 0 :(得分:12)

你有一个设计问题 - 正如你正确指出的那样,Eat()作为Fruit的成员并没有明显的意义。另一方面,“可食用的”属性会更有意义。就像“onEaten”事件等一样。你的水果/苹果课程揭示了什么(以及你的模型中有哪些其他对象有意义)取决于很多其他因素,包括你想要的东西完成你的应用程序中的那些结构。

通常,您希望类表示逻辑域级实体。有时那些对应于现实世界中的物理实体,但在许多情况下它们不对应。

在我看来,OO问题分解是程序员通常非常擅长的。我不知道有多少次我看到相当于一辆来自方向盘的汽车并摇了摇头,而原来的开发人员却无法绕过他们的设计为什么他们的设计没有多大意义。 / p>

答案 1 :(得分:8)

简单地镜像现实世界的物体很少是一个好主意。借用一个典型的例子 - 控制咖啡机的软件不是关于咖啡豆和热水 - 而是关于制作咖啡。

您需要找到真实世界问题的基础抽象,而不仅仅是将名词复制到对象层次结构中。

如果你的苹果来自水果,它会增加任何有趣的行为吗?层次结构真的需要吗?继承为您的软件增加了一定程度的复杂性,任何增加复杂性的事情都很糟糕。你的软件跟随和理解起来有点困难,你的测试只需要更多的内容,而且错误的可能性要小一些。

我发现OOP更多的是关于空白 - 你要忽略的是更重要的。

答案 2 :(得分:5)

答案 3 :(得分:3)

草食动物的某些东西会有食物功能,就像食肉动物类一样,但每个人的食物会对可以传递给食物功能的论点有一些不同的限制。 Fruit是被吃掉的,因此它将作为参数传递给Herbivore.Eat(),而你想要将Hamburger类型的对象传递给Carnivore.Eat(),并提出一个汉堡包被传递给Herbivore.Eat()的例外。

但实际上,我不认为OOP是如此,我们可以将软件对象建模为真实对象。我发现我的大多数OOP设计都使用非常抽象的对象,并且只针对它们所属的系统。如果我写了一个库签入/签出系统,我会根据它的管理属性和函数对一本书进行建模 - 我不会把它建模为一个Page对象的集合,我怀疑我甚至会定义像Read()方法的东西虽然这是首先拥有一本书的主要目的。 Book对象在系统中的角色决定了我的设计,而不是现实世界中的书籍。

答案 4 :(得分:1)

假设你正在编写一个饥饿的人模拟器,那么我认为,正如你所说,它具有Human::Eat(Fruit f)功能会更有意义。你的Fruit可能没有方法,因为Fruit自身没有做太多,但它可能有calories属性,依此类推。

答案 5 :(得分:0)

  

是否应该通过水果对象   到具有Eat()的人类对象   功能

但程序对象通常比这个天真的例子更抽象。在计算机编程的现实世界中,水果和人类等对象通常表示为数据库中的属性。这些数据的消费和操作将在编程对象中完成。

答案 6 :(得分:0)

你是正确的,可能吃()将是人类或哺乳动物或水果的方法。水果本身可能没有任何行为,因此没有方法。

我并不经常考虑OO在从真实事物到物体的映射中的好处。这可能是因为我们处理的是诸如发票和订单等不太明显的概念。

我看到OO的主要胜利是它带给我的代码的结构。在现实世界中,发票和订单实际上并不是任何东西,但在我的代码中它们确实如此。因此,程序订单可能更接近于表示订单的数据组合,以及与订单相关的一些人工业务流程。

答案 7 :(得分:0)

大多数人类语言遵循Subject Verb Object

的句子结构(简单陈述)

在像C ++这样的OOP语言中,并非完全具有讽刺意味的是,对象是对象,它首先出现,所以正常的顺序是相反的:

所以这是c ++中的'基本'模式: -

object.verb( subject );

这实际上是垃圾。大多数类都设计有subject.verb(对象)接口。然而,SVO的知识确实允许人们测试类接口是否被设计为正确的方法(在这种情况下,它没有)。

也就是说,有大量的人类语言是自然的OVS,或者其他一些变体,其中典型的英语顺序是相反的。在处理国际开发的软件时,其他开发人员可能对主题的正确和正常顺序以及简单陈述中的对象有不同的想法。

答案 8 :(得分:0)

我倾向于考虑:

有一个

所以,苹果是一种水果,所以遗传是有道理的。

但是,水果有可食用可能有意义,但这表明它是水果的属性,而不是动作(方法)。

例如,您可能有未成熟的苹果,不可食用(可食用),因此您可以设置此属性。

现在,无论吃什么,你都可以确定苹果是否是饮食的一部分。

现在Has a将用于作曲。因此,苹果有种子意味着种子不会延伸苹果,但苹果会有一堆种子。

所以,你确实遇到了设计问题,我希望这两个概念可能有助于澄清。

答案 9 :(得分:0)

  

一般来说,编程对象应该如何与现实生活中的对象进行镜像。

不多,只够了。

OOP的一个主要特征是抽象。您不需要让对象的所有属性/方法都能够使用它。

你只需要基本的使用它。

关于对象的全部内容,就是在同一个地方拥有关于该数据执行某些操作的数据和函数。

所以在你的水果课上我最好有Color的东西,或者是否会被吃掉。例如:

 Fruit
     + color : Color
     - isMature : Boolean

     + canBeEaten() : Boolean
          return isMature

这样你可以创造不同的水果

 apple = Fruit()
 appe.color = Color.red
 out.print( "Can this fruit be eaten? %s ", apple.canBeEaten() )

 orange = Fruit()
 orage.color = Color.orange
 out.print( "Can this fruit be eaten? %s ", orange.canBeEaten() )

如果您看到属性(color和isMature)存储在对象内。这样你就不必从外面跟踪他们的状态。

至于继承,只有在需要向某个方法添加新行为时才有意义,是的,这些方法与对象的属性或特征有关。正如你所指出的那样fruit.eat()没有多大意义。

但是考虑一种从水果中获取果汁的方法。

Fruit
    + getJuice(): Juice

Apple -> Fruit
     + getJuice(): Juice
         // do what ever is needed internally to create the juice

Orange -> Fruit
    + getJuice(): Juice
        // here, certainly the way to get the juice will be different

答案 10 :(得分:0)

我总是找到使用'动物'或'水果'反直觉的例子。人们很难对模型进行建模,而且您会遇到具有相同要求的应用程序。

使用拟人化的概念可以真正帮助分配责任。基本上,想象一下你的物体是一个人(我通常在设计过程中用面部和四肢勾画它们)。

现在您可以询问有关您的对象的这些问题:

  • Responsibilites 我的对象在系统中
  • “此对象是否负责执行 xxx ,或 xxx 另一个对象的责任?”
  • “这个对象做得多吗?” (例如,如果设计计算某些东西,则不应负责加载值)
大卫·韦斯特的“对象思维”是一本关于这个主题的好书。

答案 11 :(得分:0)

  

我的理解是我们有对象,所以我们可以设计我们的程序来镜像现实生活中的对象。

在适用的情况下,或许更像是将它们与真实生活对象“联系起来”。

  

水果对象是否应该传递给具有Eat()函数的人类对象?

是的,或者比人类更普遍的东西。

  

我想弄清楚正确的思考方式。一般而言,编程对象应该如何与现实生活中的对象相媲美。

仅定义您需要定义的内容。然后实现(通常)变得非常明显。换句话说,你可能会想得太多。

答案 12 :(得分:0)

我认为我们不应该试图“反映现实生活中的物体”。我认为更多的是发现真实生活对象非常类似于在系统(域)的上下文中建模的行为。游戏中的水果类切片水果可能会有一个与游戏中水果类相比完全不同的行为和属性。或模拟人们吃水果。将行为分配给以现实生活对象命名的类可以更容易地假设代码模块的行为并推测它们的交互。