Pregunta

ACTUALIZACIÓN: Así que casi todos los que están aquí me han dicho que simplemente debo comenzar de nuevo sobre cómo diseñé mis clases (¡gracias a todos por sus excelentes respuestas, por cierto!). Tomando la sugerencia, comencé a leer detenidamente el patrón de estrategia . Quiero crear clases de comportamiento (o clases de estrategia) que heredan de una clase o clases base abstractas. La clase candidata tendría propiedades con las diferentes clases / clases base abstractas como Type para los comportamientos o estrategias. tal vez algo como esto:

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
}

¿Algún comentario o sugerencia?


Pregunta ORIGINAL:

Estoy tratando de aprender y ser más competente en los patrones y principios de diseño. Actualmente estoy trabajando en un diseño para pocas clases que me ha dejado perplejo. Aquí hay una versión muy condensada del código:

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 clase InternalEntryLevelCandidate es principalmente un EntryLevelCandidate pero necesita compartir algunas de las implementations de InternalCandidate. Digo implementaciones porque no quiero que las implementaciones sean diferentes o repetidas, de lo contrario, usaría una interfaz para contratos comunes y tendría implementaciones concretas en cada clase. Algunas de las implementaciones de las propiedades y comportamientos de InternalCandidate deben ser comunes o compartidas. He leído acerca de las mezclas de C ++ y Ruby, que parecen ser algo similar a lo que quiero hacer. También leí esta interesante publicación de blog que analiza una idea para un tipo de comportamiento en el que una clase podría heredar varios comportamientos mientras mantiene un solo " es un " relación: http: // www. deftflux.net/blog/post/A-good-design-for-multiple-implementation-inheritance.aspx . Esto parece transmitir lo que estoy deseando. ¿Alguien puede darme alguna orientación sobre cómo puedo lograr esto usando buenas prácticas de diseño?

¿Fue útil?

Solución

Clases de valores de datos inmutables. Si alguna de las propiedades en sus diversas subclases de Candidatos representa algún tipo de valor de datos significativo, cree una clase inmutable para ello, con los comportamientos que necesita . Cada una de sus distintas subclases de Candidato puede usar el tipo de datos, pero su código aún está encapsulado en las clases de datos.

Métodos de extensión. Estos pueden sobrecargarse para funcionar con cualquier clase.

Evitaría el patrón de decorador y me quedaría con la funcionalidad compilada / reflectable.

Composición. Desarrolle los comportamientos únicos en clases separadas de inmediato, y desarrolle sus clases de Candidatos a su alrededor, en lugar de escribir comportamientos únicos en sus clases de Candidatos y tratar de extraer su funcionalidad para usarlas en clases más tarde.

Dependiendo de cómo use las clases, también podría implementar y hacer uso de operadores de conversión explícitos e implícitos a un tipo relacionado, por lo que, en lugar de volver a implementar interfaces (que quería evitar), en realidad podría convertir su objeto en el tipo / implementación que necesite para cualquier propósito.

Otra cosa en la que acabo de pensar, relacionada con el último párrafo, es tener un sistema de arrendamiento, donde su clase genera y el objeto del tipo apropiado, permite que sea manipulado y luego lo consuma para asimilar la información actualizada.

Otros consejos

Aquí hay un artículo académico sobre el tema que creo que es bastante interesante ( Enlace PDF ).

Pero, creo que está intentando imponer lógica de negocios en sus generalizaciones. Usted sabe que un InternalCandidate nunca tendrá su GPA revisado. Pero, un InternalCandidate ciertamente tiene un GPA. Entonces, has eliminado a este extraño tipo llamado InternalEntryLevelCandidate porque sabes que quieres ver el GPA de este tipo. Arquitectónicamente, creo que el EntryLevelCandidate es incorrecto. Me gustaría añadir un " Nivel " concepto a un candidato y darle un GPA. Depende de la lógica empresarial para decidir si analizan el GPA o no.

Editar: Además, Scott Meyers hace un gran trabajo de disección de este problema en sus libros.

Descargo de responsabilidad :

En mi experiencia, la necesidad de herencia múltiple es la excepción en lugar de la regla, el diseño cuidadoso de las jerarquías de clases generalmente puede evitar la necesidad de esta característica. Estoy de acuerdo con JP en que este requisito podría evitarse en su muestra.

Volviendo a la pregunta, no hay una solución limpia, sin embargo, tiene algunas opciones:

  • Usar métodos de extensión, tiene la desventaja de que hacer clic con el botón derecho Resolver no funciona, también a algunas personas realmente les disgustan estos cachorros.

  • Cree un objeto agregado que contenga una instancia de cada clase que desee componer, vuelva a implementar los métodos de código auxiliar que delegue.

  • Defina una interfaz para cada comportamiento y haga que los métodos de la base verifiquen si esto es IInterface antes de ejecutar el comportamiento. (le permite tirar definiciones de comportamiento a la base)

Casi duplicado: herencia múltiple en C #

Estoy de acuerdo en que la herencia no parece ser lo correcto aquí. No estoy seguro de saber la respuesta perfecta, pero quizás el Patrón decorador es apropiado.

Otra idea más esotérica es pensar en la programación orientada a aspectos. Puedes hacer cosas bastante sorprendentes con aspectos, pero es un tema muy avanzado que aún no he dominado. El tipo de gente que tiene es como Rikard Oberg y sus cohortes Qi4J .

Acabo de usar el patrón de delegación . En última instancia, usaría una interfaz para cada pieza distinta de funcionalidad, luego tendría una clase concreta como delegado para cada interfaz. Luego, las clases finales solo usan los delegados que necesitan y pueden heredar de múltiples 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 }
    }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top