|
Prev: showing progress of some processing
Next: FINALLY: Another language adopts Smalltalk's keyword -syntax
From: H. S. Lahman on 19 Dec 2007 16:17 Responding to Veloz... > 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. 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). > > 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 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: Daniel T. on 19 Dec 2007 21:15 "H. S. Lahman" <h.lahman(a)verizon.net> wrote: > The notion of "hiding" B is rooted in a desire to mitigate physical > coupling by using encapsulation. HS and I are usually on opposite sides of the fence when it comes to this particular question (at least as different as two reasonable people can get,) but I largely agree with his assessment. The only quibble, (and one might call it a big one,) is that his entire assessment of information hiding in 3GLs rests basically on the above sentence. Although some of the containment issues are rooted in a desire to mitigate physical coupling as he says, there is another use of containment (at the design level) that he isn't addressing... Containment simplifies design. The point of the containment relationship is to reduce the number of classes one must deal with at any particular level of the design. The ability to ignore the contained objects at some higher level of the design is extremely useful, even outside of a 3GL.
From: H. S. Lahman on 20 Dec 2007 11:00 Responding to Daniel T.... This is more for the OP than for you... B-) >>The notion of "hiding" B is rooted in a desire to mitigate physical >>coupling by using encapsulation. > > > HS and I are usually on opposite sides of the fence when it comes to > this particular question (at least as different as two reasonable people > can get,) but I largely agree with his assessment. The only quibble, > (and one might call it a big one,) is that his entire assessment of > information hiding in 3GLs rests basically on the above sentence. As far as it goes, that assessment is pretty much correct. However,... > > Although some of the containment issues are rooted in a desire to > mitigate physical coupling as he says, there is another use of > containment (at the design level) that he isn't addressing... > Containment simplifies design. > > The point of the containment relationship is to reduce the number of > classes one must deal with at any particular level of the design. The > ability to ignore the contained objects at some higher level of the > design is extremely useful, even outside of a 3GL. I see this as a different issue. If the problem space requires containment, then one already has design patterns like Facade that will deal with it. But containment is largely a non-issue at the OOA/D level, which is why I don't worry about it much. One reason is that the GoF approach of combining delegation and polymorphic dispatch tends to provide (IMO) a more natural and robust solution. Another reason is that containment pretty much comes for free with good A&D methodology in the OOA/D. For example, containment is really about extracting invariants and providing generic interfaces based on those invariants. I see encoding invariants as a fundamental part of OO problem space abstraction. Similarly, one has methodological techniques for separating concerns and ensuring abstraction cohesion that operate to produce the same effects as containment (as in my POS site navigation example). IOW, once one has separated concerns, identified the invariants, and ensured object cohesion one doesn't need to think about containment directly; it is already a fait accompli. The last reason is that OOA/D abstracts problem spaces; OOA for the customer problem space and OOD for the computing space. There really isn't a lot of wiggle room there because one needs to abstract the way the problem space works. IOW, What one models is not negotiable; all the modeler can do is provide accuracy, elegance, and robustness. Thus delegation is not a convenient trick to make the developer's life easier (as it often is in OOPL dependency management); it has to already be identifiable in the problem space. So one can't create objects that a domain expert can't recognize by name and those objects must abstract properties that the domain expert would naturally associate with the entity. [It was no accident in my POS example that the new object was called Tree. That <conceptual> entity already exists in the problem space and a domain would naturally associate an understanding of the data structure with it. If I named it NavigationManager, the reviewers would bring out the crucifixes and garlic cloves.] Thus one must live with whatever the problem space dictates. The best one can do is apply methodological techniques to the abstraction to ensure robustness. But those techniques (problem space abstraction, extracting invariants, cohesion, etc.) are <usually> at a different level of abstraction than the notion of containment. So one gets to the same robust place but it is via a different path in the OOA/D than it is in OOP. <aside> It is my assertion that if the OOA/D is done properly, many of the dependency management concerns of OOPL coding would go away. For example, most of the problems I see in LoD and LSP discussions are pretty much non sequiturs at the OOA/D level. That is, if the OOA/D had been done properly up front the issues triggering the discussion simply wouldn't arise during OOP. As a result one very rarely ever even thinks about LoD or LSP when creating an OOA/D model and yet the model will Just Work when correctly transformed into 3GL code by a code generator. </aside> ************* 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: Veloz on 26 Dec 2007 10:40 > What does a TestProduct use a FeatureGroup for that doesn't directly > relate to some feature or features that the TestProduct contains? Up to > this point, the only raison d'etre that you have given the FeatureGroup > class is that it modifies the price of Features... Well those are good questions. I don't know how to give you a short answer :-( .. If you can bear with me, I think I can make this a lot clearer: Let's talk about the domain for a second. In the domain, the user thinks of something called a "test product" which is a series of non-duplicate features, collectively having a price. An example of a test product is an automobile having features A, B, C and D, and costing a total of $25,000.00 We "offer, or test" this Test Product against a series of hypothetical shoppers in the software to see who might purchase this Test Product. This allows the software to compare multiple Test Products and determine which would be more likely to sell than others. (Ultimately this is the purpose of the tool, to determine the "best" Test Products) The process of offering test products to shoppers uses other information and algorithms that probably don't need to come into this discussion. Now here's where things get weird. The user does not define a series of Test Products per se. That is, they do not define N Test Products they want to software to test, and pick and chose which features will go into each. That is because, in the real domain (testing cell phones or testing automobiles) the list of possible test products could be huge if many combinations of features are allowed. Imagine if you have a product that offers 24 different features in any combination with each other.. there's no way you could manually list each such test product) Instead, the users build a specification that contains one or more groups of their desiring. In each grouping they can place any number of features, and a feature can appear in more than one group. Each group is assigned a pricing strategy, which is an algorithm that knows how to calculate a total for features coming from this group (it may be a simple sum, a percentage of the total, etc, etc). Each group also has a disposition, which is a kind of tag that identifies the nature of this group and to some extent dictates some other behavior of this grouping (for example, how features that repeat across groups should be reconciled.. since a real-life product does not usually allow you to have two of the same feature). The expectation is that the software will use this specification to create Test Products on the fly... and each Test Product will contain a different and unique combination of features, taken from the groups. To create the unique set of features and to arrive at a total cost, the software iterates over the groups, and (using certain business rules related to the disposition of each group) extracts out features from each group in all possible combinations. When it extracts features out of a given group, it has to know how much those features cost. The pricing strategy for the original group is used to come up with a cost for the features that came from the group. To be a bit more clear then, let's say the user defines two groups: Group 1 ...feature A, $50 ...feature B, $25 ...feature C, $100 pricing strategy= "sum of features" Group 2 ...feature A, $50 ...feature D, $10 ...feature E, $5 ...feature F, $20 pricing strategy = "sum of features, increased by 15%" To create Test Products from the above groupings the software will try to create every possible feature combination, using features A,B,C, D,E. (Ignore duplicating A for now) Thus, some of the Test Products generated by the code might be: Test Product 1: A Test Product 2: A,D Test Product 3: A,D,E and so on. You can see that the software "looked inside" the groups to see what features each contains, so that it can create all possible combinations of them. In reality, the combination logic is more complex than this (the dispositions I spoke of earlier come into play) , but this suffices for our discussion. Also note that due to this "create every combination" logic, there will be test products produced that do not have certain groups represented at all. As to the price of each of the above Test Products, , that's where the pricing strategies come in: each feature or features present in the Test Product must be priced using the pricing strategy assigned to the group from which the feature(s) came. Let's consider the price of Test Product 3, having features A, D and E. Feature A came from group 1 whose strategy is "simple sum". In this case, we only have that one feature in the Test Product coming from group 1, so there's no one to really sum him with. Thus, the cost for feature A is $50, which is just A's stated cost. Now, features D and E both came from group 2 whose pricing strategy is "sum + 15%". So we add up the cost of D and E, and arrive at $15.00. According to the pricing strategy we increase that by 15% to arrive at a cost of $17.25. Thus, the total cost of Test Product (having features A, D, and E) is 50 + 17.25 = 67.25 The last wrinkle in the domain I need to mention is that, after the software has generated and tested Test Products, the top N "best" ones can be captured by the user. They want to be able to edit the features in the Test Product (adding or deleting features) so that they can test them again. When they manually add delete features, they want the Test Products price to be recalculated. You can also see that to update the total cost of a Test Product, after adding or deleting features, you can't simple add or delete the stated cost of the feature that was added or deleted. This is because a given feature's contribution to the Test Product's original price may not simply have been the features stated price.. the pricing strategy for the group may have effectively caused a feature to cost less or more. Let's consider Test Product 3. Let's say the user wants to drop feature E. We can't just subtract $5 from the overall 67.25, because, due to the pricing strategy that was originally used for feature E, it didn't contribute exactly $5. So, here's a HUGE rub: in order to recalculate the cost of a test product after the fact, the pricing strategies need to be considered. I.e., if the user wants to drop feature E, we must know from which group feature E came. After we drop feature E, we can re-apply the pricing strategy for that group. In this case, we had features D and E from group 2. Now that the user has dropped E, we only have D coming from the group. Applying its "sum + 15%" rule, we see that feature D contributes $11.50 to the total cost. Thus the new total cost for the test product is $50 (feature A) + $11.50 (feature D) for a total of 61.50 Likewise, if the user wants to add a feature to a test product, they will have to indicate a group to add it to.. so that we know what pricing strategy should be used. ...................................Now, back to the software. You can now see that: 1. At some point, we need the notion of a Test Product..i.e., a list of features having a total price (so that we can offer this to hypothetical shoppers) 2. In order to create a Test Product, we need to consider the groups and their pricing strategies. Initially, Test Products don't themselves "have features". The groups have features. 3. In order to alter a Test Product (once it has been "built" from the groups) we need to know from which group each feature came, so that we can consult the pricing strategy to recompute the cost of given features. So in my design, the Test Product is a class that is responsible for 1. Producing a list of non duplicated features and a grand total cost 2. Allowing for the addition/subtraction of features, and the correct re-calculation of the grand total. To meet these responsibilities, the Test Product 1. Contains a list of Feature Groups. Each Feature Group contains a list of features and a pricing strategy. The group won't necessarily contain all the features the user placed in the corresponding group in the "specification"..just the features the Test Product generation "took" from a given group to build this particular Test Product 2. Has a "GetUnifiedFeatureList()" method which can iterate over the group's features and produce a non duplicated list of features making up the test product 3. Has a "CalculateCost()" method which invokves the CalculateCost() method of each Feature Group it contains to get the cost of features from the groups. These are summed up to arrive at the grand total. So you can see we have a couple Use Cases using Test Products elsewhere in the software: 1. The "Evaluate Test Products" use case that wants to offer Test Products to shoppers.. Its calls the GetUnifiedFeatureList() and CalculateCost() method to get a list of features making up the test product and a total ost. 2. The "Manually modify a Test Product Use Case". This use case needs to show the users the current groups and features that make up the current test product so that it can add and delete features in these groups. When this is complete, the overall TestProduct.Calculate() cost can be called and it in turn will ask the contained feature groups (which now presumable have more or fewer features than before this use case) to calculate their totals. .... I doubt you have read all the above!.. But in case you have: So you can now finally see (I hope) that a Test Product on one hand has to appear as a list of features with a grand total, and on the other hand, has to actually be comprised of a series of Feature Groups with a pricing strategy so that adding/deleting features can be supported. > Again, from what I know of the problem thus far, Clients add Features to > TestProducts, not FeatureGroups. What class decides which FeatureGroup > is associated with which Feature? As I described above, human users, in fact, add features to Feature Groups. Another class, known so far as a "Generator" can look at the "specification" and pick and chose which Feature Groups (and features therein) should "go into" a given Test Product. Looking forward, to your reply :-) Michael
From: Veloz on 26 Dec 2007 10:44 On Dec 18, 5:24 pm, "Daniel T." <danie...(a)earthlink.net> wrote: > Veloz<michaelve...(a)gmail.com> wrote: > > Thank-you for your thoughtful and thorough reply. I love those kinds > > of replies. > > > 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? > > Now, if A doesn't really contain an instance of B, it just serves as a > convenient holding place for the B, then that may be another matter. > > > 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... > > HS doesn't hold to this convention. Of course, HS doesn't code in 3GL > languages so he doesn't have to deal with the mess. :-)
First
|
Prev
|
Next
|
Last
Pages: 1 2 3 4 Prev: showing progress of some processing Next: FINALLY: Another language adopts Smalltalk's keyword -syntax |