2010-03-19 4 views
4

Hier 2 des gars de notre équipe sont venus me voir avec un problème rare. Nous utilisons un composant tiers dans l'une de nos applications Winforms. Tout le code a déjà été écrit contre. Ils ont ensuite voulu incorporer un autre composant tiers, par le même fournisseur, dans notre application. À leur grande joie, ils ont constaté que le deuxième élément avait exactement les mêmes membres du public que le premier. Mais à leur grande consternation, les 2 composants ont des hiérarchies d'héritage complètement distinctes, et n'implémentent pas d'interfaces communes. Ça vous fait réfléchir ... Eh bien, je me demande.Comment rendre 2 types incompatibles, mais avec les mêmes membres, interchangeables?

Un exemple du problème:

Incompatible Types http://www.freeimagehosting.net/uploads/f9f6b862f1.png

public class ThirdPartyClass1 
{ 
    public string Name 
    { 
     get 
     { 
      return "ThirdPartyClass1"; 
     } 
    } 

    public void DoThirdPartyStuff() 
    { 
     Console.WriteLine ("ThirdPartyClass1 is doing its thing."); 
    } 
} 

public class ThirdPartyClass2 
{ 
    public string Name 
    { 
     get 
     { 
      return "ThirdPartyClass2"; 
     } 
    } 

    public void DoThirdPartyStuff() 
    { 
     Console.WriteLine ("ThirdPartyClass2 is doing its thing."); 
    } 
} 

Volontiers ils se sentaient copier et coller le code qu'ils ont écrit pour le premier composant n'était pas la bonne réponse. Ils ont donc pensé à affecter le composant instantané à une référence d'objet, puis à modifier le code pour effectuer des conversions conditionnelles après avoir vérifié de quel type il s'agissait. Mais c'est sans doute encore plus laide que l'approche copier-coller.

Alors ils m'ont demandé si je pouvais écrire du code de réflexion pour accéder aux propriétés et appeler les méthodes des deux types d'objets différents puisque nous savons ce qu'ils sont, et ils sont exactement les mêmes. Mais ma première pensée a été que ça va l'élégance. Je pense qu'il doit y avoir une meilleure solution gracieuse à ce problème.

+0

Sons comme vous voulez pour utiliser le modèle d'adaptateur: http://en.wikipedia.org/wiki/Adapter_pattern –

Répondre

12

Ma première question était de savoir si les 2 classes de composants tiers étaient scellées? Ils n'étaient pas. Au moins, nous avons cela.

Ainsi, car ils ne sont pas scellées, le problème est résoluble de la façon suivante:

Extrait d'une interface commune sur les membres qui coïncident des 2 classes de tiers. Je l'ai appelé Icommon.

public interface ICommon 
{ 
    string Name 
    { 
     get; 
    } 

    void DoThirdPartyStuff(); 
} 

Ensuite, créez 2 nouvelles classes; DerivedClass1 et DerivedClass2 qui héritent respectivement de ThirdPartyClass1 et ThirdPartyClass2. Ces deux nouvelles classes implémentent l'interface ICommon, mais sont complètement vides.

public class DerivedClass1 
    : ThirdPartyClass1, ICommon 
{ 
} 

public class DerivedClass2 
    : ThirdPartyClass2, ICommon 
{ 
} 

Maintenant, même si les classes dérivées sont vides, l'interface est satisfaite par les classes de base, qui est l'endroit où nous avons extrait l'interface de la première place. Le diagramme de classes résultant ressemble à ceci.

alt text http://www.freeimagehosting.net/uploads/988cadf318.png

Alors maintenant, au lieu de ce que nous avions auparavant:

ThirdPartyClass1 c1 = new ThirdPartyClass1(); 
c1. DoThirdPartyStuff(); 

Nous pouvons maintenant faire:

ICommon common = new DerivedClass1(); 
common. DoThirdPartyStuff(); 

Et la même chose peut être fait avec DerivedClass2. Le résultat est que tout notre code existant qui a référencé une instance de ThirdPartyClass1 peut être laissé tel quel, en remplaçant simplement la référence ThirdPartyClass1 pour une référence ICommon. La référence ICommon peut alors recevoir une instance de DerivedClass1 ou DerivedClass2, qui hérite à son tour de ThirdPartyClass1 et ThirdPartyClass2 respectivement. Et tout fonctionne juste.

