Design Problem Aggregation

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Merlin

    Design Problem Aggregation

    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
  • John Carson

    #2
    Re: Design Problem Aggregation

    "Merlin" <merlin2769@hot mail.com> wrote in message
    news:12235816.0 405091453.5083b b2e@posting.goo gle.com[color=blue]
    > 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.[/color]

    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;

    [color=blue]
    > How can I change my design to make it better and flexible?[/color]

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


    --
    John Carson
    1. To reply to email address, remove donald
    2. Don't reply to email address (post here instead)

    Comment

    • Merlin

      #3
      Re: Design Problem Aggregation

      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

      Comment

      • John Carson

        #4
        Re: Design Problem Aggregation

        "Merlin" <merlin2769@hot mail.com> wrote in message
        news:12235816.0 405101041.3f8b8 e79@posting.goo gle.com[color=blue]
        > 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.[/color]

        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.
        [color=blue]
        > 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?[/color]

        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.


        --
        John Carson
        1. To reply to email address, remove donald
        2. Don't reply to email address (post here instead)

        Comment

        • David Rubin

          #5
          Re: Design Problem Aggregation

          Merlin wrote:[color=blue]
          > 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[/color]

          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

          --
          "As a scientist, Throckmorton knew that if he were ever to break wind in
          the echo chamber, he would never hear the end of it."

          Comment

          Working...