2010-12-15 14 views
9

He estado viendo algunos videos de Greg Young últimamente y estoy tratando de entender por qué hay una actitud negativa hacia Setters en objetos de dominio. Pensé que se suponía que los objetos de Dominio eran "pesados" con la lógica en DDD. ¿Hay algún buen ejemplo en línea del mal ejemplo y luego forma de corregirlo? Cualquier ejemplo o explicación es bueno. ¿Esto solo se aplica a los eventos almacenados en una forma CQRS o esto se aplica a todos los DDD?Diseño impulsado por dominio, objetos de dominio, actitud sobre Setters

Respuesta

11

Estoy contribuyendo esta respuesta para complementar la respuesta de Roger Alsing sobre invariantes por otras razones.

La información semántica

Roger explicó claramente que los emisores de propiedad no llevan información semántica. Permitir que un colocador de una propiedad como Post.PublishDate pueda agregar confusión, ya que no podemos estar seguros de si la publicación se ha publicado o solo si se ha establecido la fecha de publicación. No podemos estar seguros de que esto sea todo lo que se necesita para publicar un artículo. El objeto "interfaz" no muestra su "intención" claramente.

Creo, sin embargo, que las propiedades como "Habilitado" tienen suficiente semántica para "obtener" y "establecer". Es algo que debería ser efectivo de inmediato y no puedo ver la necesidad de los métodos SetActive() o Activate()/Deactivate() por la sola razón de perder semántica en el establecimiento de propiedades.

invariantes

Roger también se refirió a la posibilidad de romper invariantes a través de los emisores de propiedad. Esto es absolutamente correcto, y uno debe hacer propiedades que funcionen en tándem para proporcionar un "valor invariante combinado" (como los ángulos del triángulo para usar el ejemplo de Roger) como propiedades de solo lectura y crear un método para establecerlas todas juntas, lo que puede valide todas las combinaciones en un solo paso.

fin propiedad de inicialización/dependencias de ajuste

Esto es similar al problema con invariantes ya que causa problemas con las propiedades que deben ser validados/cambiaron juntos. Imagine el siguiente código:

public class Period 
    { 
     DateTime start; 
     public DateTime Start 
     { 
      get { return start; } 
      set 
      { 
       if (value > end && end != default(DateTime)) 
        throw new Exception("Start can't be later than end!"); 
       start = value; 
      } 
     } 

     DateTime end; 
     public DateTime End 
     { 
      get { return end; } 
      set 
      { 
       if (value < start && start != default(DateTime)) 
        throw new Exception("End can't be earlier than start!"); 
       end = value; 
      } 
     } 
    } 

Este es un ejemplo ingenuo de validación "setter" que causa dependencias de orden de acceso. El código siguiente ilustra este problema:

 public void CanChangeStartAndEndInAnyOrder() 
     { 
      Period period = new Period(DateTime.Now, DateTime.Now); 
      period.Start = DateTime.Now.AddDays(1); //--> will throw exception here 
      period.End = DateTime.Now.AddDays(2); 
      // the following may throw an exception depending on the order the C# compiler 
      // assigns the properties. 
      period = new Period() 
      { 
       Start = DateTime.Now.AddDays(1), 
       End = DateTime.Now.AddDays(2), 
      }; 
      // The order is not guaranteed by C#, so either way may throw an exception 
      period = new Period() 
      { 
       End = DateTime.Now.AddDays(2), 
       Start = DateTime.Now.AddDays(1), 
      }; 
     } 

Dado que no podemos cambiar la fecha de inicio más allá de la fecha de finalización en un periodo objeto (a no ser que se trata de un período de "vacío", con las dos fechas establecidas por defecto (DateTime) - Sí, no es un gran diseño, pero entiendes lo que quiero decir ...) intentar establecer primero la fecha de lanzamiento generará una excepción.

Se pone más serio cuando usamos los inicializadores de objetos. Como C# no garantiza ninguna orden de asignación, no podemos hacer suposiciones seguras y el código puede lanzar o no una excepción dependiendo de las elecciones del compilador. ¡MALO!

Esto es en última instancia un problema con el DISEÑO de las clases. Como la propiedad no puede "saber" que está actualizando ambos valores, no puede "desactivar" la validación hasta que ambos valores realmente se cambien. Debe hacer ambas propiedades de solo lectura y proporcionar un método para establecer ambas al mismo tiempo (perder la función de los inicializadores de objetos) o eliminar el código de validación de las propiedades (quizás introduciendo otra propiedad de solo lectura como IsValid o validando siempre que sea necesario).

ORM "hidratación" *

