2009-08-29 6 views
1

Compte tenu de l'application de la console exemple ci-dessous:héritage multi-niveaux avec interface fluide en C#

QUESTION 1: Pourquoi .Nom() retourne typeof OranizationBuilder, mais .write() appelle CorporationBuilder?

QUESTION # 2: Comment obtenir .Name() pour renvoyer typeof CorporationBuilder?

namespace MyCompany 
{ 
    using System; 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      Console.WriteLine(Environment.NewLine); 

      Factory.Organization() 
        .ID(33) 
        .Name("Oranization A") 
        .Write(); 

      Console.WriteLine("\n----------------------------\n"); 

      Factory.Corporation() 
        .Date(DateTime.Today)  // Pass 
        .ID(44) 
        .Name("Company B") 
        // .Date(DateTime.Today) // Fail 
        .Write(); 

      // QUESTION #1: Why does .Name() return typeof OranizationBuilder, 
      //    but .Write() calls CorporationBuilder? 

      // QUESTION #2: How to get .Name() to return typeof CorporationBuilder? 


      Console.ReadLine(); 
     } 
    } 

    /* Business Classes */ 

    public abstract class Contact 
    { 
     public int ID { get; set; } 
    } 

    public class Organization : Contact 
    { 
     public string Name { get; set; } 
    } 

    public class Corporation : Organization 
    { 
     public DateTime Date { get; set; } 
    } 


    /* Builder */ 

    public abstract class ContactBuilder<TContact, TBuilder> 
     where TContact : Contact 
     where TBuilder : ContactBuilder<TContact, TBuilder> 
    { 
     public ContactBuilder(TContact contact) 
     { 
      this.contact = contact; 
     } 

     private TContact contact; 

     public TContact Contact 
     { 
      get 
      { 
       return this.contact; 
      } 
     } 

     public virtual TBuilder ID(int id) 
     { 
      this.Contact.ID = id; 
      return this as TBuilder; 
     } 

     public virtual void Write() 
     { 
      Console.WriteLine("ID : {0}", this.Contact.ID); 
     } 
    } 

    public class OrganizationBuilder : ContactBuilder<Organization, OrganizationBuilder> 
    { 
     public OrganizationBuilder(Organization contact) : base(contact) { } 

     public virtual OrganizationBuilder Name(string name) 
     { 
      (this.Contact as Organization).Name = name; 
      return this; 
     } 

     public override void Write() 
     { 
      base.Write(); 
      Console.WriteLine("Name : {0}", this.Contact.Name); 
     } 
    } 

    public class CorporationBuilder : OrganizationBuilder 
    { 
     public CorporationBuilder(Corporation contact) : base(contact) { } 

     public virtual CorporationBuilder Date(DateTime date) 
     { 
      // Cast is required, but need this.Contact to be typeof 'C' 
      (this.Contact as Corporation).Date = date; 
      return this; 
     } 

     public override void Write() 
     { 
      base.Write(); 
      Console.WriteLine("Date : {0}", (this.Contact as Corporation).Date.ToShortDateString()); 
     } 
    } 

    /* Factory */ 

    public class Factory 
    { 
     public static OrganizationBuilder Organization() 
     { 
      return new OrganizationBuilder(new Organization()); 
     } 

     public static CorporationBuilder Corporation() 
     { 
      return new CorporationBuilder(new Corporation()); 
     } 
    } 
} 

EDIT/UPDATE

Voici ma première tentative de solution (voir ci-dessous), bien que je suis coincé dans l'usine et ne savez pas comment configurer le .Organisation() et .Corporation() types de méthode.

namespace MyCompany 
{ 
    using System; 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      Console.WriteLine(Environment.NewLine); 

      Factory.Organization() 
        .ID(33) 
        .Name("Oranization A") 
        .Write(); 

      Console.WriteLine("\n----------------------------\n"); 

      Factory.Corporation() 
        .ID(44) 
        .Name("Company B") 
        .Date(DateTime.Today) 
        .Write(); 

