Design Problem Aggregation

M

Merlin

Hi

Imagine the following classes (A class diagram will help)

BASE, A, B, C, D, E, F, G.


A, B, C, D, G inherit from BASE.

E, F inherit from D.

Class E has a member (aggregation by value) of type A.
Class F has a member (aggregation by value) of type B.


Class G has a member that is container that can accept objects of type
D. As D is the base class of E and F we can add to the container
objects of type E or F. I have made the container type safe in this
way.

I wanted G to be a collection of objects of type A or B but never C so
I introduced an abstract class D and made the container of that type
so it would only accept objects of base type D. However, although this
looks ok, I am not happy with the extra work it has created.
As I need to access the interface to A and B, I need to repeat all
that interface in E and F. A and B have many member functions and I
dont want to rewrite all that interface in E and F and delegate the
calls to the aggregate.

The solution where I provide a member function in classes E and F to
return the aggregate seems wrong as its exposing a private data
member.

How can I change my design to make it better and flexible?

I have intentionally used meaningless letters in order to focus on the
relationships of the classes as oppose to what they model.
I hope this wont confuse anyone.


Many Thanks in advance
 
J

John Carson

Merlin said:
Hi

Imagine the following classes (A class diagram will help)

BASE, A, B, C, D, E, F, G.


A, B, C, D, G inherit from BASE.

E, F inherit from D.

Class E has a member (aggregation by value) of type A.
Class F has a member (aggregation by value) of type B.


Class G has a member that is container that can accept objects of type
D. As D is the base class of E and F we can add to the container
objects of type E or F. I have made the container type safe in this
way.

I wanted G to be a collection of objects of type A or B but never C so
I introduced an abstract class D and made the container of that type
so it would only accept objects of base type D. However, although this
looks ok, I am not happy with the extra work it has created.
As I need to access the interface to A and B, I need to repeat all
that interface in E and F. A and B have many member functions and I
dont want to rewrite all that interface in E and F and delegate the
calls to the aggregate.

The solution where I provide a member function in classes E and F to
return the aggregate seems wrong as its exposing a private data
member.

I would be happy to do something even simpler: make the A and B objects
contained in E and F (respectively) public members; this doesn't involve any
more exposure than if you had just declared

A a;
B b;

How can I change my design to make it better and flexible?

How about ditching E and F and making A and B inherit from D (which in turn
inherits from Base)?
 
M

Merlin

Hi John

Thanks for your suggestion. I like your first suggestion but making
the members public makes me a bit uneasy. Your second suggestion works
well if no class sits between BASE and A or BASE and B.

In order to make things clear, discard the original classes and
consider the following scenario.

Imagine we have the following classes

BASE, A, B, C, D, E, F, G, H, I, and J (class diagram will help)

Classes A, D, F, G, J inherit from BASE.

Class B inherits from A

Class C inherits from B

Class E inherits from D

Classes H and I inherit from G

Class H has a member (aggregation by value) of type C.
Class I has a member (aggregation by value) of type E.


Class J has a member that is container that can accept objects of type
G. As G is the base class of H and I we can add to the container
objects of type H or I. I have made the container type safe in this
way.


I wanted J to be a collection of objects of type C or E but never F or
even BASE so I introduced an abstract class G and made the container
of that type so it would only accept objects of base type G. However,
although this looks ok, I am not happy with the extra work it has
created.

As I need to access the interface to C and E, I need to repeat all
that interface in H and I. C and E have many member functions and I
dont want to rewrite all that interface in H and I and delegate the
calls to the aggregate.

How can I change my design to make it better and flexible?

If I do what you suggested I will have multiple inheritance and cyclic
dependencies. What else can I do to get a type safe container? Should
I put member functions in G to return the objects C and E? Doesnt that
break encapsulation?

Many Thanks

Merlin
 
J

John Carson

Merlin said:
Hi John

Thanks for your suggestion. I like your first suggestion but making
the members public makes me a bit uneasy. Your second suggestion works
well if no class sits between BASE and A or BASE and B.

In order to make things clear, discard the original classes and
consider the following scenario.

Imagine we have the following classes

BASE, A, B, C, D, E, F, G, H, I, and J (class diagram will help)

Classes A, D, F, G, J inherit from BASE.

Class B inherits from A

Class C inherits from B

Class E inherits from D

Classes H and I inherit from G

Class H has a member (aggregation by value) of type C.
Class I has a member (aggregation by value) of type E.


Class J has a member that is container that can accept objects of type
G. As G is the base class of H and I we can add to the container
objects of type H or I. I have made the container type safe in this
way.

Please note that a container that can store objects of type G CANNOT store
objects of type H or I. If you attempt to do this, then you get "slicing" in
which H and I objects have everything sliced off them except for their G
base component. The compiler will let you do this, but it isn't what you
want (as you will discover when you attempt to use the objects you have
stored).

