|
Prev: showing progress of some processing
Next: FINALLY: Another language adopts Smalltalk's keyword -syntax
From: H. S. Lahman on 18 Dec 2007 11:16 Responding to Veloz... > If class A contains an object of class B, and A wants to expose some > or all of the abilities of class B, should A " > hide" B and provide an appropriate interface (in which it might > delegate some of the calls right on to B's native interface), or > should it just expose B as a public type? The short answer is that A should not be exposing any of B's properties in its interface. The reason is that OO communications are supposed to be peer-to-peer; the client talks directly to the service. As soon as A exposes B's properties, the B semantics is also "owned" by A (i.e., the specification of A's properties include the specification of B's properties). The reason is to provide maintainability in the face of requirements changes. Suppose the properties of B change. When that happens one necessarily needs to modify the client and B. If the client talks directly to B, then those are the only changes that need to be made. But if the client talks to A, then A's interface needs to change as well. Peer-to-peer communications in OO are designed to limit the scope of change. For example, for the model 1 R1 1 1 R2 1 [Client] -------------- [A] --------------- [B] + addX(...) we might have (in C++): client::doIt() { myA->addX (myX); } A::addX(X* x) { myB->addX(x); } B::addX(X* x) { // do something with x } Now suppose the requirements change and for B::addX to do its job the client must provide additional information: B::addX(X* x, int count) { // do something with x and count } In the example above A::addX also needs to change so that the client can provide the new information. Now consider what happens if the communication is peer-to-peer: client::doIt() { B* theB; theB = myA->getBForR2(); theB->addX(X* x); // original requirements theB->addX(X* x, myCount); // new requirements } class A { private: B* myB; // implement R2; public: B* getBForR2() {return myB;} // navigate R2 } Note that since relationship navigation is orthogonal to class semantics, we do not touch A to accommodate the change to B. But if we exposed addX in A's interface, we would have to modify it to accommodate that change. > > I know this is an age-old question, but I still debate it in my own > mind. > > An example: I have a class named Test Product (this is class "A" for > example's sake). It is comprised, in part, by one or more > "PricedFeatureGroup"s. That is, it contains a list (class B) of > PricedFeatureGroups. This multiplicity is even more reason for not exposing B in A's interface. Here we have: 1 R1 1 1 R2 * [Client] --------- [TestProduct] ---------- [PricedFeatureGroup] During OOP we would like to implement R2 with a collection class, preferably a very generic one so it can be reused. In fact, we want the relationship implementation, instantiation, and navigation to be as close to idiomatic as possible. This also allows us to encapsulate things like special searches and whatnot in the collection class rather than encumbering A with them. So at OOP time one wants that R2 collection class to be a peer of A and B. That allows the static relationship structure and its dynamic instantiation to be managed separately for the semantics and implementation of A. So one wants a Client to navigate through a TestProduct to the R2 collection and then navigate through it to whatever PricedFeatureGroup it needs. For example, if Client might want a blue PricedFeatureGroup, that selection is a private matter for the R2 collection class and [testProduct] should have no knowledge of what flavor of PricedFeatureGroup is needed by Client in its context. So Client navigates to the R2 collection and asks it for the right PricedFeatureGroup. Once CLient has done that navigation, it then talks directly to the right PricedFeatureGroup. Now consider what happens if A exposes B's properties in it interface. Since B no longer knows the collection class exists, it becomes part of A's implementation. So A must necessarily know the blue selection criteria, which is none of its business. [It could be a pass-through, but then we are back to the case above where A's interface must change if the nature of the selection (e.g., a different property, like small, is used) changes.] ************* 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 18 Dec 2007 17:24 Veloz <michaelveloz(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? 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. :-)
From: Veloz on 18 Dec 2007 14:10 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. 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? 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... M
From: Daniel T. on 18 Dec 2007 18:23 Veloz <michaelveloz(a)gmail.com> wrote: > Thank-you for your reply, Daniel. It's very helpful to discuss this > with someone else! Programming alone is great, but sometimes you get > lost in your own head too easily :-) I've tried to make this reply > more skimmable :-) > > You're diagrams and descriptions above have a lot correct in them, but > I need to clear up some things I didn't describe well. > I'm going to follow your lead and show some diagrams: > > Here's what I see as the logical relationships of the entities at this > point.. > > TestProduct 1---->* FeatureGroup 1--->* Features I understand that this is how you saw the relationships, but based on my current understanding of the problem domain, I think that the diagram is incorrect. Either I'm right, or I still don't understand enough about the problem domain. So, the question below... > - In the above "Design", the Test Product is both an access point for > the list of Feature Groups and a user of the Feature Groups as well. 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... 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?
From: Daniel T. on 17 Dec 2007 18:29 In article <5fbdaae6-535a-4a64-b63d-22359ebe62dc(a)d21g2000prf.googlegroups.com>, Veloz <michaelveloz(a)gmail.com> wrote: > Thanks for your reply; here is some more information (perhaps too much > more LOL.. sorry, sometimes you just have to explain things for them > to make sense <grin>) > > One of the programs main purpose's in life is to evaluate the > effectiveness of something known as Test Products, which is > essentially a list of features and a total cost. A real world example > of a Test Product is a cell phone and the features it contains might > be: small buttons, shockproof case, camera. etc.. each of which has an > individual price. > > THe user of the tool supplies a specification, and then a piece of > software known as a Generator looks at this specification and churns > out Test Products, each having different features and a total cost, > that another part of the software can offer to hypothetical customers, > to estimate which Test Products (combinations of features) would make > the most money. > > So far then we have a "Generator" that reads this specification and > produces "Test Products" that have Features and a total cost. > > If life were simple, the Test Product class could just be a List of > Features and an interested party could just iterate over the features > to sum up their costs to arrive at a grand total. > > However, two complications change the picture: A) The pricing > requirements are more complex than a simple sum, and B) The desire to > let the user alter a Test Product later on. > > With regards to pricing, the total cost of a Test Product is not just > the sum of the costs of the Features the Generator decides should go > into a given Test Product. > > In the specification I referred to earlier, the user actually creates > Feature Groups which each contain a list of features and a Pricing > Strategy. The Pricing Strategy is a user-selected algorithm that > indicates how features coming from this Feature Group should be priced > (e.g., should they simply be summed, should we sum and increase by a > percentage, and so on). > > So the first new wrinkle from the pricing requirement is this: to get > a grand total of pricing, some code needs to know which features came > from which Feature Groups, and it needs to invoke the associated > Pricing Strategy on those features to get subtotals that can then be > combined to get a grand total. > > The next wrinkle, coming from B, is allowing a user to edit a Test > Product after it is generated. The user is allowed to add or delete > features from a test product and its new price should be calculated. > > If the total price of a Test Product were the sum of the costs of the > features in the Test Product, adding/deleting features and > recalculating the total would be easy. But as it turns out, to add or > remove a feature and get the new price right that part of the software > will need to know which Feature Group each feature came from so it can > add/remove the features from the correct Feature Groups and then ask > then Feature Groups for new subtotals in order to get the grand total. > > > > SO. From the above, we can see that a Test Product has to provide the > following services: a list of features and a grand total, and a way > to add and remove features with the ability to get a new grand total > (which implies we know from which Feature Group each feature comes > from so we can correctly re-price the group later) > > To do this I came up with a Test Product class and a Priced Feature > Group class. > > The Priced Feature Group class contains a list of features and a > reference to a pricing strategy algorithm. Priced Feature Group has a > method, "Calculate Cost", which invokes its pricing strategy to figure > out the right cost for the features in the group. This class has add/ > delete methods to allow features to be added/deleted at any time, > after which, another call to Calculate Cost would produce a new total. > > The Test Product then holds a list of these Priced Feature Groups > (which are created by the Generator). Test Product also has a > Calculate Cost method (to get the grand total). When it is invoked, it > calls the Calculate Cost on each contained Priced Feature Group and > then sums them up (and adds in some other values I won't discuss > here). > > Later, if the user wants to add or delete features from a Test > Product, the software will be able to figure out which Priced Feature > Group that feature is in, and then it can add or delete the feature in > question from that Group, and ask the Priced Feature Group to cough up > a new total. > > WHen it's time to evaluate the overall product, Test Product iterates > over Priced Feature Groups to bring together the various features in > the group to produce one combined list of features that the evaluation > logic can use. > > So you can see, Test Product is both a container for the Priced > Feature Groups and a user of their content as well, which probably > complicates things. > > I could break the Priced Feature Groups out of the Test Product, but > the Test Product has such a strong relationships with each of its > Feature Groups that it seems weird to separate them. > > Thoughts? That was quite a brain dump. :-) I have to admit, I just skimmed... Some things I got from the above though (correct me where I'm wrong.) [TestProduct]<>--->*[Feature]--->[FeatureGroup] For a TestProduct to get its price, it iterates through its list of features and asks each one how much it costs. A feature determines its price by consulting the FeatureGroup it is associated with. TestProducts can also produce a list of included features, but Features cannot be modified through this list. Features can be added and removed to/from TestProducts. I'm imagining a C++ class that looks something like this: class TestProduct { public: void addFeature( auto_ptr<Feature> feature ); void removeFeature( string featureName ); Money price() const; vector<string> featureList() const; }; The 'addFeature' above looks odd though. Probably a better choice would be to have a FeatureFactory that uses the Flyweight pattern. Then we end up with something like this: class TestProduct { public: TestProduct( FeatureFactory* f ); void addFeature( string featureName ); void removeFeature( string featureName ); Money price() const; vector<string> featureList() const; }; User's of TestProduct need never deal with Feature objects.
|
Next
|
Last
Pages: 1 2 3 4 Prev: showing progress of some processing Next: FINALLY: Another language adopts Smalltalk's keyword -syntax |