En C#, pas d'héritage multiple.
L'héritage est une chaîne en droite ligne.
Parfois cependant il pourrait être utile que des classes séparées dans l'arbre d'héritage puissent partager un même bagage.
C'est un peu le rôle des interfaces, qui définissent un bagage commun de façon "transversale", indépendament de la notion d'héritage.
Un exemple dans RunUO : les classes Mobile et Item définissent toutes deux une variable Location de type Point3D (donc un ensemble de trois int formant les coordonnées x,y et z)...
Lorsque par exemple après une Target, on reçoit une instancde
object, il nous faut faire les tests
if(obj is Mobile) else if (obj is Item) avant de pouvoir s'adresser à mob.Location ou a item.Location...
Ici c'est simple, on n'a que deux classes différentes, mais on pourrait en avoir 36...
Une autre façon de faire est de caster l'object reçu en un objet de type
IPoint3D, c'est à dire un objet défini en tant qu'interface.
C'est possible dans RunUO car Mobile et Item déclarent tous deux l'interface IPoint3D...
- Code:
-
public class Mobile : IEntity, IPoint3D, IHued, IComparable, IComparable<Mobile>, ISerializable
- Code:
-
public class Item : IPoint3D, IEntity, IHued, IComparable, IComparable<Item>, IComparable<IEntity>, ISerializable
A partir de là, il est possible de faire :
- Code:
-
if(obj is IPoint3D)
{
IPoint3D point = (IPoint3D)obj;
}
On peut donc manipuler cet objet, indépendament de sa nature, Item ou Mobile, en le définissant par rapport à ce que ces deux classes, d'héritage différent, ont en commun grâce à cette interface.
Ensuite, dans les fichiers source, voici comment est définie l'interface IPoint3D (et donc IPoint2D) :
- Code:
-
public interface IPoint2D
{
int X{ get; }
int Y{ get; }
}
public interface IPoint3D : IPoint2D
{
int Z{ get; }
}
A partir de là,on sait que l'objet
point définit X,Y et Z que l'on peut utiliser pour définir un new Point3D.
L'interface est un objet, mais n'est pas défini avec le mot clef
class, mais
interface.
Ce qui est déclaré dans le corps de l'interface est tout ce qui doit être commun aux différentes classes qui déclareront l'interface dans leur héritage.
Une classe qui déclarerait IPoint3D, sans déclarer de propriété X, Y et Z ne compilerait pas.
La déclaration d'une interface est l'assurance que la classe obéit bien aux spécifications déclarées dans l'interface, et peut donc par conséquent être castées dans le type de l'interface.Un autre exemple dans RunUO, l'interface ICraftable.
Les items craftables sont tout simplement ceux qui sont scriptés dans les classes def du craftsystem (DefAlchemy, DefBlacksmithy, etc...). Pas besoin de l'interface.
L'interface sert uniquement à appeler l'objet à sa création pour un comportement particulier.
Voici la déclaration de l'interface :
- Code:
-
public interface ICraftable
{
int OnCraft( int quality, bool makersMark, Mobile from, CraftSystem craftSystem, Type typeRes, BaseTool tool, CraftItem craftItem, int resHue );
}
Donc, toutes les classes déclarant ICraftable
doivent déclarer une méthode OnCraft de type int qui attend ces arguments.
A partir de là, dans CraftItem, lorsque l'item est créé on a :
- Code:
-
if( item is ICraftable )
endquality = ((ICraftable)item).OnCraft( quality, makersMark, from, craftSystem, typeRes, tool, this, resHue );
La méthode OnCraft de l'item est donc appelée, ici pour calculer un int pour la variable Quality, alors qu'on ne sait pas de quel type d'item il s'agit.
On aurait pu s'en passer en déclarant OnCraft par défault dans Item, mais on n'aura des OnCraft que dans les items qui le nécessitent, sans être obligé dans le code appelant de déclarer des dizaines de if(item is truc) if(item is machin) etc...
Et donc dans le OnCraft de chaque objet, on met le comportement qu'on veut...