      Console.ReadLine(); 
     } 
    } 


    /* Business Classes */ 

    public abstract class Contact 
    { 
     public int ID { get; set; } 
    } 

    public class Organization : Contact 
    { 
     public string Name { get; set; } 
    } 

    public class Corporation : Organization 
    { 
     public DateTime Date { get; set; } 
    } 


    /* Builder */ 

    public abstract class ContactBuilder<TContact, TBuilder> 
     where TContact : Contact 
     where TBuilder : ContactBuilder<TContact, TBuilder> 
    { 
     public ContactBuilder(TContact contact) 
     { 
      this.contact = contact; 
     } 

     private TContact contact; 

     public TContact Contact 
     { 
      get 
      { 
       return this.contact; 
      } 
     } 

     public virtual TBuilder ID(int id) 
     { 
      this.Contact.ID = id; 
      return this as TBuilder; 
     } 

     public virtual void Write() 
     { 
      Console.WriteLine("ID : {0}", this.Contact.ID); 
     } 
    } 

    public class OrganizationBuilder<TOrganization, TBuilder> : ContactBuilder<TOrganization, TBuilder> where TOrganization : Organization where TBuilder : OrganizationBuilder<TOrganization, TBuilder> 
    { 
     public OrganizationBuilder(TOrganization contact) : base(contact) { } 

     public virtual TBuilder Name(string name) 
     { 
      this.Contact.Name = name; 
      return this as TBuilder; 
     } 

     public override void Write() 
     { 
      base.Write(); 
      Console.WriteLine("Name : {0}", this.Contact.Name); 
     } 
    } 

    public class CorporationBuilder<TCorporation, TBuilder> : OrganizationBuilder<TCorporation, TBuilder> where TCorporation : Corporation where TBuilder : CorporationBuilder<TCorporation, TBuilder> 
    { 
     public CorporationBuilder(TCorporation contact) : base(contact) { } 

     public virtual TBuilder Date(DateTime date) 
     { 
      this.Contact.Date = date; 
      return this as TBuilder; 
     } 

     public override void Write() 
     { 
      base.Write(); 
      Console.WriteLine("Date : {0}", this.Contact.Date.ToShortDateString()); 
     } 
    } 


    /* Factory */ 

    public class Factory 
    { 
     public static OrganizationBuilder<Organization, OrganizationBuilder> Organization() 
     { 
      return new OrganizationBuilder<Organization, OrganizationBuilder>(new Organization()); 
     } 

     public static CorporationBuilder<Corporation, CorporationBuilder> Corporation() 
     { 
      return new CorporationBuilder<Corporation, CorporationBuilder>(new Corporation()); 
     } 
    } 
} 

Voici la problématique spécifique:

/* Factory */ 

public class Factory 
{ 
    public static OrganizationBuilder<Organization, OrganizationBuilder> Organization() 
    { 
     return new OrganizationBuilder<Organization, OrganizationBuilder>(new Organization()); 
    } 

    public static CorporationBuilder<Corporation, CorporationBuilder> Corporation() 
    { 
     return new CorporationBuilder<Corporation, CorporationBuilder>(new Corporation()); 
    } 
} 

Comment configurer le OrganizationBuilder et CorportationBuilder?

+0

Vous devriez retourner TBuilder dans ces méthodes Nom et date - non? –

+0

@Charles Prakash Dasari: bonne prise. J'ai révisé le deuxième échantillon. Problème avec le (s) type (s) de retour d'usine et le constructeur ctor. – jlang

+0

Voir le udpate sur la réponse ci-dessous ... –

Répondre

4

Lorsque Name renvoie une référence, il retourne this - donc quand l'instance est en fait une instance de CorporationBuilder, cette référence est retourné comme normal. Tout simplement parce que la méthode est déclarée pour retourner OrganizationBuilder ne veut pas dire seulement renvoie une référence OrganizationBuilder. Il renvoie une référence à une instance de OrganizationBuilder instance ou une classe dérivée (ou null, bien sûr). Lorsque la méthode Write est ensuite appelée, il s'agit d'une méthode virtuelle, de sorte que le type d'exécution de l'objet est vérifié pour trouver l'implémentation à utiliser.

