2012-05-08 15 views
7

Estoy escribiendo un objeto que siempre debe tener ciertos valores. En particular, siempre debe tener un valor para la propiedad Name.¿Cómo puedo abortar una inicialización de objeto?

public class User 
{ 
    public string Name { get; set; } 

    public User(string name) 
    { 
     Name = name; 
    } 
} 

Ahora, hay un par de reglas de negocio que tengo que poner en práctica en esta clase. Una de ellas es que la propiedad Name debe ser un nombre único. Por lo tanto, yo creo que el inicializador para este objeto sería algo esto:

public User(string name, IQueryable<User> allUsers) 
    { 
     var matches = allUsers.Where(q => q.Name == name).ToList(); 
     if(matches.Any()) 
     { 
      // abort object initialization 
     } 
     Name = name; 
    } 

Pero no estoy seguro de cómo iba a abortar la inicialización de objetos. De hecho, ¿es eso posible?

¿Hay alguna manera de abortar una inicialización de objeto (es decir, establecer el objeto como nulo) o hay una forma mejor de lograr esto?

+0

una forma de fuerza bruta, establezca todos los campos en nulo, añadir el objeto con campos nulos, eliminar el objeto – RhysW

+2

esto sólo puede ser código de ejemplo, pero en caso de que no lo es: Se puede suministrar un predicado a 'Cualquier ', para que no tengas que pasar por' Donde'. –

+0

@BrianRasmussen Ambos tienen exactamente el mismo resultado, así que es una preferencia realmente personal la que usa. La llamada 'ToList' por otro lado evita que el cortocircuito de' Any' no evalúe toda la consulta. – Servy

Respuesta

3

Abortando inicialización de un objeto se realiza por lanzar una excepción en el constructor, y se recomienda para rechazar de entrada válido.

public class User 
{ 
    public User(String name) { 
     if (String.IsNullOrWhiteSpace(name)) { 
      if (name == null) { 
       throw new System.ArgumentNullException("Cannot be null.", "name"); 
      } 
      else { 
       throw new System.ArgumentException("Cannot be empty.", "name"); 
      } 
     } 
    } 
} 

La lógica de negocio que desea definir en el constructor no encaja allí. Los constructores deben ser livianos y crear instancias solamente. Consultar un origen de datos es demasiado costoso para un constructor. Debido a esto, debe usar el patrón de fábrica en su lugar. Con el patrón de fábrica, una persona que llama puede esperar que haya algún trabajo pesado relacionado con la creación de objetos.

public class User 
{ 
    private User(String name) { 
     if (String.IsNullOrWhiteSpace(name)) { 
      if (name == null) { 
       throw new System.ArgumentNullException("Cannot be null.", "name"); 
      } 
      else { 
       throw new System.ArgumentException("Cannot be empty.", "name"); 
      } 
     } 
    } 

    public static User CreateUser(String name) { 
     User user = new User(name); // Lightweight instantiation, basic validation 

     var matches = allUsers.Where(q => q.Name == name).ToList(); 

     if(matches.Any())   
     {   
      throw new System.ArgumentException("User with the specified name already exists.", "name");   
     }  

     Name = name; 
    } 

    public String Name { 
     get; 
     private set; // Optionally public if needed 
    } 
} 

se puede ver que el patrón de la fábrica se ajusta mejor, y porque es un método que llama podría esperar que haya algunas obras en invocando él. Mientras que con un constructor uno esperaría que sea liviano.

Si quería ir a la ruta constructor, entonces usted le gustaría probar algún otro método de hacer cumplir las reglas de negocio, tales como cuando se intenta la inserción real en la fuente de datos.

public class User 
{ 
    public User(String name) { 
     if (String.IsNullOrWhiteSpace(name)) { 
      if (name == null) { 
       throw new System.ArgumentNullException("Cannot be null.", "name"); 
      } 
      else { 
       throw new System.ArgumentException("Cannot be empty.", "name"); 
      } 
     } 
    } 
} 

public class SomeDataSource { 
    public void AddUser(User user) { 
     // Do your business validation here, and either throw or possibly return a value 
     // If business rules pass, then add the user 
     Users.Add(user); 
    } 
} 
+0

¡Muchas gracias por una respuesta tan detallada! Me persuadieron para ir a la ruta del patrón de fábrica que usted delineó. – quakkels