What is true is that a container that can store POINTERS to objects of type
G can store POINTERS to objects of type H or I.
I wanted J to be a collection of objects of type C or E but never F or
even BASE so I introduced an abstract class G and made the container
of that type so it would only accept objects of base type G. However,
although this looks ok, I am not happy with the extra work it has
created.

As I need to access the interface to C and E, I need to repeat all
that interface in H and I. C and E have many member functions and I
dont want to rewrite all that interface in H and I and delegate the
calls to the aggregate.

How can I change my design to make it better and flexible?

If I do what you suggested I will have multiple inheritance and cyclic
dependencies. What else can I do to get a type safe container? Should
I put member functions in G to return the objects C and E? Doesnt that
break encapsulation?

As I have already indicated, if the sole purpose of G, H and I is to
effectively give C and E a common base class, then there is no meaningful
encapsulation to protect in G, H and I. C and E should incorporate their own
encapsulation and that is what you should be relying on.

However, on further reflection, this strategy has problems of its own. The
whole point of using a container with more than one type is presumably to
implement polymorphism. So how are you going to return the member of H and
I? You presumably want a virtual function in G, GetObject, that is
overridden in H and I. The return type of this GetObject virtual function in
G must be a reference or pointer to a common base of C and E, which means in
this case that it must be a reference or pointer to BASE (note that H and I
are just containers so they have no inheritance relationship with C and E
and hence neither does G).

Here is the problem: the overrides of GetObject in H and I can have a return
type of a reference/pointer to C and E respectively, but it remains the case
that if GetObject is called using a pointer of type pointer to G (rather
than a pointer of type pointer to H or I), then the return type of GetObject
is determined by the function's declaration in class G. Thus the return type
is a pointer/reference to BASE. This means that you can only use the return
value of GetObject to call functions that are declared in BASE. Thus the
entire interface to C and E needs to be declared in BASE.

It is difficult to know what design would be ideal without knowing what each
class does. Possibilities:

1. Scrap G, H and I and introduce a base class Z incorporating the required
interface for C and E. Classes C and E would then inherit from Z and one
other class (B in the case of C and D in the case of E). This will only work
if you make C and E override all pure virtual functions in Z --- they can't
just inherit the functions from BASE.

2. As above, but make Z inherit from BASE, so that Z has BASE's interface
without the need for any typing. C and E would then potentially have two
BASE components --- one via Z and one via B or D. This may or may not be a
problem. If it is, you can avoid it by using virtual inheritance (this,
however, has performance penalties).

3. If your type safety requirements are accurately stated as "type C or E
but never F or even BASE", then you could achieve that by having X inherit
from BASE and having A and D inherit from X. Your container could then store
X pointers. This, of course, would not rule out A or D or B.

4. Perhaps the entire class heirarchy could do with a re-think.
 
D

David Rubin

Merlin said:
Hi

Imagine the following classes (A class diagram will help)

BASE, A, B, C, D, E, F, G.


A, B, C, D, G inherit from BASE.

E, F inherit from D.

Class E has a member (aggregation by value) of type A.
Class F has a member (aggregation by value) of type B.


Class G has a member that is container that can accept objects of type
D. As D is the base class of E and F we can add to the container
objects of type E or F. I have made the container type safe in this
way.

I wanted G to be a collection of objects of type A or B but never C

You have:

class Base {};
class A : public Base {};
class B : public Base {};
class C : public Base {};

class G : public Base
{
Container<Base *> c; // holds A* or B* but never C*
public:
//...
};

This is a rather awkward design, but you have a few choices:

1. G has member functions

bool add(A* a);
bool add(B* b);

This has the benefit of allowing A and B subtypes as well, although you
will only ever be able to call virtual functions of Base implemented in
A and B from elements of c.

2. G has member function

bool add(Base *e);

which checks whether you can dynamic_cast e to A* or B*. This has the
same benefit of (1) but incurrs a run-time check rather than a
compile-time check.

3. Same as (1), but stores A* and B* elements in different containers.
This allows you to call non-polymorphic functions in A or B depending on
the container. (This can also be done with the dynamic_cast interface of
[2]).

The problem inherent in this design is that it does not scale: you need
a new add() function, or a new dynamic_cast check for every Base derived
type you want (or don't want) to store in G. This is true even if you
introduce intermediate classes E and F.

The basic problem you are trying to address is how to store objects with
a common base, and yet access functions which are not defined in the
base. Options 1 and 2 are the natural way to do this in C++, but you
might think hard about whether you can change your design to separate
the code which depends on calling such functions from code which
maintains the collection of such objects.

On top of this, by creating a single container of pointers to base-type
objects, you are creating an additional problem which is that you need a
way to restrict elements of the container to only certain derived types
of the base type.

/david
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top