Question

UPDATE: Tous les gens ici m'ont dit qu'il me fallait tout recommencer sur la façon dont j'ai conçu mes cours (merci à tous pour vos excellentes réponses!). Prenant l’idée, j’ai commencé à faire des lectures approfondies sur le modèle de stratégie . Je veux créer des classes de comportement (ou classes de stratégie) qui héritent d'une classe ou de classes de base abstraites. La classe Candidat aurait alors des propriétés avec les différentes classes de base abstraites comme Type pour les comportements ou stratégies. peut-être quelque chose comme ça:

public abstract class SalaryStrategy {
    public abstract decimal Salary { get; set; }
    public abstract decimal Min { get; set; }
    public abstract decimal Mid { get; set; }
    public decimal CompaRatio {
        get {
            if (this.Mid == 0) { return 0; }
            else { return this.Salary / this.Mid; }
        }
    }
}

public class InternalCurrentSalaryStrategy {
    public override decimal Salary { get; set; }
    public override decimal Min {
        get { return this.Salary * .25m; }
        set { }
    }
    public override decimal Mid { get; set; }
}

public class Candidate {
    public int Id { get; set; }
    public string Name { get; set; }
    public SalaryStrategy CurrentSalaryStrategy { get; set; }
}

public static void Main(string[] args) {
    var internal = new Candidate();
    internal.CurrentSalaryStrategy = new InternalCurrentSalaryStrategy();
    var internalElp = new Candidate();
    internalElp.CurrentSalaryStrategy = new InternalCurrentSalaryStrategy();
    var elp = new Candidate();
    // elp.CurrentSalaryStrategy can stay null cause it's not used for elps
}

Avez-vous des commentaires ou des suggestions?

Question ORIGINALE:

J'essaie d'apprendre et de mieux maîtriser les modèles et les principes de conception. Je travaille actuellement sur une conception pour quelques classes qui m'a laissé perplexe. Voici une version très condensée du code:

public class Candidate {
    public int Id { get; set; }
    public string Comments { get; set; }
    // lots more properties and behaviors...
}

