|
Prev: Interfaces everywhere?
Next: Business Plans Sample Template, Download Free Business Plan Workbook, Write a Business
From: Firkraag on 19 Dec 2007 17:41 Hello When doing an excersise project I've come up with the following (part of) UML diagram: approach 1: http://img337.imageshack.us/my.php?image=approach1lt0.png In that approach the concrete classes depend on the interface class, which is as far as I understand "OK" or "not bad". But I've read in "Head First Design Patterns", that it is best to create dependencies between "high level abstractions" i.e. for example between interfaces (pure abstract classes in C++). So I've come up with the following approach: approach 2: http://img230.imageshack.us/my.php?image=approach2fg8.png But is this 2nd approach really better? I mean now (in the 2nd case), in the (interface) class IShape I have to store some kind of reference to the (interface) class IScreen (in C++ for example a pointer). Somehow I am not happy with the idea. What was a nice pure abstract class now has some kind of physical weight. And in Java I wouldn't even be able to do that, since you can't store any data in that "purest form of interface", right? Moreover why should I be forced to store any kind of reference to IScreen, what if I wanted some of my shapes to use ISoundSystem, anyway this has become less universal than the first approach I think. PS. don't mint borderColor variable in IShape, pretend it is not there :)
From: Daniel T. on 19 Dec 2007 21:33 Firkraag <firkraag07(a)tlen.pl> wrote: > When doing an excersise project I've come up with the following (part > of) UML diagram: > approach 1: http://img337.imageshack.us/my.php?image=approach1lt0.png > > In that approach the concrete classes depend on the interface class, > which is as far as I understand "OK" or "not bad". If you are going to go with approach 1, I say remove the IShape class. It adds absolutely nothing to the solution. You have an implementation inheritance case, but no implementation to inherit. > But I've read in > "Head First Design Patterns", that it is best to create dependencies > between "high level abstractions" i.e. for example between interfaces > (pure abstract classes in C++). So I've come up with the following > approach: > approach 2: http://img230.imageshack.us/my.php?image=approach2fg8.png Here too, IShape is inherited simply for implementation (no one ever sends a message to an IShape apparently.) At least IShape has a purpose in this diagram, a small one to be sure, its only job seems to be to hold a reference to an IScreen, but that's something... > But is this 2nd approach really better? I mean now (in the 2nd case), > in the (interface) class IShape I have to store some kind of reference > to the (interface) class IScreen (in C++ for example a pointer). > Somehow I am not happy with the idea. What was a nice pure abstract > class now has some kind of physical weight. And in Java I wouldn't > even be able to do that, since you can't store any data in that > "purest form of interface", right? Moreover why should I be forced to > store any kind of reference to IScreen, what if I wanted some of my > shapes to use ISoundSystem, anyway this has become less universal than > the first approach I think. If all IShapes send messages to IScreens, then they need to have some way to access the IScreen they can send messages to, and even if they also send messages to ISoundSystems, they still (by definition) send messages to IScreens. To reiterate, if you have an interface that has no uses or aggregation arrows pointing toward it (only inheritance arrows,) then the interface can safely be done away with. If you have a class (i.e., it contains implementation details) that only has inheritance arrows pointing to it (i.e., implementation inheritance only,) then you can (and should IMO) turn those arrows into aggregations rather than inheritance.
From: H. S. Lahman on 20 Dec 2007 12:05 Responding to Firkraag... > Hello > When doing an excersise project I've come up with the following (part > of) UML diagram: > approach 1: http://img337.imageshack.us/my.php?image=approach1lt0.png Basically I agree with Daniel T. As Daniel T. points out, without the border stuff there is no reason to have IShape at all. What IShape provides is a means for a screen to collaborate with a shape without knowing which shape it is collaborating with. The real issue here is about the relationships to IScreen. The reason that one employs relationships to subclasses is because the client navigating the relationship cannot accept the implementations provided by other subclasses. [Not quite true; there is an arcane situation where the relationship enforces an problem space rule that is orthogonal to the subclass implementation. But let's not go there.] In your case, though, IScreen seems to be able to access either subclass implementation. Therefore IScreen is indifferent to which subclass is actually there. Therefore IScreen should collaborate with IShape. So... > > In that approach the concrete classes depend on the interface class, > which is as far as I understand "OK" or "not bad". But I've read in > "Head First Design Patterns", that it is best to create dependencies > between "high level abstractions" i.e. for example between interfaces > (pure abstract classes in C++). So I've come up with the following > approach: > approach 2: http://img230.imageshack.us/my.php?image=approach2fg8.png This is better. Now a screen doesn't care which flavor or a shape it is collaborating with. > > But is this 2nd approach really better? I mean now (in the 2nd case), > in the (interface) class IShape I have to store some kind of reference > to the (interface) class IScreen (in C++ for example a pointer). > Somehow I am not happy with the idea. What was a nice pure abstract > class now has some kind of physical weight. And in Java I wouldn't > even be able to do that, since you can't store any data in that > "purest form of interface", right? Moreover why should I be forced to > store any kind of reference to IScreen, what if I wanted some of my > shapes to use ISoundSystem, anyway this has become less universal than > the first approach I think. I'm afraid I don't see your concern. The relationships need to be implemented in both versions. The directionality of navigation will depend on how they collaborate: case I: a shape invokes screen behaviors. Then you need a pointer in IShape to reach IScreen. case II: a screen invokes shape behaviors. Then you need a pointer in IScreen to reach IShape. [This is a problem in the first version for two reasons. One is that you need two pointers, one for each flavor of shape. The second is that a screen needs to make a decision about which pointer to navigate when it needs to collaborate with a shape. Avoiding burdening IScreen with that decision manifests why your second version is preferable.] In either case the relationships need to be implemented in the same way in both of your versions because the class semantics hasn't changed so <presumeably> the collaborations haven't changed. At the OOPL level, it is difficult to implement such pointers, especially in statically type languages, without introducing a lot of physical coupling. One reason why the second version is preferred is that by abstracting the interface via superclasses one can reduce the amount of physical implementation detail that is exposed to the compiler. > > PS. don't mint borderColor variable in IShape, pretend it is not > there :) I don't think they can be ignored because they are crucial to why IShape exists in your first example. They introduce true implementation inheritance, which is an entirely different issue than relationship implementation and collaboration between a screen and shape. Implementation inheritance is one of those early OOPL features that seemed like a good idea at the time but turned out badly when tested over time. As a general rule one should avoid implementation inheritance. The problem is that it introduces a new dimension into how behaviors are resolved. That is, the client can see a different behavior depending on where an inherited implementation is introduced in the tree and where it is overridden. That tends to be a maintenance nightmare when somebody performs surgery on the generalization tree because it can break existing <superclass> clients. If one has common implementation actions, one can eliminate the redundancy with private class methods that the public leaf subclass behaviors invoke. Then the client does not need to worry about whether the behavior will change based on where in the tree the collaboration access is done. Since the behavior is always explicitly defined at the leaf level all the client needs to check is that the relevant leaves invoke acceptable private methods. ************* There is nothing wrong with me that could not be cured by a capful of Drano. H. S. Lahman hsl(a)pathfindermda.com Pathfinder Solutions http://www.pathfindermda.com blog: http://pathfinderpeople.blogs.com/hslahman "Model-Based Translation: The Next Step in Agile Development". Email info(a)pathfindermda.com for your copy. Pathfinder is hiring: http://www.pathfindermda.com/about_us/careers_pos3.php. (888)OOA-PATH
From: Firkraag on 20 Dec 2007 12:22 Oh boy. The UML diagrams which I provided given were "reverse_engineered" from my C++ code in a free UML tool. The thing is I made a mistake and also when it comes to dependencies that tool doesn't take forward declarations into consideration. Its only concern are header file dependencies. Here is the whole diagram, hopefully good now (I've manually added the association arrow from DrawLogic to IShape. I am really sorry for creating confusion, although maybe a good thing have come out of it :) http://img525.imageshack.us/my.php?image=fig1sz2.png The original question holds: Should I leave it as it is (approach 1), or make it so IShape holds a pointer to IScreen (but what kind of "I" it is then)?
From: Daniel T. on 20 Dec 2007 14:33
Firkraag <firkraag07(a)tlen.pl> wrote: > Oh boy. The UML diagrams which I provided given were > "reverse_engineered" from my C++ code in a free UML tool. The thing is > I made a mistake and also when it comes to dependencies that tool > doesn't take forward declarations into consideration. Its only concern > are header file dependencies. Here is the whole diagram, hopefully > good now (I've manually added the association arrow from DrawLogic to > IShape. I am really sorry for creating confusion, although maybe a > good thing have come out of it :) > > http://img525.imageshack.us/my.php?image=fig1sz2.png > > The original question holds: Should I leave it as it is (approach 1), > or make it so IShape holds a pointer to IScreen (but what kind of "I" > it is then)? The main thing that strikes me is that Rectangle and Triangles send messages to IScreens, but I don't see any way of telling a Rectangle or Triangle what screen to use. I expect that, through some means not shown, DrawLogic tells IShapes what IScreen they can use. If that is true, then IShape should have an association line to IScreen. BTW, just because the UML shows an association between IShape and IScreen (rather than between each IShape sub-type and IScreen) you need not implement it that way. You can still put an IScreen pointer in each IShape sub-type. What you will have once you put the association between IShape and IScreen is the Bridge pattern from GoF. |