2012-04-04 15 views
5

A menudo utilizamos enumeraciones simples para representar un estado en nuestras entidades. El problema surge cuando presentamos un comportamiento que depende en gran medida del estado o en el que las transiciones de estado deben cumplir con ciertas reglas comerciales.Patrón de estado y diseño impulsado por dominio

Tome el siguiente ejemplo (que utiliza una enumeración para representar estado):

public class Vacancy { 

    private VacancyState currentState; 

    public void Approve() { 
     if (CanBeApproved()) { 
      currentState.Approve(); 
     } 
    } 

    public bool CanBeApproved() { 
     return currentState == VacancyState.Unapproved 
      || currentState == VacancyState.Removed 
    } 

    private enum VacancyState { 
     Unapproved, 
     Approved, 
     Rejected, 
     Completed, 
     Removed 
    } 
} 

Se puede ver que esta clase pronto llegar a ser bastante detallado a medida que añadimos métodos para rechazar, retire etc.

lugar podemos introducir el patrón de Estado, lo que nos permite encapsular cada estado como objeto:

public abstract class VacancyState { 

    protected Vacancy vacancy; 

    public VacancyState(Vacancy vacancy) { 
     this.vacancy = vacancy; 
    } 

    public abstract void Approve(); 
    // public abstract void Unapprove(); 
    // public abstract void Reject(); 
    // etc. 

    public virtual bool CanApprove() { 
     return false; 
    } 
} 

public abstract class UnapprovedState : VacancyState { 

    public UnapprovedState(vacancy) : base(vacancy) { } 

    public override void Approve() { 
     vacancy.State = new ApprovedState(vacancy); 
    } 

    public override bool CanApprove() { 
     return true; 
    } 
} 

Esto hace que sea fácil hacer la transición entr n estados, realizan lógica basada en el estado actual o añadir nuevos estados si necesitamos:

// transition state 
vacancy.State.Approve(); 

// conditional 
model.ShowRejectButton = vacancy.State.CanReject(); 

Esta encapsulación parece más limpio pero dado suficientes estados, estos también pueden llegar a ser muy detallado. Leí Greg Young's post on State Pattern Misuse que sugiere usar polimorfismo en su lugar (por lo que tendría las clases ApprovedVacancy, UnapprovedVacancy etc.), pero no puedo ver cómo me ayudará esto.

¿Debo delegar tales transiciones de estado a un servicio de dominio o es correcto mi uso del patrón de estado en esta situación?

Respuesta

5

Para responder a su pregunta, no debe delegar esto en un servicio de dominio y su uso del patrón de estado es casi correcto.

Para elaborar, la responsabilidad de mantener el estado de un objeto pertenece a ese objeto, por lo que relegar esto a un servicio de dominio conduce a modelos anémicos. Esto no quiere decir que la responsabilidad de la modificación del estado no pueda delegarse mediante el uso de otros patrones, pero esto debería ser transparente para el consumidor del objeto.

Esto me lleva a su uso del patrón de estado. En su mayor parte, estás usando el patrón correctamente. La única parte en la que te extravías un poco es en tus violaciones a la Ley de Demeter. El consumidor de su objeto no debe acercarse a su objeto y llamar a los métodos en su estado (por ejemplo, vacante.State.CanReject()), sino que su objeto debería estar delegando esta llamada al objeto State (por ejemplo, vacante.CanReject() - > bool CanReject() {return _state.CanReject();}). El consumidor de su objeto no debería saber que está utilizando el patrón de estado.

Para comentar sobre el artículo que ha mencionado, el patrón de estado se basa en el polimorfismo, ya que es un mecanismo facilitador. El objeto que encapsula una implementación de estado puede delegar una llamada a la implementación asignada actualmente, ya sea que sea algo que no haga nada, genere una excepción o realice alguna acción. Además, si bien es posible causar una violación del Principio de sustitución de Liskov utilizando el patrón de estado (o cualquier otro patrón), esto no está determinado por el hecho de que el objeto puede arrojar una excepción o no, sino por si las modificaciones a un objeto puede hacerse a la luz del código existente (lea this para mayor discusión).

+0

si el consumidor no debe cambiar el estado directamente, seguramente esto significa que terminaré con un método para cada transición de estado en mi entidad. Entonces, ¿qué estoy realmente ganando aquí? –

+2

Obtiene la encapsulación y la ausencia de un montón de sentencias if :) Aquí están los beneficios oficiales: 1. Localiza comportamiento de estado y particiones específicas para diferentes estados. 2. Hace explícitas las transiciones de estado. 3. Los objetos de estado se pueden compartir. –

+0

Gracias.Una última pregunta: estamos persistiendo en la vacante en una tienda de documentos (RavenDB). Sugeriría persistir una enumeración que se utiliza para cargar el objeto de estado relevante o simplemente almacenar todo el objeto de estado –

Cuestiones relacionadas