public class InternalCandidate : Candidate {
    public decimal CurrentMid { get; set; }
    public decimal CurrentMax {
         get { return this.CurrentMin * 1.3m;
    }
    // lots more properties and behaviors...
}

public class EntryLevelCandidate : Candidate {
    public string Gpa { get; set; }
    // lots more properties and behaviors...
}

public class InternalEntryLevelCandidate /* what do I inherit here??? */ {
    // needs all of the properties and behaviors of
    // EntryLevelCandidate but also needs the CurrentMin and
    // CurrentMax (and possibly more) in InternalCandidate
}

La classe InternalEntryLevelCandidate est principalement un EntryLevelCandidate mais doit partager certaines des implémentations de InternalCandidate. Je dis implémentations parce que je ne veux pas que les implémentations soient différentes ou répétées, sinon j'utiliserais une interface pour des contrats communs et disposerais d'implémentations concrètes dans chaque classe. Certaines implémentations des propriétés et comportements InternalCandidate doivent être communes ou partagées. J'ai lu sur les mixins C ++ et Ruby, qui semblent être quelque chose de similaire à ce que je veux faire. J'ai également lu cet article de blog intéressant qui traite d'une idée de type de comportement dans lequel une classe pourrait hériter de plusieurs comportements tout en maintenant un seul "est un". relation: http: // www. deftflux.net/blog/post/A-good-design-for-multiple-implementation-inheritance.aspx . Cela semble transmettre ce que je veux. Quelqu'un peut-il m'expliquer comment y parvenir en utilisant de bonnes pratiques de conception?

Était-ce utile?

La solution

Classes de valeurs de données immuables. si des propriétés de vos diverses sous-classes de candidats représentent un type de valeur de données explicite, créez une classe immuable pour les classes dont vous avez besoin. . Chacune de vos sous-classes Candidate distinctes peut ensuite utiliser le type de données, mais votre code est toujours encapsulé dans les classes de données.

Méthodes d'extension. elles pourraient être surchargées pour fonctionner avec toutes les classes.

J'éviterais le motif de décorateur et me contenterais de fonctionnalités compilées / réfléchissables.

Composition. Développez immédiatement les comportements uniques dans des classes distinctes et créez votre classe de candidats autour d'elle, plutôt que de créer des comportements uniques dans vos classes de candidats et d'essayer de tirer parti de leurs fonctionnalités pour les utiliser dans des environnements connexes. cours plus tard.

En fonction de votre utilisation des classes, vous pouvez également implémenter et utiliser des opérateurs de conversion explicites et implicites en un type associé. Ainsi, au lieu de réimplémenter des interfaces (ce que vous vouliez éviter), vous pourriez en fait transposer votre objet dans le type / mise en œuvre dont vous avez besoin pour quelque fin que ce soit.

Une autre chose à laquelle je viens de penser, en relation avec ce dernier paragraphe, est d’avoir un système de crédit-bail, où votre classe apparaît et un objet du type approprié, permet de le manipuler, puis le consomme plus tard pour assimiler les informations mises à jour.

Autres conseils

Voici un article scientifique sur le sujet qui, à mon avis, est très intéressant ( Lien PDF ).

Mais je pense que vous essayez d’imposer la logique d’entreprise dans vos généralisations. Vous savez qu'un Candidat interne n'aura jamais son GPA examiné. Mais un InternalCandidate a certainement un GPA. Donc, vous avez démantelé cet étrange gars appelé un InternalEntryLevelCandidate parce que vous savez que vous voulez regarder l'AMP de ce type. Sur le plan architectural, je pense que EntryLevelCandidate est incorrect. J'ajouterais un " Niveau " concept à un candidat et lui donner un GPA. Il appartient à la logique applicative de décider s’il examine ou non le GPA.

Modifier: Scott Meyers dissèque très bien ce problème dans ses livres.

Clause de non-responsabilité :

D'après mon expérience, la nécessité de recourir à l'héritage multiple est l'exception plutôt que la règle. Une conception soigneuse des hiérarchies de classes permet généralement d'éviter de recourir à cette fonctionnalité. Je conviens avec JP que cette exigence pourrait être évitée dans votre échantillon.

Retour à la question, il n'y a pas de solution propre, mais vous avez quelques options:

  • Utiliser des méthodes d'extension a l'inconvénient que le clic droit sur Résoudre ne fonctionne pas. Certaines personnes n'aiment vraiment pas ces chiots.

  • Créez un objet agrégé contenant une instance de chaque classe à composer, réimplémentez des méthodes de stub délégantes.

  • Définissez une interface pour chaque comportement et demandez aux méthodes de la base de vérifier si il s'agit de IInterface avant d'exécuter le comportement. (vous permet d'extraire les définitions de comportement vers la base)

Quasi en double: Héritage multiple en C #

Je conviens que l'héritage ne semble pas être la bonne chose ici. Je ne suis pas sûr de connaître la réponse parfaite, mais peut-être le Le motif de décorateur est approprié.

Une autre idée plus ésotérique est de penser à la programmation par aspects. Vous pouvez faire des choses assez étonnantes avec des aspects, mais c'est un sujet très avancé que je n'ai toujours pas maîtrisé. Rikard Oberg et ses cohortes de Qi4J

font partie de ces groupes.

J'aimerais simplement utiliser le modèle de délégation . En fin de compte, j'utiliserais une interface pour chaque fonctionnalité distincte, puis une classe concrète en tant que délégué pour chaque interface. Ensuite, vos classes finales utilisent uniquement les délégués dont elles ont besoin et peuvent hériter de plusieurs interfaces.

public class InternalEntryLevelCandidate : EntryLevelCandidate {
    private  InternalCandidate internalCandidateDelegate
        = new InternalCandidate();

    public decimal CurrentMid { 
        get { return internalCandidateDelegate.CurrentMid; }
        set { internalCandidateDelegate.CurrentMid = value; }
    }

    public decimal CurrentMax {
        get { return internalCandidateDelegate.CurrentMax }
    }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top