Re: Design help...an explosion of interfaces......
From: H. S. Lahman (h.lahman_at_verizon.net)
Date: 08/29/04
- Previous message: Chris Hanson: "Re: [NEW!!] Binding together Properties of Objects"
- In reply to: Daniel T.: "Re: Design help...an explosion of interfaces......"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
Date: Sun, 29 Aug 2004 15:54:02 GMT
Responding to Daniel T....
>>>I'm not sure I would use an isBuffered attribute unless a Pipe was able
>>>to change from buffered to unbuffered and/or back. IE using an attribute
>>>this way implies that the attribute won't allways be the same for a
>>>particular object. (for example, Rectangle::isSquare may return true or
>>>false depending on the state of that particular rectangle.)
>>
>>I don't see a problem. There is no rule that requires knowledge
>>responsibilities to be volatile. On the contrary, when one does
>>parametric polymorphism the specification objects usually have fixed
>>values throughout their lives and the variability lies in dynamic
>>instantiations of their relationships.
>
>
> I'm not saying there is a problem, its just not my preferred way of
> working. I don't see much difference between:
>
> void foo( IPipe& p ) {
> if ( p.isBuffered() )
> // do buffered things
> else
> // do unbuffered things
> }
>
> and
>
> void foo( IPipe& p ) {
> if ( dynamic_cast<IBufferedPipe*>( &p ) )
> // do buffered things
> else
> // do unbuffered things
> }
I didn't read this as Nicholls' problem. Foo doesn't care whether the
pipe is buffered are not; it is just loading or extracting from the pipe
and it couldn't care less how Pipe does its thing. IOW, foo does Pipe
things, not buffered pipe things or unbuffered things. It is the
overall problem solution context that demands that the client only
collaborate with a buffered or unbuffered specialization.
* R1 1
[Client] ------------------ [Pipe]
+ foo A
|
+-------+-------+
| |
[Buffered] [Unbuffered]
If one tries to enforce type safety doing that one has to invoke
void foo (IBufferedPipe p)
{
p.load(...)
}
but Nicholls' problem is that another Client may need to collaborate
with an unbuffered pipe. So one needs a different method:
void fooPrime (IUnbufferedPipe p)
(
p.load(...)
}
or some kind of overloading, which is what I believe prompted the OP.
In addition, the client of Client.foo needs to understand the context
and call the right method with the right Pipe subclass instance. That's
because Client's client is also instantiating the relationship in
addition to collaborating with Client.
So my answer is not to pass a pipe to foo at all. Let some third party
who understands the buffering context instantiate the relationship and
then one has:
void foo ()
{
myPipe.load(...)
}
It would only be that third party that had to somehow make sure only
buffered or unbuffered pipes got into the relationship.
That might yield a factory that looked like:
void ClientFactory::factory ()
{
Pipe p
Client c = new Client
...
p = myPipeList.getNext()
if (p.isBuffered)
{
c.setR1 (p)
return
}
}
[Note that if the factory is creating Pipe instances, one doesn't need
any checking at all because the subclass instance will be known when
invoking new.]
In this simple example checking isBuffered is semantically equivalent to
checking the subclass by a downcast because the results are the same.
However, I argue it is not semantically the same thing. What defines
Buffered pipe is the nature of the behavior (i.e., its implied DbC
contract for that responsibility). However, when checking the property
we are only checking whether the instance in hand has the behavior, not
its nature nor anything about other properties that may define the
specialization. That distinction becomes clear when one deals with
multiple characteristics:
[Pipe]
+ type
A
|
+----------+----------+
| |
[Buffered] [Unbuffered]
A A
| |
+------+----+ +-----+-------+
| | | |
[Character] [Binary] [Character] [Binary]
We still have a single type variable but it has a data domain of
{BufferedAndCharacter, BufferedNotCharacter, NotBufferedAndCharacter,
NotBufferedNotCharacter}. That also exactly maps to the leaf subclass
but it is clearly not the same as testing the class via downcasting
(i.e., one doesn't need to "walk" the tree itself with the downcasts).
One can construct other solutions for the same semantics:
[Pipe]
+ isCharacter
+ type
A
|
+----------+----------+
| |
[Buffered] [Unbuffered]
Now type attribute has the same data domain but it is clearly
semantically different than simply mapping the subclass. (There are 1NF
reasons for not doing this, but that is orthogonal to the subclassing
issue.)
Bottom line: the type attribute has a different semantics than the class
type. One can abuse that by deliberately mapping it to the subclass and
then using it to navigate a subclassing relationship just like a
dynamic_cast. But in that case one has failed to properly instantiate
the direct subclass relationship so a good reviewer will jump on that as
quickly as on using a dynamic_cast.
Note that this ties back to my issue of the problem being solved above.
I would be using the isBuffered check to /instantiate/ the
relationship, not navigate it. One of the benefits of not passing
object references around is that it makes it clear when one is
instantiating vs. navigating relationships. As I pointed out to
Nicholls, one can view isBuffered as part of the implementation of the
direct subclass relationship just as one might need a static find(id)
method to locate specific instances when instantiating a relationship.
*************
There is nothing wrong with me that could
not be cured by a capful of Drano.
H. S. Lahman
hsl@pathfindermda.com
Pathfinder Solutions -- Put MDA to Work
http://www.pathfindermda.com
blog (under constr): http://pathfinderpeople.blogs.com/hslahman
(888)-OOA-PATH
- Previous message: Chris Hanson: "Re: [NEW!!] Binding together Properties of Objects"
- In reply to: Daniel T.: "Re: Design help...an explosion of interfaces......"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
Relevant Pages
|