From: H. S. Lahman on
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
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
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
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
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.