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