Re: C++ design question
From: H. S. Lahman (h.lahman_at_verizon.net)
Date: 09/29/04
- Next message: H. S. Lahman: "Re: File Saving Strategies"
- Previous message: Kurt: "Re: So let's build a router"
- In reply to: Simon Elliott: "Re: C++ design question"
- Next in thread: Kurt: "Re: C++ design question"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
Date: Wed, 29 Sep 2004 21:20:09 GMT
Responding to Elliott...
>>fooDerived (int i1, int i2)
>>{
>>this.bar_base_ref_ = (barBase&) my_bar_dervice;
>>this.my_bar_derived.setI1(i1);
>>this.my_bar_derived.setI2(i2);
>>}
>
>
> AFAIK this isn't valid C++. It's necessary to initialise references
> from the constructor's init list.
The default (no argument) constructor is implied in the initialization list.
>
>>Why not invoke the barDerived constructor? Because constructors are
>>notoriously fragile and it is wise to do as little as possible inside
>>them. A good rule of thumb is to limit them to simple data
>>assignments. Explicitly invoking another constructor within a
>>constructor is an unnecessary complication. If the initialization of
>>an instance is necessarily complex, there should be a separate
>>initializer method that separates those rules and policies from the
>>more mundane housekeeping of instantiation.
>
>
> That would mean removing barDerived's current constructor, and
> replacing it (explicitly or implicitly) with a default constructor.
> Which would allow users of this class to instantiate it without
> initialising i1_ and i2_.
The default constructor is always there, regardless of how many
alternatives you supply, so anyone can instantiate it without defining
i1_ and i2_ anyway.
[There is a reason why it is a good practice to explicitly put the
default constructor in the class header rather that allowing it to be
implicit, but I forget what it is. I am a translationist, so I haven't
written any C++ code in over a decade. B-)]
>
>
>>However, I have a more abstract question: Why have both
>>my_bar_derived and bar_base_ref_? my_bar_derived is private so
>>there is no issue of it being accessed by some external object.
>>Since it is an embedded object, it is a matter of private
>>implementation to know that it is a member of the barDerived class.
>>Therefore casting of bar_base_ref_ does not risk type safety if
>>specialized properties are accessed within fooDerived.
>
>
> This is an interesting question, which addresses design issues rather
> than C++ semantics.
>
> The reason fooBase has a bar_base_ref_ is to allow this to work:
> my_foo_derived.DoStuffWithBarBase();
>
> In the full scale design, DoStuffWithBarBase() calls a virtual function
> in barBase which does different things depending on the implementation
> of barDerived. And there's a fooDerived1 with corresponding
> barDerived1, fooDerived2 with corresponding barDerived2, fooDerivedN
> with corresponding barDerivedN.
>
> However, this design relies on an object in a derived class, which puts
> a burden on developers of the various fooDerivedN.
I still don't see it as a design problem in the OOA/D sense, but a
little OOA/D might help the discussion. B-) If I understand this
correctly what you really have is:
[Bar]
A
|
+------------+-----+------+------...-----+
| | | |
[barDerive1] [barDerive2] [barDerive3] [barDeriveN]
| 1 | 1 |1 |1
| uses | uses | uses | uses
| | | |
| | | |
| 1 | 1 | 1 | 1
[fooDerive1] [fooDerive2] [fooDerive3] [fooDeriveN]
| | | |
+-------------+----+-------+-----...-------+
|
_
V
[Foo]
One wants to access Bar at the superclass level to avoid static typing
clutter in each Foo subclass implementation, but the rules of navigation
reflected in the relationships have to be enforced. From a design
viewpoint, one does that by instantiating the relationship correctly.
Usually that would be done by whoever instantiates a Foo subclass
instance. You are doing that, but you are doing it by embedding an
instance of a Bar subclass and maintaining a separate Bar reference to
it for navigation in DoStuffWithBarBase. I see that as a tactical OOP
solution.
I think a simpler solution would be to create the Bar instance
separately and initialize a pointer to it in Foo:
class Foo
{
private:
Bar* myBar;
...
public:
void DoStuffWithBarBase ()
...
}
fooDeriveN::DoStuffWithBarBase()
{
...
this.myBar.doIt();
...
}
Creator::createFooDeriveN(...)
{
Bar* myBar = new (barDeriveN(...));
fooDerive* myFoo = new (fooDeriveN(myBar,...));
...
}
DoStuffWithBarBase now navigates to the Bar with confidence that it will
do the right thing simply because it is at the end of the relationship.
The rules for instantiating the relationship are encapsulated in
createFooDeriveN or whoever creates Foos. Note that to invoke your Foo
constructor, whoever does that has to know exactly the same things to
provide the initializer list.
This may seem like a trivial difference in encoding the constructor
code, but it actually represents a more robust approach. That's because
Foo doesn't need to know anything about the relationship rules or even
what sort of Bar is on the other end of the relationship. One way that
is manifested is that the Foo code only needs the Bar reference. [It
also reduces the complexity of the constructor so that you safety
problem goes away. B-)]
A more important benefit is that if things change so that Bar is created
separately for some reasons (e.g., because other objects need to access
it or one decides one can optimize size by eliminating redundant
embedded Bar instances or whatever) it is highly unlikely that the Foo
constructor will have to change nor any other internals of Foo.
If you look at the way full UML code generators work, they invariably
implement relationship navigation with pointers and collections of
pointers because that allows very generic, aspect-like implementation
that does not depend upon the class semantics at all. (They even use
naming conventions that reflect relationship discriminators.) Among
other things that allows the code generator to generate code for one
class without even "looking" at the head files for other classes
involved in the navigation. That abstraction and separation of concerns
is not possible in your reference approach because Foo needs to know the
specific Bar subclass type even though it actually accesses through the
Bar superclass.
*************
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
- Next message: H. S. Lahman: "Re: File Saving Strategies"
- Previous message: Kurt: "Re: So let's build a router"
- In reply to: Simon Elliott: "Re: C++ design question"
- Next in thread: Kurt: "Re: C++ design question"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
Relevant Pages
|