Salut,

Envoyé par
RPGamer
A mon sens ça n'a pas lieu d'être. L'héritage multiple est utile pour l'implémentation d'interfaces et les interfaces étant dépourvues de données membres, elles sont dépourvues d'invariants.
Ca, c'est une vision très... java de ce que sont les interfaces! Elle est imposée en java pour éviter les problèmes liés à l'héritage en losange (ou en diamant, comme tu veux) pour l'unique raison que toute classe hérite forcément -- de manière directe ou indirecte -- d'une classe Object.
Mais, si tu y porte un tout petit peu d'attention, tu te rendras compte que la notion d'interface en java (et le mot clé
implements qui y est rattaché) réagit exactement de la même manière que la notion de classe ou de structure dans un contexte d'héritage, en respectant scrupuleusement le LSP.
Dés lors, si l'on remet les pendules à l'heure pour ce qui concerne la notion d'interface, et que l'on considère que c'est "un ensemble regroupant un certain nombre de comportement destinés à travailler correctement de concert dans un but clairement déterminé" (et tu avoueras que cette définition correspond sérieusement à la notion d'interface

) on se rend compte que c'est exactement la même chose qu'une classe, surtout en C++, vu qu'il ne dispose pas de la possibilité de faire la distinction.
Dés lors, prenons l'exemple une interface IPositionnable. En java, tu devrais avoir quelque chose ressemblant à
1 2 3 4 5 6 7 8 9
| public interface IPositionnable{
public void moveTo(Position newPos);
public void move(Type diffX, Type diffY /*, Type diffZ*/);
};
public class MaClasse implements IPositionnable{
/* il faut définir les comportements hérités de IPositionnable dans toutes les classes
* implémentant cette interface
*/
}; |
Mais, il n'y a rien qui nous empêche en C++ d'avoir quelque chose ressemblant à
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| class IPositionnable{
public:
void moveTo(Position const & newPos){
pos_=newPos;
}
void move(Type diffX, Type diffY /*, Type diffZ*/){
pos_ = Position(pos_.x()+diffX, pos_.y()+diffY/*,pos.z()+diffZ*/);
}
Type x() const{
return pos_.x();
}
Type y() const{
return pos_.y();
}
/*
Type z() const{
return pos_.z()
}
*/
private:
Position pos_;
};
class MaClass : public IPositionnable{
/* on n'a même plus besoin de faire quoi que ce soit pour supporter l'interface IPositionnable
*/
}; |
voire, pourquoi pas, mieux encore :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| template <typename CRTP, typename V, typename F>
class IPositionnable{
public:
using value_t = V;
using flag_t = F;
using pos_t = Position<value_t, flag_t>;
void moveTo(pos_t const & newPos){
pos_ = newPos;
}
template <typename U = F,
typename = typename std::enable_if<std::is_same<U, Flag2D>::value>::type>
void move(value_t diffX, value_t diffY){
pos_=pos_t{pos_.x()+diffX, pos_.y()+diffY};
}
template <typename U = F,
typename = typename std::enable_if<std::is_same<U, Flag3D>::value>::type>
void move(value_t diffX, value_t diffY, value_t diffZ){
pos_=pos_t{pos_.x()+diffX, pos_.y()+diffY, pos_.z()+diffZ};
}
value_t x() const{
return pos_.x();
}
value_t y() const{
return pos_.y();
}
template <typename U = F,
typename = typename std::enable_if<std::is_same<U, Flag3D>::value>::type>
value_t z() const{
return pos_.z()
}
private:
pos_t pos_;
};
class MaClasse : public IPositionnable<MaClasse, double, Flag3D>{
/* il n'y a plus rien à faire en ce qui concerne l'interface IPositionnable */
}; |
(la version template de IPositionnable est faite à main levée, il y a peut être quelques corrections à apporter

)
la classe IPositionnable correspond bel et bien à la notion d'interface (c'est une classe qui regroupe un ensemble de comportements "destinés à travailler ensemble"

, et pourtant, elle dispose de "tout ce qu'il faut" pour pouvoir définir les comportements en question.
Mieux encore, la version template de cette classe permet, grâce au CRTP, d'éviter que l'interface serve de "glue artificielle" pour regrouper deux hiérarchies de classes entre elles sous prétexte "qu'elles sont toutes les deux positionnables".
En deux mots comme en cent, la restriction imposée par java en ce qui concerne la notion d'interface est spécifique à java et n'est due qu'au fait que ce langage a effectivement forcé la distinction "physique" entre les notions de classes et d'interfaces, alors que conceptuellement parlant, il n'y a absolument rien que pourrait faire une classe qu'une interface ne pourrait faire (à moins que ce soit le contraire

)
Mais, du coup, on se rend donc aussi compte que les invariants destinés à garantir le bon fonctionnement de l'interface se trouvent... au niveau de l'interface, et non de la classe qui l'implémente
3 |
0 |