hidratación, en una visión simplista, significa obtener los datos conservados de nuevo en objetos. Para mí, este es realmente el mayor problema al agregar lógica detrás de los establecedores de propiedades.

Muchos/la mayoría de los ORM asignan el valor persistente a una propiedad. Si tiene una lógica de validación o lógica que cambia el estado del objeto (otros miembros) dentro de los establecedores de propiedades, terminará tratando de validar contra un objeto "incompleto" (uno aún se está cargando). Esto es muy similar al problema de inicialización de objetos ya que no puede controlar el orden en que los campos están "hidratados".

La mayoría de los ORM le permiten mapear la persistencia a campos privados en lugar de propiedades, y esto permitirá que los objetos se hidraten, pero si su lógica de validación se encuentra principalmente dentro de los ajustadores de propiedades, puede tener que duplicar objeto cargado es válido o no.

Dado que muchas herramientas ORM soportan la carga diferida (un aspecto fundamental de un ORM!) A través del uso de propiedades virtuales (o métodos) el mapeo a los campos imposibilitará que el ORM cargue los objetos asignados en campos.

Conclusión

Así que, al final, para evitar la duplicación de código, permiten ORM para llevar a cabo lo mejor que pudo, impiden excepciones sorprendentes dependiendo del orden se establecen los campos, es aconsejable mover la lógica lejos de establecedores de propiedad.

Todavía estoy averiguando dónde debería estar esta lógica de 'validación'. ¿Dónde validamos los aspectos invariantes de un objeto? ¿Dónde ponemos validaciones de alto nivel? ¿Usamos ganchos en los ORM para realizar la validación (OnSave, OnDelete, ...)? Etc. Etc. Etc. Pero este no es el alcance de esta respuesta.

+1

entonces en su ejemplo deberíamos usar una sola función como 'period.setDateBoundries (startDate, endDate)' para evitar setters? una función única que tiene un significado comercial en lugar de establecedores ficticios? – Songo

+1

@Songo eso es lo que haría. Pero entonces uno podría quejarse para tener que pasar ambas fechas para cambiar solo una (imagínese si quisiéramos extender un período, ¡tendríamos que aprobar ambas fechas! ... pero entonces, oye, ¿por qué no creamos un método Extendido? ?) Piense de esta manera: cada vez que termina con una propiedad que depende de otra, entonces tal vez sea una buena idea introducir métodos para cambiarlos o métodos que agregarán más valor semántico (como el Período.Extender uno ...)! – Loudenvier

+0

Estoy considerando pasar arreglos. No es genial para IDE indirecta, pero me permite pasar datos en cualquier orden. También puedo agregar u omitir datos según la circunstancia (es decir, intervalo de fecha completo o solo una fecha). La validación se realiza de todos modos, por lo que no parece mucho más una carga verificar también si se dio u omitió un parámetro. – prograhammer

1

Un colocador simplemente establece un valor. No se supone que sea "heavy" with logic.

Los métodos en sus objetos que tienen buenos nombres descriptivos deben ser los "heavy" with logic y tienen un análogo en el propio dominio.

7

La razón detrás de esto es que la entidad misma debería ser responsable de cambiar su estado. No hay realmente ninguna razón por la que deba establecer una propiedad en cualquier lugar fuera de la entidad misma.

Un ejemplo simple sería una entidad que tiene un nombre. Si tiene un organismo público, podrá cambiar el nombre de una entidad desde cualquier lugar de su aplicación. Si, en su lugar, quita ese setter y pone un método como ChangeName(string name) en su entidad, esa será la única forma de cambiar el nombre. De esta forma, puede agregar cualquier clase de lógica que siempre se ejecutará cuando cambie el nombre porque solo hay una forma de cambiarlo. Esto también es mucho más claro que simplemente establecer el nombre a algo.

Básicamente esto significa que expone públicamente el comportamiento de sus entidades mientras mantiene el estado en privado.

+0

Y será un colocador de nuevo. Pero en este caso tiene un método (o un consumidor de mensajes) en lugar de propiedad y hace que su objeto de dominio esté activo (no solo DTO). Y por qué necesita (o no necesita) hacer lo que se explica en el artículo http://martinfowler.com/bliki/AnemicDomainModel.html –

+0

@Vsevolod Parfenov, Exactamente. Este fue un ejemplo muy simple. Pero a veces puede que necesites agregar algún tipo de lógica a ese método. Quizás también quieras actualizar algún otro campo. –

+0