Le type d'exécution est toujours CorporationBuilder, donc le remplacement spécifié dans ce type est utilisé. Comme pour faire Name() retourner le type approprié - cela nécessiterait plus de génériques, fondamentalement. Cela peut être fait, mais c'est une douleur - j'ai fait quelque chose de similaire dans Protocol Buffers, mais ce n'est pas agréable.Vous devez également créer OrganizationBuilder générique dans TContact et TBuilder, et faire Name retourner TBuilder via une distribution de this à TBuilder. Alors CorporationBuilder serait soit générique aussi, soit hériterait de OrganizationBuilder<Corporation, CorporationBuilder>.

EDIT: Oui, je vois le problème (que j'avais oublié auparavant). Vous voudrez peut-être avoir une classe non générique béton appelé CorporationBuilder ainsi, pour éviter les génériques récursives:

public class OrganizationBuilder : 
    OrganizationBuilder<Organization, OrganizationBuilder> 

Vous pouvez également renommer OrganizationBuilder à OrganizationBuilderBase pour éviter toute confusion :)

(Vous n Il n'est pas nécessaire que CorporationBuilder soit lui-même générique s'il se trouve en bas de la hiérarchie.)

Cependant, cela devient extrêmement complexe. Vous voudrez peut-être au moins envisager en évitant l'héritage ici. Scrap les génériques, et faites OrganizationBuilderont un CorporationBuilder au lieu d'en dériver. Fondamentalement, ce modèle se complique toujours après un certain temps - vous avez besoin de tous les niveaux génériques en dehors des nœuds feuilles, qui doivent toujours être non génériques pour éviter le problème de récurrence que vous avez déjà vu. C'est une douleur.

+0

@Jon Skeet - J'ai mis à jour le post original avec ma première tentative de solutions plus élaborées, mais il y a des problèmes. Je suis coincé sur les méthodes factorielles? des conseils? – jlang

+0

post EDIT: vous, vous l'avez cloué. Je suis arrivé à la même conclusion et j'ai décidé que OrganizationBuilder "est une" hiérarchie OrganizationBuilderBase. La feuille (OrganizationBuilder) "est une classe" non générique (ified) qui se branche à partir de la branche OrganizationBuilderBase générique (ified). Cela semble bien fonctionner, alors je vais courir avec ce scénario et voir où cela me mène. – jlang

0

La fonction .Name() dans OrganizationBuilder a une signature pour renvoyer le type OrganizationBuilder, quel que soit l'objet dérivé duquel il est appelé. C'est pourquoi vous le voyez renvoyer OrganizationBuilder. Si vous devez remplacer la fonction Name() dans votre constructeur de contrat et définir le nom à quelque chose d'autre, vous remarquerez que la fonction Name() agit sur votre objet d'exécution.

Maintenant, si vous voulez savoir comment rendre Name() retourner le constructeur que vous voulez, vous devez suivre la même technique que celle utilisée pour la méthode ID().

EDIT/UPDATE: Eh bien, maintenant, je ne comprends pas l'erreur réelle que vous rencontrez - avec les nouvelles mises à jour. Pouvez-vous partager l'erreur exacte à laquelle vous faites face?

Sur une note de côté: Je pense que ce design est totalement compliqué. Je ne donnerais pas ceci à mes consommateurs juste pour soutenir un modèle gentil d'une méthode de constructeur renvoyant l'objet de constructeur approprié. Je m'en tiens à une approche beaucoup plus simple.

+0

alambiqué .. peut-être je suis d'accord que les exemples de classes d'affaires ne sont pas le meilleur exemple, mais j'essayais de faire un exemple générique. L'usecase réel bénéficierait de l'API fluide. Les classes Builder semblent alambiquées, mais l'API de l'utilisateur final génère une interface fluide propre. L'erreur * réelle que je fais face est le deuxième exemple d'application ci-dessus ne compile pas. Voici le message d'erreur: Erreur Utilisation du type générique « MyCompany.OrganizationBuilder » exige « 2 » arguments de type L'erreur est évidente, mais je ne suis pas sûr de la syntaxe correcte fais-le fonctionner. – jlang

+0

J'essaie de faire ce travail parce que tous les exemples d'interface/api fluides C# que j'ai vu fonctionner seulement avec un niveau d'héritage dans Visual Studio intellisense. – jlang

+0

La chose sur laquelle je suis bloqué est comment créer une nouvelle instance de CorporationBuilder ?? [Exemple] return new CorporationBuilder (New Corporation()); – jlang