|
Prev: showing progress of some processing
Next: FINALLY: Another language adopts Smalltalk's keyword -syntax
From: Veloz on 26 Dec 2007 11:04 > Just to be clear, if other objects than A need to collaborate with B, > then B should be a peer object and A should contain only a reference to > it (or some other implementation of the relationship to a peer B). B's > responsibilities should not be available through A's interface (i.e., by > A acting as a middleman in collaborations). > > Other objects do need to collaborate with B.. B is actually a list of objects in this case. Imagine that A is a "book" class and B is a list of chapter objects. Both the book and the chapters are objects in their own right. Code may want to interact with A's methods (add chapters, etc) and may also want to interact with the chapter objects (add a sentence, for example) Now, the List holding the chapters is of course a legitimate object too. So we really have three kinds of objects. The Book object (what we've been calling A), the List object (what we have been calling B) and the actual chapter objects. The list of chapter objects is very strongly related to A (the Book). A "has" a "list" of "chapters". In the actual software, A will have a field of List type, to which outsiders should be able to add chapter objects and/or interact with existing chapter objects. The question was whether or not I should expose the List type fully and publically (i.e., let code interact with the list object directly), or whether I should view the list as a private detail of how I am storing the chapters, and instead expose some methods in A that let outside code work with the list semantically. That is, instead of outside code calling A.TheList.Add(chapter), which shows exposing TheList directly, the user would call A.AddChapter(chapter) and privately A, would call TheList.Add(chapter). It also seems to me that A should be the primary "holder" of TheList of its chapters. That is, if some software wants to interact with a certain chapter belonging to a certain book, it should expect to have to interact with that book object (i.e., to get access to the list of chapters). So from this perspective, A and TheList are peers in that they are both public objects in the system. They are parent/child in that A is the original and permanent" "holder" to the reference to TheList , Other code may make reference to TheList (or to objects in TheList) but in any event, A can always be relied on to be the "authority", having the reference to TheList of its chapters. Perhaps the way I am using parent, owner, and sibling is confusing this topic? > > > Concretely, if a Test Product contains a List (of Feature Groups) then > > we are saying client of Test Product should be able to interact with > > the List without having A be the middleman, right? > > Yes. > > > This makes perfect sense to me... but there are other posts out there > > saying that A should "hide" B (as an implementation detail) and > > surface its own interface, which might in some cases delegate to B, > > privately. I think that's what was confusing me... > > The problem lies in the OOPLs. They are 3GLs and they must necessarily > make compromises with the hardware computational models. Thus all the > OOPLs are inherently procedural and employ procedural message passing, > procedural scope, and procedural block structuring because that is what > 3GL abstraction of the computer is about. (Though some OOPLs, like > Smalltalk, do a good job of hiding it.) So the 3GL type systems marry > message and method via a procedure signature. > > As I mentioned, that leads to substantial problems with physical > coupling than can make the code difficult to maintain. A large portion > of dependency management refactoring guidelines are aimed at mitigating > that physical coupling. (Dependency management is largely irrelevant in > UML OOA/D models because they use class systems rather than type systems > and separate message and method.) > > The notion of "hiding" B is rooted in a desire to mitigate physical > coupling by using encapsulation. That is fine up to a point. A lot of > refactoring technique involve creating abstract classes and "buffer" > objects (e.g., the Facade pattern) to hide the details from clients. But > those classes are just providing generic access and they are dedicated > to that generic access. > > However, when A exposes B's properties in its interface, one really > isn't hiding B; instead one is just duplicating B in A's interface. IOW, > A already has a unique suite of responsibilities in the problem solution > before one worries about dependency management refactoring. In effect, > one is adding B's properties to those that A already has. That is what > is creating the problem because one is trashing A's cohesion. > > That is quite different than providing a facade or delegation to hide > /details/. Consider a typical POS web site where the user navigates a > hierarchy of pages. One can describe that hierarchy with a single class: > > 0..1 child of > [Page] ----------------+ > | 0..* | > | parent of | R1 > | | > +-------------------+ > > The R1 relationship captures everything one needs to know about the > hierarchy. Now one can navigate up and down the tree easily using > accessors in [Page]. But that gets tricky when one needs to keep track > of where one has been (e.g., for a depth-first traversal). So a common > solution is: > > [Tree] > | 0..1 > | root of > | > | R2 > | > | 1 0..1 child of > [Page] ----------------+ > | 0..* | > | parent of | R1 > | | > +-------------------+ > > Now all the [Page] does is hold page data and referential attributes to > instantiate R1. The traversal algorithm is encapsulated in [Tree] and it > keeps track of where it has been. > > This is a case where the clients now all talk to [Tree] to navigate the > tree (e.g., via getNext, get(linkID), or whatever). Inserting the [Tree] > class to encapsulate the traversal algorithm makes the solution more > robust. It also very effectively hides the details of the tree > organization from clients (e.g., the tree might actually be implemented > as a B-Tree or Red-Black tree). To that extent "hiding" [Page] is a Good > Thing. > > But you will note that [Tree] is a new class expressly created to do the > hiding and it has no other responsibilities than traversing the > structure, which is inherent in the implementation of the tree. IOW, > [Tree] is a delegation of responsibilities of [Page] in the first > diagram. The result is a new [Tree] class and a simplified [Page] class. > Also critical to dependency management, [Tree] provides a more generic > interface for clients so that they do not have to know how the tree is > actually implemented. Thus one creates [Tree] to hide details of [Page] > from clients. > > OTOH, note that the clients still talk directly to [Page] to do page > collaborations (e.g., getItemName or getPrice). They just get the Page > reference from Tree. IOW, [Tree] is a smart list collection. But they > don't talk to [Tree] about [Page] responsibilities. They only talk to > [Tree] about tree navigation responsibilities, which are no longer > embedded in [Page] accessors. > > If I was going to provide a pithy summary of this distinction it would > be: don't hide detail by adding responsibilities to other objects that > already have disparate responsibilities in the solution. Which is just > another way of saying the Separation of Concerns Rules. > > ************* > There is nothing wrong with me that could > not be cured by a capful of Drano. > > H. S. Lahman > h...(a)pathfindermda.com > Pathfinder Solutionshttp://www.pathfindermda.com > blog:http://pathfinderpeople.blogs.com/hslahman > "Model-Based Translation: The Next Step in Agile Development". Email > i...(a)pathfindermda.com for your copy. > Pathfinder is hiring:http://www.pathfindermda.com/about_us/careers_pos3.php. > (888)OOA-PATH
From: Daniel T. on 26 Dec 2007 12:42 Veloz <michaelveloz(a)gmail.com> wrote: > "Daniel T." <danie...(a)earthlink.net> wrote: > > Veloz<michaelve...(a)gmail.com> wrote: > > > To make sure I am understanding you - you are saying if A contains an > > > instance of B, and you want clients to have access in some way to B's > > > functionality, then A should expose B as a public type (or at least be > > > able to return a reference to B when asked) so that clients can deal > > > directly with B and not have to "ask A to ask B" for the things it > > > needs. > > > > The above is correct as far as it goes, however I'm left wondering... > > Why do you want clients to have access in some way to A's guts? > > .....A is a dynamically created entity, and we have a Use Case that > says the user can manually alter this entity later. Most of the "guts" > of a consists of a list of objects with which the user will be > interested (in adding and deleting items, for example). So the > principal question was: should A just expose that list publically and > let other software interact with that type's methods/attributes, or > should A "wrap up" this list with its own accessors, and privately > delegate to the list? And the answer is the same to the above as to this question... Is A simply a container of objects, or is A important in its own right? To go back to the top. If A contains an instance of B and you want clients of A to have access in some way to B's functionality. The first question I have is, why does A contain the instance of B? It seems to me that the client of A should contain the instance of B, after all, that is who is using it. I actually had this situation come up in my current project. I had a BottomScreen class that contained several different things that all went on the bottom screen (of the NintendoDS.) I kept finding myself trying to reach into the BottomScreen object to work with its parts directly. Upon further analysis, I found that the BottomScreen class was wholly unnecessary and removed it. Now you may say, "but both A and it's client uses B." In that case, the client should have an A and a B and pass its B to the A to use.
From: Veloz on 26 Dec 2007 14:14 > And the answer is the same to the above as to this question... Is A > simply a container of objects, or is A important in its own right? Well that depends on the client's point of view. For the client that wants to find out which Feature Groups are "in" this Test Product, the Test Product pretty much looks like a container with the added ability to calculate an overall price. To the clients who want to get a list of non-duplicate features in the Test Product, Test Product is something that provides this behavior; these clients are not interested in, or aware, that the Test Product is also a container for the Feature Groups. > To go back to the top. If A contains an instance of B and you want > clients of A to have access in some way to B's functionality. The first > question I have is, why does A contain the instance of B? It seems to me > that the client of A should contain the instance of B, after all, that > is who is using it. I agree with you in principal here. Just to make sure...When I say that A "contains" B I don't mean the class definition for B is part of A's definition. I just mean A has an instance variable of type "B" (and B specifically is a List) and if someone wants to get at B, they have to go through A. In my case, it's like A and B have a whole/part relationship. As I mentioned in a previous article, kind of like a Book object with a List of its Chapter objects. If the parts (The list of items in B) are not known to A, how could A get at its own parts? And how would the outside world know how to get to the B related to A, except by navigating through A to get at them? > I actually had this situation come up in my current project. I had a > BottomScreen class that contained several different things that all went > on the bottom screen (of the NintendoDS.) I kept finding myself trying > to reach into the BottomScreen object to work with its parts directly. > Upon further analysis, I found that the BottomScreen class was wholly > unnecessary and removed it. > So in this context, though, you have a set of objects related to the "one and only" Bottom Screen. So having BottomScreen as a "container" was not useful. I can see this. But what if you have lots of Bottom Screens (like I have lots of Test Products)... Would you want to put these objects as contained children of BottomScreen objects? > Now you may say, "but both A and it's client uses B." In that case, the > client should have an A and a B and pass its B to the A to use. I like this idea too. But going back to the whole/part example of a Book and its Chapters. Someone can allocate the chapters as a separate list of objects and then tell the book "here are your chapters". From that point on, however, wouldn't the Book "hold on" to this list so that some later code could get to its chapters? Or put another way, if the Book wasn't the authority (i.e., the one holding the "permanent" reference to its chapters) how would some code find a given book's chapters? We could have a separate list of chapter lists floating out there, and each could carry the name of its book... but why not make a stronger association between the book and its contained chapters? M
From: Daniel T. on 26 Dec 2007 19:34 Veloz <michaelveloz(a)gmail.com> wrote: > > Just to be clear, if other objects than A need to collaborate with B, > > then B should be a peer object and A should contain only a reference to > > it (or some other implementation of the relationship to a peer B). B's > > responsibilities should not be available through A's interface (i.e., by > > A acting as a middleman in collaborations). > > Other objects do need to collaborate with B.. B is actually a list of > objects in this case. Imagine that A is a "book" class and B is a > list of chapter objects. Both the book and the chapters are objects > in their own right. Code may want to interact with A's methods (add > chapters, etc) and may also want to interact with the chapter objects > (add a sentence, for example) > > Now, the List holding the chapters is of course a legitimate object > too. So we really have three kinds of objects. The Book object (what > we've been calling A), the List object (what we have been calling B) > and the actual chapter objects. > > The list of chapter objects is very strongly related to A (the Book). > A "has" a "list" of "chapters". > > In the actual software, A will have a field of List type, to which > outsiders should be able to add chapter objects and/or interact with > existing chapter objects. > > The question was whether or not I should expose the List type fully > and publically (i.e., let code interact with the list object > directly), or whether I should view the list as a private detail of > how I am storing the chapters, and instead expose some methods in A > that let outside code work with the list semantically. That is, > instead of outside code calling A.TheList.Add(chapter), which shows > exposing TheList directly, the user would call A.AddChapter(chapter) > and privately A, would call TheList.Add(chapter). > > > It also seems to me that A should be the primary "holder" of TheList > of its chapters. That is, if some software wants to interact with a > certain chapter belonging to a certain book, it should expect to have > to interact with that book object (i.e., to get access to the list of > chapters). > > So from this perspective, A and TheList are peers in that they are > both public objects in the system. They are parent/child in that A is > the original and permanent" "holder" to the reference to TheList , > Other code may make reference to TheList (or to objects in TheList) > but in any event, A can always be relied on to be the "authority", > having the reference to TheList of its chapters. > > Perhaps the way I am using parent, owner, and sibling is confusing > this topic? Perhaps... When we (i.e. HS and I) talk about A and B (as in [A]--->*[B]) it is assumed that there is a list/bag/vector/set class that is actually in A that holds all of the Bs. Such a class is strictly part of the solution space, not the problem space (and it conforms to a somewhat different set of rules.) We are generally ignoring it. That said, what is A good for? If all A does is iterate through the list it contains, then it adds no value. In order for A to have value, it must provide something that the simple list can't provide. It must have an invariant. Once you give A some value (expressed as an invariant,) then it is easy to see that you can't let just anybody add/remove/change items in the list (i.e., you can't expose the list) because that would make it impossible for A to ensure its invariant.
From: Veloz on 27 Dec 2007 10:17 Thanks for continuing with this thread, I am learning from our discussion..:-) >That said, what is A good for? If all A does is iterate through the list >it contains, then it adds no value. In order for A to have value, it >must provide something that the simple list can't provide. It must have >an invariant. I'm not totally sure what you mean by "invariant" here.. but: In our case, A is the Test Product class and what it provides, above and beyond containing list of Feature Groups, is the knowledge of how to provide a total price, given access to the Feature Groups and some additional data we have not talked about. So I guess that's what "A is good for"... Also, it knows how to provide a de-duped list of features from across the Feature Groups it contains. So I think A does provide some use above and beyond acting as a container.... >Once you give A some value (expressed as an invariant,) then it is easy >to see that you can't let just anybody add/remove/change items in the >list (i.e., you can't expose the list) because that would make it >impossible for A to ensure its invariant. Hmm. Interesting. As it currently stands, A doesn't much care who adds/ deletes features from the feature groups, or who adds/deletes feature groups from the Test Product (A)... at the point of being asked to calc the cost, it iterates over whatever feature groups are currently contained in it and asks them for their price as of that point in time. To this, it adds some other amounts the come from elsewhere. So from this perspective, A doesn't need to "protect" the list of the Feature Groups or the content of the Feature Groups. ... Makes me wonder how strong the association between A and B really is. If A doesn't care what's in the B (the list of Feautre Groups) then.... then ... then what?? :-) M
First
|
Prev
|
Next
|
Last
Pages: 1 2 3 4 Prev: showing progress of some processing Next: FINALLY: Another language adopts Smalltalk's keyword -syntax |