views:

241

answers:

5

Imagine these relationships:

  • 1 A has many B's
  • 1 B has many C's...

In reverse:

  • C has 1 B
  • B has 1A
  • By transitivity, C has 1 A

To model this relationship in DB, we have:

TableA
a_id

TableB
b_id
a_id (fk to TableA)

TableC
c_id
b_id (fk to TableB)

To model this relationship in OO, we have:

objA
objB
objC

And... - objB has reference to objA - objC has reference to objB

If objC has a method that needs to call a method of objA, what will you do?

Option 1.

b.getA().runMethodX()

Many ppl I know would do this, but I've also learned that this is not good because getA() is not a behavior of B in a pure OO sense. Doing so is just like doing procedural programming. Agree/Disagree?

Option 2.

Let objC has direct reference to objA and objB through constructor injection / setter

Is this a good idea? but then objB that objC has reference to also has reference to objA. Is this okay? Or as long as it is not a case of cyclic object references, it is acceptable?

Option 3.

Move the method in question to objA, and pass objC in by parameter.

I'm not sure if this considers as an option. I think it'll not work in every case. Have the method in objC reduce to bare minimum that only works on its states, and let objA do whatever objA needs to do before or after.

Option 4. (Delegation)

add runMethodXinA() to B, it calls

a.runMethodX()

C calls

b.runMethodXinA()

I tried this method before, but B will very likely end up having as many methods as A, and doesn't having 1 method in both B and A violates DRY?

What are your though? Any other options? Comments? Suggestions?

Thank you!

A: 

from your list of choices, the choice is option 4, as it obeys the Law of Demeter

  • option 1 violates the Law of Demeter
  • option 2 introduces redundant references and violates the Law of Demeter indirectly
  • option 3 pollutes object A with knowledge of object C, which arguably also violates the Law of Demeter (in reverse)

that leaves option 4

there may be an option 5, but that is beyond the scope of the original question ;-)

Steven A. Lowe
Please post Option 5 as well. :)
Henry
@[Henry]: option 5 depends on knowledge of objects A, B, and C, which I don't have - I find the "need" for object C to call methods of object A to be highly suspicious, so option 5 is probably "Don't Do That" ;-)
Steven A. Lowe
A: 

I think the question is what kind of model you're going for. If you're going for a relational model where you know the entire structure ahead of time and it will not change, option 1 is acceptable. That is, the data/methods in A in a particular organizational pattern is required for C.

Option 2 may seem like a good idea. but you're essentially saving two references to the same object. Now if B gets mapped to a different A, B has to notify C to change its reference as well. No good.

Option 4 seems like the correct way to go for a truly OO approach, however. The point being that A can change its structure and you only have to adapt B - the immediate child. C doesn't know and doesn't care how A is implemented, it's enough that A simply exists and B knows what to do with it.

lc
Oops, I should have pointed out getB() in C is really a private method. It will just return private property B.
Henry
I've updated the question. Changed "getB()" to just "b".
Henry
A: 

Do you actually need the back pointers from C to B to A, or do you only have them so that you can access A from C? Maybe A should take responsibility for creating B and C and for managing their relationship. In that scenario, A can insert itself into C when it creates C (ex. C might have a constructor that takes A).

You might also isolate the function on A that C needs and create an interface for it. The interface (instead of A) will be passed into C on construction. That will decouple C from A.

Bill.D
+2  A: 

Option 2 is a fairly dangerous duplication of information, in addition to whatever issues may come out of the shape of the reference graph depending on your environment.

Option 3 might make sense if the method is in some sense dependent on the type of objA, as a sort of double dispatch.

Option 1 goes against the Law of Demeter, but it sounds like it's the least intrusive option.

You may also want to consider a forwarding method on objB that passes the call to objA.

Jeffrey Hantin
A: 

No. 1 or No. 4 depending on the circumstances. In my metal cutting software we have many instances where we need to know the parent/origin of an object. For example we have a Path Collection on a Fitting and each Path has a Fitting property that returns the fitting that created it.

However if a connected object is there simply because it helps the original object fulfill it's purpose (like math support) then delegation is likely the better approach.

If the connected object MODIFIES the original object then you are likely better off using the visitor pattern.

The only caveat is that in most OOP you have to be careful of doubly linked objects. Garbage collection will often fail on objects that have parent-child links connecting each other. And it is a common mistake to forget to clean up parent-child link.

With OOPs that support creating and consuming events you can work around this issue. By using proxies. The parent will hand the child a proxy object instead of itself. When the child needs the parent it will get a reference from the proxy object. The proxy object in turn raise as a event that cause the parent to return itself. There are no hard links so the problem of the programmer or garbage collector not cleaning up is mitigated.

RS Conley