面向对象的范式问题

时间:2010-07-15 03:32:45

标签: language-agnostic oop coupling

即使我已经编程了很长一段时间,但是当谈到耦合对象时,我似乎总是把头撞到墙上,所以我想知道是否有人有任何资源或黄金规则我可以​​遵循。< / p>

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

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中的每个人都需要得到通知等等,而我只想弄清楚我是否有任何可以应用的原则。

10 个答案:

答案 0 :(得分:3)

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

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

处理此类情况的一种方法是使用双重发送的语言(谢谢,@ Ken。)

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

但通常这些解决方案并不实用 - 改变整个语言似乎有点矫枉过正。

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

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

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

......反之亦然。如果一个人所做的一切都在爆炸,那么逻辑应该在那里保持实验室清洁和原始(这对实验室来说很重要!)

答案 1 :(得分:2)

您可能想要了解一下Observer和Publish / Subscribe模式。您所描述的几乎是Observer模式的经典应用程序。发布/子模式基本上是相同的想法抽象了一点,以帮助缩放。

在任何情况下,鉴于这种模式已经众所周知,你可能也会遵循它的惯例,除非你遇到一种情况,你真的确信你会从中受益。

答案 2 :(得分:1)

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

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

答案 3 :(得分:1)

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

Craig Larman Applying UML and Patterns是一本出色的书,其中包含你想要的东西。

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

答案 4 :(得分:1)

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

multiple dispatch的系统可以很好地避免此问题。例如,在Dylan中,您可能会说:

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

(我链接到c2.com MultiMethods页面,因为我认为它描述的内容最不好。维基百科有一个Multiple_Dispatch页面,但它的例子非常糟糕。)

答案 5 :(得分:1)

oO为您提供了一个不同的视角:实际上您对人员或实验室不感兴趣,但他们之间的关系。如果从UML或数据库的角度来看,你会发现这种关系在你的(心理)模型中是一个非常新的概念。请参阅上面的@dacris评论,他在那里介绍了一个新课程。

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

Larman的书确实应该包含有关此主题的内容。

答案 6 :(得分:0)

我认为它与现实世界和编码惯例有关,而不是一般的良好做法。对于你的英语,我仍然更喜欢打电话给人们。通知(实验室)。但是,如果您希望您的实验室掌握一些关于谁调用它的数据,那么您可以做lab.isNotifiedBy(人)。

这里的好习惯是,你和你的同事在查看代码,理解它的作用,如果他们想要找到一个方法,他们知道应该从哪里开始而不仅仅是继续搜索或询问时,这是有道理的。你

答案 7 :(得分:0)

我喜欢设计这样的东西:

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

答案 8 :(得分: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

答案 9 :(得分:0)

  

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

这取决于您的use case,用户将如何使用该系统?它会被Person“吹”给实验室吗?或者系统的用例是Person爆炸Labs

  

或者它甚至不重要:S

最后结果是一样的,但重要的是代码的语义。如果让人们吹嘘实验室听起来很傻,那么就不要这么做了。

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

您通常会定义用户历史或叙述如何使用系统,例如,叙述是:

  

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

然后,对我而言,这意味着PersonLab中有效,当它被创建时,该人可能会收到他正在进行的实验室,并注册自己以通知所发生的事情。那个实验室。

由于实验室有要通知的人员名单,因此成为执行通知的实验室是有意义的(在这种情况下,人员控制实验室)

然后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()
             } 
         }
       }
   } 

这个主要应用程序,将创建三个人,与一些实验室,只有两个是相关的(斯科特和戈登)

随机其中一个将收到blowUpLab消息,并将执行该方法。实验室将反过来通知该实验室的所有订户。

所以,当James Hetfield吹响它的实验室时,没有人会被通知:)

重点是 描述您的用例,并在那里识别信息专家;将控件赋予该对象,并让该对象将控件依赖于其他对象,但仅根据您的用例

我希望这是有道理的。