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
}

让我们暂时忽略 ctors/setters/getters/dtors,只实例化一些东西......

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需要通知每个人,等等,我只是想弄清楚是否有任何原则可以在这里应用。

4

10 回答 10

3

我的答案是几个现有答案的组合。

这里的本质问题是这里有一个隐藏的概念。该方法并不是真正谈论实验室对象或人员对象,而是谈论它们之间的关系。(正如@dacris 和@vs 所建议的那样。)

处理这种情况的一种方法是使用双分派语言(谢谢@Ken。)

另一种方法是使用自动生成的代码(谢谢@vs.),在这种情况下,任一方向都有可用的方法。

但这些解决方案通常并不实用——在此基础上更改整个语言似乎有点过头了。

不过,自动生成的解决方案给了我们一个洞察力。这两种技术都应该是合法的。所以你可以手动实现这两种技术。

但是,如果您不想重复自己,这种方法清楚地表明任何方向都是合法的。所以不要出汗,太多。

如果您正在编写一个系统,其中 Person 对象除了爆炸之外还有其他用途,那么从 Lab 到 Person 的耦合会更好(即将方法放在 Lab 对象上),这样 Person 对象就可以在其他地方使用而无需必须处理对 Lab 对象或与爆炸相关的方法的更改。

... and vice-versa. If all a person does is explode things, then the logic should be there to keep the lab clean and pristine (which is important for labs!)

于 2010-07-15T10:20:23.380 回答
2

您可能想阅读一些有关观察者和发布/订阅模式的信息。您所描述的几乎是观察者模式的经典应用程序。pub/sub 模式基本上是相同的想法,抽象了一点以帮助扩展。

在任何情况下,鉴于这种模式已经广为人知,您不妨遵循它的约定,除非您遇到一种情况,即您确实确定您可以从其他方式中受益。

于 2010-07-15T04:06:37.870 回答
1

想像你在说英语。一般规则是,动词(和方法)应该尽可能地具有“主动语态”——也就是说,一个对象应该做某事,而不是对它做某事。

如果这是一个事件,被动语态更有意义——实验室应该知道里面有什么人,但一些随机的人(即使是在同一个实验室工作的人)可能不应该知道,所以实验室爆炸的通知最好来自实验室本身。但实际上,在这种情况下,这与个人(或团队)偏好有关。

于 2010-07-15T03:36:06.673 回答
1

我不完全确定您的示例是什么意思,但是

一本包含您想要的内容的优秀书籍是Craig Larman 的Applying UML and Patterns

这本书广泛讨论了分配责任。例如,您可能使用信息专家模式,在这种情况下,对所涉及的变量了解最多的对象将是负责拥有该方法的对象。

于 2010-07-15T04:39:00.180 回答
1

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

具有多个分派的系统巧妙地避免了这个问题。例如,在 Dylan 中,您可能会这样说:

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

(我链接到 c2.com MultiMethods 页面,因为我认为它在描述这一点方面做得最差。维基百科有一个 Multiple_Dispatch 页面,但它的示例非常糟糕。)

于 2010-07-15T05:14:03.243 回答
1

oO 让您对此有不同的看法:实际上您对 Person 或 Labs 都不感兴趣,而是对它们之间的关系感兴趣。如果您从 UML 或数据库的角度来看它,您会发现这种关系在您的(心理)模型中是一个非常新的概念。参见上面的@dacris 评论,他介绍了一个新课程。

如果您使用 ORM(对象-关系映射),就像您在使用 UML 模型进行工程时所做的那样,这两种方法将自动生成代码,并通过它们各自的运行时检查来确保它们的一致性blowsUp()blownUpBy()

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

于 2010-07-15T06:01:28.730 回答
0

我认为它与现实世界和您的编码约定有关,而不是一般的良好实践。对于你的英语,我还是更喜欢调用 people.notify(lab)。但是,如果您希望您的实验室有一些关于谁调用它的数据,哪个人,您可以执行 lab.isNotifiedBy(people)。

这里的良好做法是,在查看代码时,对您和您的同事来说是有意义的,他们了解代码的作用,如果他们想找到一种方法,他们知道应该从哪里开始,而不是继续搜索或询问您

于 2010-07-15T03:39:32.083 回答
0

我喜欢这样设计:

let blackMesa = labs["BlackMesa"]
if (blackMesa.isDestroyed) 
{
    let destroyer = blackMesa.destroyer
}
于 2010-07-15T03:40:07.713 回答
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
于 2010-07-15T04:27:18.613 回答
0

这里的最佳做法是什么...

这取决于您的用例,用户将如何使用系统?会不会是实验室被“吹”了Person?或者系统的用例是有一些Person爆炸Labs

或者也许它甚至不重要:S

最后结果是一样的,但这里重要的是代码的语义。如果让 Labs 被人攻击听起来很愚蠢,那就不要这样做。

因此,正如 BobTurbo 所提到的,黄金法则是找出系统中的“信息专家”(参见:GRASP)并将控制权交给该对象。

您通常会定义用户历史记录或系统使用方式的叙述,例如,叙述是:

当一个人做某事时,必须通知实验室中的每个人。

然后,对我来说,这意味着 aPerson在 a 中工作Lab,当它被创建时,该人可能会收到他工作的实验室,并注册自己以了解该 perticula 实验室中发生的事情。

由于实验室有要通知的人员列表,因此执行通知的实验室是有意义的(在这种情况下,人员将控制权交给实验室)

那么可能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只是通过调用 Lab 的方法来炸毁这个人的实验室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 炸毁其实验室时,不会通知任何人 :)

关键是要描述您的用例,并确定那里的信息专家;将控制权交给该对象,并让该对象将控制权依赖于其他对象,但仅根据您的用例

我希望这是有道理的。

于 2010-07-15T05:51:33.543 回答