开发者

object orientation, inheritance, and composition in c++

开发者 https://www.devze.com 2023-03-29 09:23 出处:网络
There are 4 classes: A, B, C, and D.Class A \"HAS A\" B, and class C \"HAS A\" D.As it happens, C \"IS A\" A, and D \"IS A\" B.What\'s the best way to model these relationship开发者_运维技巧s in C++?

There are 4 classes: A, B, C, and D. Class A "HAS A" B, and class C "HAS A" D. As it happens, C "IS A" A, and D "IS A" B. What's the best way to model these relationship开发者_运维技巧s in C++?

The B that A has is the same D that C has.

Example: A building has an entrance, and a house has a door. A house is a building, and a door is an entrance.


class B {
};
class A {
  B b;  // A "has a" B
};
class C : public A  { // C "is a" A
  D d;  // C "has a" D
};
class D : public B { // D "is a" B
};


I think there is a flaw in your design. If I understand correctly, you want to restrict the type of a base class member variable:

struct Entrance {};
struct Door : public Entrance {};

struct Building
{
    Entrance * entrance; // raw pointer just for the sake of clarity
};

struct House : public Building
{
    // Building::entrance must always be a door
};

You could achieve that using accessors (getter/setter) to entrance and checking the type, throwing an exception if the entrance is not a door, but that would break the Liskov substitution principle: you would not be able to manipulate Houses as if they were Buildings:

struct Drawbridge : public Entrance {};

House house;
Drawbridge bridge;

Building & building = house;
building.setEntrance(bridge); 
// Oups, I'm trying to install a drawbridge on my house!

Some libraries perform this kind of restrictions (for example, ReadOnlyCollection<T> throws an exception when one try to modify its content), but in my opinion, this is not a clean design. If collection's interface states that I can add elements to the collection, then a read only collection is not a collection (since it does not support adding elements).

The same reasoning can be applied here: a House is not a Building since it cannot contain all kind of Entrances.


A building has an entrance, and a house has a door. A house is a building, and a door is an entrance.

struct Entrance        {};
struct Door : Entrance {};

struct Building {
   virtual Entrance& getEntrance() = 0;
};

struct House : Building {
   virtual Entrance& getEntrance() {
      return entrance;
   }

   private:
   Door entrance;
};

typedef Building A;
typedef Entrance B;
typedef House    C;
typedef Door     D;

This is the nearest approximation, using polymorphism. Note in particular that Building cannot be instantiated because it is "abstract".

Otherwise, you cannot have A has-a B and C has-a D, and have both the B and the D be the same object.


If you get tempted to attempt to have the base hold a reference, avoid it:

struct Entrance        {};
struct Door : Entrance {};

struct Building {
   Building(Entrance& e) : e(e) {}
   Entrance& e;
};

struct House : Building {
   House() : Building(e) {}
   Door e;
};

typedef Building A;
typedef Entrance B;
typedef House    C;
typedef Door     D;

Since House's Building constructor is invoked before its Door is created, this is questionable at best (and undefined at worst.. I'd have to look it up).


Luc is right, though, that this modelling is flawed.


C extends A and D extends B. A holds a B object in its definition and C holds a D object. [Has-a] [Is-a]

0

精彩评论

暂无评论...
验证码 换一张
取 消