58Fermer60
Kevin KoflerLe 14/01/2008 à 20:37
JackosKing (./57) :
Si tu regardes le contrat de setWidth: height constant.

Donc il faut interdire d'appeler setWidth sur un carré, par exemple:
class Rectangle {
  public:
    virtual void setWidth(int);
}

class SquareVKiller : public Rectangle{
  public:
    virtual void setWidth(int) {throw "Invalid on a square";}
}

class Square : public SquareVKiller {
  private:
    void setWidth(int); // to detect the error at compile time where possible
}


Comment permettre de transformer un carré en un rectangle en ne changeant que la largeur? Ben, avec le système copy-on-write du ./56. Ici, la bonne solution pour avoir le polymorphisme est d'utiliser le copy constructor Rectangle::Rectangle(const Square &) qui utilise le polymorphisme de SquarePrivate en interne, mais si on écrit en utilisant les interfaces de la classe Rectangle, l'objet est détaché (copié) en tant que RectanglePrivate, pas SquarePrivate et les contrats de Rectangle sont remplis. (Et oui, on peut appeler Rectangle::setWidth parce que c'est une copie qui est effectuée, donc l'objet n'est plus de type Square ou SquareVKiller même si l'objet interne est toujours de type SquarePrivate avant le détachement.) Utiliser un Rectangle * est une mauvaise idée et en faisant ça, on risque de se taper l'exception si on a le malheur de tomber sur un carré, mais la solution est de ne pas utiliser un pointeur sur cette classe implicitly shared.

Le COW fonctionne ici parce qu'en lecture, un carré peut remplir toutes les fonctionnalités d'un rectangle, ce n'est qu'à l'écriture qu'on a une invariante supplémentaire.

Sinon, une solution encore plus simple est d'avoir des rectangles et carrés immutables, dans ce cas le problème de setWidth ne se pose même pas. Mais cette solution n'est pas toujours appliquable.