面向对象编程范式问题

8

尽管我已经编程了相当长的时间,但当涉及到对象耦合时,我总是感到很困惑,所以我想知道是否有任何资源或黄金规则可以遵循。

让我举个小例子,没有特定的语言...

class Person {
    private int personnel_id
    private String first_name;
    private String last_name;
    private int personnel_level;
    //Lab labs[4]; <- Lab(s) the Person works in
}

class Lab {
    private int lab_id;
    private String lab_name;
    //Person[99] personnel; <- Person(s) working in the Lab
}

现在先忽略构造函数/设置器/获取器/析构函数,直接实例化一些东西...

Person people = new Person[1500];
Lab labs = new Lab[10];

我的问题是,这里的最佳实践是什么...

people["Gordon Freeman"].blewUp((Lab)"Black Mesa");
-> returns T/F

或者...

labs["BlackMesa"].blownUpBy((Person)"Gordon Freeman");
-> returns T/F

或许这并不重要 :S

我正在处理的真实案例更加复杂。每当Person做出一些动作时,需要通知Lab中的所有人等等,我只是在尝试找出是否有任何原则可以应用于此。


什么是 返回真/假 - OscarRyz
我猜它返回True 或 False? - Prabhu. S
是的,抱歉。T/F = 返回真或假。 - David Titarenco
+1 给戈登·弗里曼和黑山实验室的例子。 - GeReV
10个回答

3
我的回答是多个现有回答的结合。
这里的关键问题在于存在一个隐藏的概念。该方法实际上并不涉及实验室对象或人员对象,而是涉及它们之间的关系。(如@dacris和@vs所建议的那样)
处理这种情况的一种方法是使用具有双重调度的语言(感谢@Ken)。
另一种方法是使用自动生成的代码(感谢@vs)。在这种情况下,将提供两个方向的方法。
但通常这些解决方案并不实用-因此更改整个语言似乎过度了。
自动生成的解决方案为我们提供了见解。两种技术都应该是合法的。因此,您可以手动实现这两种技术。
然而,如果您不想重复自己,这种方法使得清晰明了:任何方向都是合法的。因此,不要过于担心。
如果您正在编写一个系统,其中Person对象除了爆炸事物以外还有其他用途,那么最好的耦合方式是从Lab到Person(即将方法放在Lab对象上),以便Person对象可以在没有处理Lab对象或与爆炸相关的方法的更改的情况下在其他地方使用。
反之亦然。如果一个人只是爆炸东西,那么逻辑应该在那里保持实验室的清洁和原始状态(这对于实验室非常重要!)

2

您可能需要了解一下观察者模式和发布/订阅模式。您所描述的基本上就是观察者模式的经典应用。发布/订阅模式基本上是相同的思想,只不过更抽象,以帮助扩展。

无论如何,鉴于这种模式已经非常出名,除非您遇到确实可以从其他方式中获益的情况,否则最好按照它的约定来进行操作。


嗯...所以你建议Person观察Lab,这样他们就可以在爆炸时被告知,但这仍然存在一个问题,即爆炸是如何触发的(可以这么说)-例如lab1.exploded(culprit)或culprit.explode(lab1)。 - Oddthinking
我真的很喜欢发布/订阅模式,但就像@Oddthinking所说,耦合“问题”仍然存在。也许这只是我的大脑工作方式。最终,你基本上会得到相同的结果,但例如...当我在现实生活中离开一个房间时。我只是...离开了房间。房间不必“失去我”...房间也不必告诉房间里的每个人“David离开了”。也许我只是出于没有好理由的强迫症,之前我已经写过这种类型的代码数百万次了 >_< - David Titarenco

1

像你在说英语一样思考。一般规则是,动词(和方法)应该尽可能使用“主动语态”——也就是说,一个对象应该做某事,而不是被做某事。

如果是事件的话,被动语态更合理一些——实验室应该知道有哪些人员在其中,但是某个随机的人(即使是在同一个实验室工作的人)可能不应该知道,所以最好由实验室本身发出实验室爆炸的通知。但真正来说,这取决于个人(或团队)的喜好。


我不同意第一部分。在确定职责之后,可以使用英语规则来想出良好的方法名称,但它们不应该决定对象的职责本身。 - Oddthinking
1
不是必须的,但它们通常是相辅相成的。如果可能的话,一个对象应该是行为主动的,而不是被动的。这是面向对象编程思想的一部分。 - cHao
如果你说lab.explode(culprit),那么lab对象是在起作用,而不是被动的。如果你说culprit.explode(lab),那么人物对象是在起作用,而不是被动的。在这种情况下并没有帮助区分。 - Oddthinking

1

我不完全确定你的例子是什么意思,但是

Craig Larman的《应用UML与模式》是一本非常好的书,它有你所需要的内容。

这本书详细介绍了如何分配责任。例如,你可以使用信息专家模式,在这种情况下,最了解相关变量的对象将负责该方法。


1

你说得对。我认为这是当今大多数面向对象系统的主要问题之一:通常,方法似乎自然地“属于”一个对象,但实际上并非总是如此。

具有多重分派的系统可以很好地避免这个问题。例如,在Dylan中,你可以这样说:

define method blows-up(p :: <person>, l :: <lab>) => explodes :: <boolean>;
  // ...returns #f or #t...