Je ne sais pas s'il existe un nom spécifique pour cela, mais pour moi, il ressemble à une variante du modèle d'adaptateur.

Peut-être que nous aurions pu résoudre le problème avec les types dynamiques en C# 4.0, mais cela n'aurait pas eu l'avantage de vérifier à la compilation.

Je serais très intéressé de savoir si quelqu'un d'autre a une autre manière élégante de résoudre ce problème.

+0

Plus ou moins comme ma réponse, mais je préférerais la composition à l'héritage. –

+0

Même s'ils étaient scellés, vous pouviez créer un wrapper héritant de l'interface et juste déléguer à la classe enveloppée, mais l'encapsuleur ne pouvait pas être traité comme cette classe ... – saret

+0

@orsogufo: Oui, mais alors vous devriez promouvoir chaque méthode et propriété à travers l'emballage si vous alliez avec la composition. Et dans ce cas, c'est beaucoup de code inutile car les classes de base satisfont déjà l'interface. Maintenant, s'ils étaient scellés, je serais d'accord avec vous. :) –

1

Ajouter une interface. Vous pouvez ajouter un wrapper (qui implémente l'interface) pour chacune des tierces parties. Quoi qu'il en soit, si vous avez le code de ces tierces parties, vous pouvez passer le wrapper et implémenter directement l'interface. Je suis tout à fait sûr que vous n'avez pas la source, cependant.

+0

Non. Aucune source. C'était tout le problème. :) –

3

Qu'en est-il de certains emballages?

public class ThirdPartyClass1 { 
    public string Name { 
     get { 
      return "ThirdPartyClass1"; 
     } 
    } 

    public void DoThirdPartyStuff() { 
     Console.WriteLine("ThirdPartyClass1 is doing its thing."); 
    } 
} 

public interface IThirdPartyClassWrapper { 
    public string Name { get; } 
    public void DoThirdPartyStuff(); 
} 

public class ThirdPartyClassWrapper1 : IThirdPartyClassWrapper { 

    ThirdPartyClass1 _thirdParty; 

    public string Name { 
     get { return _thirdParty.Name; } 
    } 

    public void DoThirdPartyStuff() { 
     _thirdParty.DoThirdPartyStuff(); 
    } 
} 

... et même pour ThirdPartyClass2, vous utilisez l'interface wrapper dans toutes vos méthodes.

+0

Merde les gars, répondez vite! :) Ma question a été répondue avant même que j'ai réussi à poster ma propre réponse pré-écrite. J'aime ça! Je ne suis pas si bon! –

+0

@Quigrim: c'était donc une question réthorique? :) –

+0

Oui c'est. :) Basé sur la faq stackoverflow qui dit: "Il est aussi parfaitement bien de poser et de répondre à votre propre question, mais prétendez que vous êtes sur Jeopardy: exprimez-le sous la forme d'une question." J'ai pensé que cela pourrait être utile aux autres. –

4

Si vous utilisez .Net 4, vous pouvez éviter d'avoir à faire beaucoup de cela car le type dynamique peut aider avec ce que vous voulez. Cependant, si vous utilisez .Net 2+, il existe un autre moyen (différent) de réaliser ceci:

Vous pouvez utiliser une bibliothèque de typage de canard comme celle de Deft Flux pour traiter vos classes tierces comme si elles implémentaient une interface.

Par exemple:

public interface ICommonInterface 
{ 
    string Name { get; } 
    void DoThirdPartyStuff(); 
} 

//...in your code: 
ThirdPartyClass1 classWeWishHadInterface = new ThirdPartyClass1() 
ICommonInterface classWrappedAsInterface = DuckTyping.Cast<ICommonInterface>(classWeWishHadInterface); 

classWrappedAsInterface.DoThirdPartyStuff(); 

Cela évite d'avoir à construire des classes wrapper dérivées manuellement pour toutes ces classes - et fonctionnera aussi longtemps que la classe a les mêmes membres que l'interface

+0

@saret: Oui, j'ai adressé le DLR dans ma réponse. Je ne connaissais pas le DuckTyping. Très agréable. Merci. –

Questions connexes