开发者

To polymorph or to incapsulate, this is the question! (C++)

开发者 https://www.devze.com 2023-03-21 10:14 出处:网络
I need to store a polymorphic object (let\'s say Polygon) inside another object (let\'s say Simulation). At the same time I want to keep encapsulation of Simulation.

I need to store a polymorphic object (let's say Polygon) inside another object (let's say Simulation). At the same time I want to keep encapsulation of Simulation.

class Polygon {
public:
  开发者_开发问答virtual double area() { return 0; }
};

class Square : public Polygon {
public:
  Square(double edge) : edge_(edge) {}
  virtual double area() { return edge_*edge_; }
private:
  double edge_;
};

class Simulation {
public:
  Simulation(Polygon& polygon) { polygon_ = &polygon; }
  Polygon* polygon() { return polygon_; }
private:
  Polygon* polygon_;
};

int main (int argc, const char * argv[]) {
  Square square(2.0);
  Simulation sim(square);
  std::cout<<sim.polygon()->area()<<"\n";
  return 0;
}

This works perfectly fine! However, it violates encapsulation of Simulation, in fact, if from the main I go and change square it will also change inside Simulation.

I was thinking of modifying the constructor of Simulation using the copy constructor as:

Simulation(Polygon& polygon) { polygon_ = new Polygon(polygon); }

but this will mean that I don't have polymorphism...

There is obviously something I am missing here... CHEERS!


Add a clone function to Polygon (and a virtual destructor!). It is a good idea to ensure that Polygon is abstract so make sure at least one function is pure virtual.

Your Simulation class will require a copy constructor, destructor and assignment operator. Note that the Square clone function can return a Square* even though the super class returns a Polygon* because it is covariant. Some older compilers may not support this, in which case return a Polygon*.

class Polygon {
public:
  virtual ~Polygon() = 0;
  virtual Polygon* clone() const = 0;
  virtual double area() { return 0; }
};
inline Polygon::~Polygon() {}

class Square : public Polygon {
public:
  Square(double edge) : edge_(edge) {}
  virtual Square* clone() const { return new Square(*this); }
  virtual double area() { return edge_*edge_; }
private:
  double edge_;
};

class Simulation {
public:
  Simulation(Polygon const& polygon) 
  : polygon_(polygon.clone())
  {}
  Simulation(Simulation const& rhs) 
  : polygon_(rhs.polygon_->clone())
  {}
  Simulation& operator=(Simulation const& rhs) 
  {
      if (this != &rhs) {
          delete polygon_;
          polygon_ = rhs.polygon_->clone();
      }
      return *this;
  }
  ~Simulation() {
     delete polygon_;
  }
  Polygon* polygon() { return polygon_; }
private:
  Polygon* polygon_;
};


If Simulation contains Polygon then it means that it is meant to do something with it. If you need to access the polygon directly from the 'outside', you have either missed the design somewhere, or if not, you can use observer pattern and have polygon notify the simulation if something about it changes.

So, either:

outside -> polygon -> callback -> simulation 

or

outside -> simulation -> polygon


So you want to make sure that there's no way for outside code to alter the inner polygon of simulation, but yet allow any subclass to be used inside it? I.e. make sure that there are no references outside of simulation to the object passed by ref in the c'tor?

You could think of an abstract copy method to accomplish that: (don't forget to delete in simulation destructor)

class Polygon {
public:
   virtual Polygon *copy() = 0;
   //..
};
class Square : public Polygon {
public:
   virtual Polygon *copy() { return new Square(_edge); }
   //...
}
class Simulation {
public:
   Simulation(const Polygon &p) : poly(p.copy()) {}
};


If you want to copy a polymorphic object, this can be done with a clone method.

class Polygon
{
  ...
  virtual Polygon* clone() const = 0;
};

class Square: public Polygon
{
  ...
  virtual Square* clone() const { return new Square(*this); }
};

However, in the example it seems a bit pointless that the Simulation neither does anything with the polygon itself nor do you want to hand it out for other code to use.


That's just how C++ works. If you write a wrapper for an object (PIMPL) you have to implement its full interface. The functions going to be very small just passing the calls to the actual implementation but you have to write them. Then you can alter the behaviour, add logging, or whatever you need...


You just need to decide if the polygon is inside or outside of simulation. If it's supposed to be outside of it, then you have reference constructor parameter . If it's inside, you'll need the following code:

class Simulation {
public:
    Simulation() : poly(2.0) { }
    Polygon *polygon() { return &poly; }
private:
    Square poly;
};

Now, the polymorphism aspect you can easily do like this:

class Simulation {
public:
    Simulation() : poly(2.0), poly2(3.0) { }
    Polygon *polygon(int i) 
   { 
       switch(i) { 
          case 0: return &poly;  
          case 1: return &poly2; 
       } 
       return 0; 
   }
private:
    Square poly;
    Cylinder poly2;
};

And once you get tired to adding new data members, here's another trick which will fix some cases:

class Simulation {
public:
    Simulation() : poly(2.0) { }
    Polygon *polygon(float x) 
    {
        poly.edge_ = x;
        return &poly;
    }
private:
    Square poly;
 };

Edit: Note that the order of classes in header file needs to be carefully considered.

0

精彩评论

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

关注公众号