El SETTER para una propiedad es solo un azúcar sintáctico para un método. Si una propiedad tiene lógica, también hace que su objeto de dominio sea un objeto activo no solo un DTO, porque de hecho acaba de agregarle un método, aunque "oculto" como un setter de propiedad. Aún tendría un solo lugar para cambiar el nombre de la entidad: el establecimiento de la propiedad Nombre. Entonces estas no son las razones de la actitud negativa hacia los que establecen la propiedad. Hay otras razones (que elaboraré en una respuesta): invariantes, dependencia de secuencia, problemas al hidratar entidades desde una herramienta ORM. – Loudenvier

2

La pregunta original está etiquetada .net, por lo tanto, presentaré un enfoque pragmático para el contexto en el que desea vincular sus entidades directamente a una vista.

Sé que eso es una mala práctica, y que probablemente debería tener un modelo de vista (como en MVVM) o similar, pero para algunas aplicaciones pequeñas simplemente tiene sentido no sobre-patronizar en mi humilde opinión.

El uso de propiedades es la forma en que el enlace de datos listo para usar funciona en .net. Tal vez el contexto estipule que el enlace de datos debería funcionar en ambos sentidos y, por lo tanto, implementar INotifyPropertyChanged y subir PropertyChanged como parte de la lógica del setter tiene sentido.

La entidad podría, p. agregar un elemento a una colección de reglas rotas o similares (sé que CSLA tuvo ese concepto hace unos años y tal vez todavía lo haga) cuando el cliente establece un valor no válido, y esa colección podría mostrarse en la interfaz de usuario. La unidad de trabajo más tarde se negaría a persistir objetos inválidos, si llegara tan lejos.

Estoy tratando de justificar el desacoplamiento, la inmutabilidad, etc. en gran medida. Solo digo que en algunos contextos se necesita una arquitectura más simple.

+0

+1 Estoy luchando con el concepto por los motivos exactos que enumeró. Estoy familiarizado con el MVVM de Silverlight y la experiencia anterior con algunos CSLA. Me encantaría ver comentarios de otros desarrolladores de .NET. – BuddyJoe

10

Setters no lleva ninguna información semántica.

p. Ej.

blogpost.PublishDate = DateTime.Now; 

¿Eso significa que la publicación ha sido publicada? ¿O simplemente que se ha establecido la fecha de publicación?

considerar:

blogpost.Publish(); 

Esto muestra claramente la intención de que la entrada de blog debe ser publicado.

Además, los programadores pueden romper las invariantes del objeto. Por ejemplo, si tenemos una entidad "Triangular", la invariante debería ser que la suma de todos los ángulos debería ser de 180 grados.

Assert.AreEqual (t.A + t.B + t.C ,180); 

Ahora bien, si tenemos los emisores, podemos romper fácilmente los invariantes:

t.A = 0; 
t.B = 0; 
t.C = 0; 

Así que tenemos un triángulo donde la suma de todos los ángulos son 0 ... ¿Es realmente un triángulo? Yo diría que no.

emisores de Sustitución con métodos nos podrían obligar a mantener invariantes:

t.SetAngles(0,0,0); //should fail 

Tal llamada debe lanzar una excepción que le dice que esto provoca un estado no válido para su entidad.

Así que obtienes semántica e invariantes con métodos en lugar de setters.

+0

así que mientras no rompa un invariante usando setters ¿está bien? Quiero decir, si un usuario está editando una información de clientes (Nombre, Segundo nombre, Apellido) y el único invariante es que ninguno de ellos puede estar vacío o NULO, entonces el uso de un colocador para cada campo es aceptable. – Songo

-1

Puedo estar fuera de aquí, pero creo que los setters deberían usarse para establecer, a diferencia de los métodos setter. Tengo algunas razones para esto.

a) Tiene sentido en .Net. Todo desarrollador conoce las propiedades. Así es como pones las cosas en un objeto. ¿Por qué desviarse de eso para los objetos de dominio?

b) Setters pueden tener código. Antes de 3.5 creo, el establecimiento de un objeto consistió en una variable interna y una firma propiedad

private object _someProperty = null; 
public object SomeProperty{ 
    get { return _someProperty; } 
    set { _someProperty = value; } 
} 

Es muy fácil y elegante de la OMI para poner la validación en la incubadora. En IL, getters y setters se convierten en métodos de todos modos. ¿Por qué duplicar el código?

En el ejemplo del método Publish() publicado anteriormente, estoy totalmente de acuerdo. Hay momentos en que no queremos que otros desarrolladores establezcan una propiedad. Eso debería ser manejado por un método. Sin embargo, ¿tiene sentido tener un método setter para cada propiedad cuando .Net ya ofrece toda la funcionalidad que necesitamos en la declaración de propiedad?

Si tiene un objeto Persona, ¿por qué crear métodos para cada propiedad en él si no hay ninguna razón?

Cuestiones relacionadas