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.