开发者

Object-oriented Paradigm Question

Even though I've been programming for quite a while now, when it comes to coupling objects I always seem to bang my head against the wall so I'm wondering if anyone has any resources or golden rules I can follow.

Let me give a small example, in no particular language...

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
}

Lets ignore ctors/setters/getters/dtors for now and just instantiate some stuff...

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

My question is.. what's the best practice here...

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

or...

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

or maybe it doesn't even matter :S

The real-life example I'm working on is a ton more complex. Whenever the Person does something, everyone in the Lab needs to开发者_运维问答 be notified, etc, and I'm just trying to figure out if there are any principles I can apply here.


My answer is a combination of several existing answers.

The essential problem here is that there is a hidden concept here. The method isn't really talking about lab object or the person object, but about the relationship between them. (As suggested by @dacris and @vs.)

One way to deal with such situations is to use a language with double-dispatch (Thank you, @Ken.)

Another way, is to have auto-generated code (Thank you @vs.) in which case there would be methods available in either direction.

But often those solutions aren't practical - changing entire languages over this seems overkill.

The auto-generated solution gives us an insight though. Both techniques should be legal. So you could implement both techniques manually.

However, if you don't want to repeat yourself, this approach makes it clear that EITHER direction is legal. So don't sweat it, too much.

If you are coding a system where the Person object has other uses apart from exploding things, it would be better for the coupling to go from Lab to Person (i.e. put the methods on the Lab object) so the Person object can be used elsewhere without having to deal with changes to the Lab object or the explosion-related methods.

... 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!)


You might want to read a bit about the Observer and Publish/Subscribe patterns. What you're describing is pretty much the classic application for the Observer pattern. The pub/sub pattern is basically the same idea abstracted a bit more to help scaling.

In any case, given how well known this pattern already is, you might as well follow its convention unless you encounter a situation where you're really sure you benefit from doing otherwise.


Think like you're speaking English. The general rule is, verbs (and methods) should have "active voice" as much as possible -- that is, an object should do something, rather than have something done to it.

If it's an event, passive voice makes a little more sense -- the Lab should know what Persons are in it, but some random Person (even one working in the same Lab) probably shouldn't, so a notification that the Lab blew up would be best coming from the Lab itself. But really, it's about personal (or team) preference in that case.


You're right. I think this is one of the major problems of most of today's object-oriented systems: often, methods seem to naturally "belong to" an object, but often they don't.

Systems with multiple dispatch neatly avoid this problem. For example, in Dylan, you might say something like:

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

(I linked to the c2.com MultiMethods page because I think it does the least-bad job of describing this. Wikipedia has a page for Multiple_Dispatch, but its example is pretty awful.)


I am not entirely sure what your example means, but an

An excellent book that has what you want in it is Applying UML and Patterns by Craig Larman.

The book talks extensively about assigning responsibilities. For example, you might use the Information Expert pattern, in which case, the object that has the most knowledge of the variables involved will be the one that is given the responsibility of having the method.


oO give you a different perspective on this: actually you're not interested in either Persons or Labs, but in a relation between them. If you look at it from a UML- or database perspective, you would see that this relation is very much a new concept in your (mental) model. See @dacris comment above as well, where he introduces a new class.

If you would use ORM (Object-Relational Mapping), like when you would do when engineering with UML models, those two methods blowsUp() and blownUpBy() would be automatically code-generated, with their respective runtime checks to ensure their consistency.

Larman's book should indeed contain something about this topic for you.


I think that it is related to real world and your coding convention rather than general good practices. For your English, I still prefer call people.notify(lab). However, if you want your lab to have some data about who call it, which person, you can do lab.isNotifiedBy(people).

The good practice here is that it makes sense for you and your colleague when looking at the code, they understand what it does, if they want to find a method, they know where they should start rather than just keep searching or asking you


I like designing things like this:

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


In this case I'd like to introduce a new object - LabExplosion

class LabExplosion
{
    private Person personResponsible;
    private Lab labAffected;
}

Then, keep a repository of LabExplosions somewhere, and do something like:

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


what's the best practice here...

It depends on your use case, how is the user going to use the system?. Would it be a Lab being "blowed" by a Person? or the use case of the system is to have some Person blow up Labs?

or maybe it doesn't even matter :S

At the end the result is the same, but the important thing here is the semantic of the code. If sounds silly to have Labs being blowed by people, then don't do it.

So the golden rule, as BobTurbo mention, is to find out the "information expert" ( see: GRASP ) in the system and give the control to that object.

You usually define a user history or narrative on how the system would be used, if, for instance, the narrative is:

When a person does something everyone in the lab has to be notified.

Then, to me it means that a Person works in a Lab, when it is created, that person may receive the lab he works on, and register himself to be notified of what happens in that perticula lab.

Since the lab has the list of the persons to notify, it makes sense to be the lab who performs the notification ( Person gives control to Lab in this case )

Then probably the Person could be defined as:

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.... 
          }
       }
  }

So, the person does something in the lab, in this case the public method blowUpLab which just blows up the person's lab by invoking the Lab's boom! method.

In turn the Lab perform the method actions and notify all its subscribers at the end:

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 )
         }
     }
 }

This is the observer pattern.

Finally your main app will create persons and labs and execute the use case:

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

This main app, will create three person, with some lab, only two of them are related ( scott and gordon )

Randomly one of them will receive the blowUpLab message and will perform the method. The lab in turn will notify all the subscribers of that lab.

So, when James Hetfield, blow its lab, no one will be notified :)

The point is Do describe your use case, and identify the information expert there; give the control to that object, and let that object rely the control to other object, but only according to your use case

I hope it makes sense.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