end method;

我链接到c2.com的MultiMethods页面,因为我认为它最好地描述了这个概念。维基百科有一个Multiple_Dispatch页面,但它的示例非常糟糕。


1

我可以给你一个不同的角度来看待这个问题:实际上,你对于人员或实验室都不感兴趣,而是对它们之间的关系感兴趣。如果你从UML或数据库的角度来看,你会发现这种关系在你的(心理)模型中是一个全新的概念。请参考上面@dacris的评论,他介绍了一个新类。

如果你使用ORM(对象关系映射),就像在使用UML模型进行工程设计时一样,这两种方法blowsUp()blownUpBy()将自动生成代码,并进行相应的运行时检查以确保它们的一致性。

Larman的书中确实应该包含关于这个主题的内容。


0

我喜欢设计这样的东西:

let blackMesa = labs["BlackMesa"]
if (blackMesa.isDestroyed) 
{
    let destroyer = blackMesa.destroyer
}

0
在这种情况下,我想介绍一个新对象 - LabExplosion。
class LabExplosion
{
    private Person personResponsible;
    private Lab labAffected;
}

然后,在某个地方保留一个LabExplosions的存储库,并执行以下操作:

// To find out if Gordon Freeman ever blew up Black Mesa
explosions.find("Gordon Freeman", "Black Mesa").length > 0;
// returns T/F

当有人炸毁实验室时,我猜你会实例化一个新的LabExplosion? - David Titarenco

0
“这里有什么最佳实践...”
这取决于您的用例,用户将如何使用系统?是实验室被“人”“吹”起来的情况吗?还是系统的用例是让一些“人”炸毁“实验室”?
“或者也许这并不重要:S”
最终结果是相同的,但重要的是代码的语义。如果让人们吹起实验室听起来很傻,那就不要这样做。
所以,正如BobTurbo所提到的,黄金法则是找出系统中的“信息专家”(参见:GRASP)并将控制权交给该对象。
通常,您会定义一个用户历史或故事,说明系统将如何使用。例如,如果故事是:
当一个人做某事时,实验室里的每个人都要被通知。对我来说,这意味着一个在一个实验室工作,当他被创建时,他可能会接收到他所在实验室的信息,并注册自己以便接收该实验室发生的事件的通知。
由于实验室拥有要通知的人员列表,因此实验室执行通知是合理的(在这种情况下,人员将控制权交给了实验室)。
那么,可能Person可以定义为:
labs.Person {
     - name: String
     - lab : Lab 

     + Person( withLab: Lab , andName: String ) {
           self.lab = withLab
           self.name = andName
           self.lab.subscribe( self ) // want to know what happens
      }


     + blowUpLab() {
           lab.boom!(blownBy:self)
       }
       // receive a lab even notification 
       // performed by "someone" 
     + labEvent( lab:Lab, by: Person  ) {
          // if is my lab and it wasn't me?
          if( self.labg .== lab .&& self .!= by ) {
             // ok it was someone else.... 
          }
       }
  }

因此,该人在实验室中执行某些操作,例如公共方法blowUpLab,该方法通过调用实验室的boom!方法来炸毁实验室。

反过来,Lab执行方法操作并在结束时通知其所有订阅者:

labs.Lab {
    - labName:String
    - subscribers: Person[0..*]

    + subscribe( to: Person ) {
          subscribers.add( to ) 
      }

    + boom!( blowedBy: Person ) {
         // do blow up the lab 
         .... 
         // and then notify:
        subscriber.forEach( person: Person ) {
             person.labEvent( self, blowedBy )
         }
     }
 }

这是观察者模式。

最后,您的主应用程序将创建人员和实验室并执行用例:

 labs.MainApp {
     _ main() {
          blackMesaLab = Lab("BlackMesa")
          gordon = Person( withLab: blackMesaLab, andName: "Gordon Freeman")
          scott  = Person( withLab: blackMesaLab, andName: "Scott Tiger")
          james  = Person( withLab: Lab("SO Labs"), andName:" James Hetfield");

          persons = Array( gordon, scott, james )

          .... 

         while( true ) {
              // every now and then, have someone blowing up it's lab 
              if ( randomNumber() .== 42 ) {
                  person.at( randomPosition ).blowUpLab()
             } 
         }
       }
   } 

这个主应用程序将创建三个人,每个人都有一些实验室,只有两个人是相关的(Scott和Gordon)。

随机选择其中一个人将收到blowUpLab消息并执行该方法。实验室将通知该实验室的所有订阅者。

因此,当James Hetfield炸毁自己的实验室时,没有人会被通知:)

重点是描述您的用例,并确定信息专家;将控制权交给该对象,并让该对象依据您的用例来控制其他对象,但仅限于您的用例

希望这有意义。


0

我认为这与现实世界和您的编码惯例有关,而不是一般的良好实践。就你的英语而言,我仍然更喜欢称呼people.notify(lab)。但是,如果您希望您的实验室有关于谁调用它的一些数据,可以使用lab.isNotifiedBy(people)。

这里的良好实践是当您和同事查看代码时,它有意义,他们了解它的作用,如果他们想找到一个方法,他们知道应该从哪里开始查找,而不仅是不停地搜索或询问您。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接