+0

De nada, me alegro de haber podido ayudar. –

4

Supongo que podría verificar y lanzar una excepción en el constructor del objeto o en el creador del nombre, pero eeeehhhh puede venir con una serie de problemas y preocupaciones mixtas. Digo crear el objeto a través de una fábrica que hace esta comprobación y devuelve nulo (o una excepción bien nombrada). O crea el objeto POCO y haz la validación a través de una clase/método separado.

2

Probablemente debería comprobar el nombre duplicado antes de crear el usuario.

+1

Puede obtener una condición de carrera. – jason

+0

Siempre se puede obtener condiciones de carrera :-) – Steven

6

Bueno, usted lanzaría una excepción. Pero no me gusta esta forma de manejar este problema en absoluto. Por el contrario, debe crear el usuario a través del servicio y hacer que el servicio compruebe si el nombre es válido o no.

2

Personalmente, ejecuto verificaciones lógicas antes de instanciar. Por ejemplo:

if(UserLogic.PreInsertValidation(string username)){ 
    User newUser = new User(username); 
} 
else{ 
    // Handling - maybe show message on client "The username is already in use." 
} 

El PreInsertValidation tendría todas las comprobaciones de la lógica de negocio en base a sus necesidades.

0

Lo que está buscando es el patrón identity map, o algo así. Probablemente sea incorrecto dejar esta responsabilidad en el objeto mismo, debe hacerse en el componente que crea las entidades. por supuesto, el mapa debería estar seguro si es necesario, para evitar condiciones de carrera.

0

Me encargaría de esto en el compromiso de tener al usuario en la colección. Por ejemplo, si está editando una colección de usuarios y luego los persiste en la base de datos, la capa de persistencia será responsable de la validación. Siempre consideré que es una mala práctica tener un objeto que sea responsable de mantener todos los demás objetos como este. Introduce una relación padre-hijo con el objeto en sí cuando no lo hay. Sugiero implementar un motor de validación de algunos tipos para manejar esto.

3

lugar de tener un constructor público, tienen un método como este y un constructor privado:

public static User CreateUser(string name) 
{ 
     // Check whether user already exists, if so, throw exception/return null 

     // If name didn't exist, record that this user name is now taken. 
     // Construct and return the user object 
     return new User(name); 
} 

private User(string name) 
{ 
     this.Name = name; 
} 

A continuación, el código de llamada podría utilizar User myUser = User.CreateUser("Steve"); y manejar nula rentabilidad/excepción en consecuencia.

Vale la pena agregar que cualquiera que sea el método que esté utilizando y que almacene los nombres de usuario que se toman debe actualizarse para decir que este nombre se toma dentro del método CreateUser. De lo contrario, si espera un poco antes de almacenar este objeto en una base de datos o algo así, aún tendrá problemas. He actualizado el código anterior para que quede más claro.

+1

De esta manera se puede utilizar una estructura estática 'IQueryable' para contener todos los usuarios (o una referencia a un repositorio de todos los usuarios) y evitar las condiciones de carrera con bloqueo. – SWeko

+0

@SWeko Tanto este método como el uso de un constructor tienen condiciones de carrera si no hay bloqueos, y ambos pueden eliminar las condiciones de carrera agregando los bloqueos apropiados. – Servy

0

En vez de hacer esta validación dentro del objeto en sí mismo, poner la creación, validación y ahorro de esta entidad en un servicio. Este servicio puede arrojar un ValidationException cuando el nombre de usuario no es único, e incluso podría comenzar una transacción para garantizar que no exista una condición de carrera. Un buen modelo que uso es el patrón command/handler. He aquí un ejemplo:

public class CreateNewUserCommand 
{ 
    public string UserName { get; set; } 
} 

internal class CreateNewUserCommandHandler 
    : ICommandHandler<CreateNewUserCommand> 
{ 
    private readonly IUnitOfWork uow; 

    public CreateNewUserCommandHandler(
     IUnitOfWork uow) 
    { 
     this.uow = uow; 
    } 

    public void Handle(CreateNewUserCommand command) 
    { 
     // TODO Validation 

     var user = new User { Name = command.Name }; 

     this.uow.Users.InsertOnSubmit(user); 
    } 
} 

Usted podría incluso la validación adicional en su propia clase.

Cuestiones relacionadas