2012-05-08 3 views
7

Я пишу объект, который должен всегда иметь определенные значения. Прежде всего, он должен всегда иметь значение для свойства Name.Как я могу прервать инициализацию объекта?

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

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

Теперь есть несколько бизнес-правил, которые мне необходимо реализовать в этом классе. Одним из них является то, что свойство Name должно быть уникальным именем. Так что, я думаю, что инициализатор для этого объекта будет выглядеть это:

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

Но я не знаю, как я бы прервать процесс инициализации объекта. На самом деле это возможно?

Есть ли способ прервать инициализацию объекта (то есть: установить объект в null) или есть лучший способ выполнить это?

+0

методом грубой силы, установите все поля в нуль, добавить объект с нулевыми полями, удалите объект – RhysW

+2

Это может быть просто пример кода, но в случае, если это не так: Вы можете поставить предикат 'Любой ', поэтому вам не нужно проходить' Where'. –

+0

@BrianRasmussen Оба имеют тот же результат, поэтому это личное предпочтение, которое вы используете. С другой стороны, вызов «ToList» предотвращает короткое замыкание «Any'», не оценивая весь запрос. – Servy

ответ

3

Отмена инициализации объекта выполняется путем исключения исключения в конструкторе и рекомендуется отклонять недопустимый ввод.

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 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 
    } 
} 

Вы можете увидеть, что шаблон завод лучше подходит, и потому, что это метод вызывающий абонент может ожидать там быть какой-то работа происходит путем вызова его. В то время как с конструктором можно было бы ожидать, что он будет легким.

Если вы хотите перейти по пути к конструктору, попробуйте другой метод обеспечения соблюдения ваших бизнес-правил, например, при попытке фактической вставки в источник данных.

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

Большое спасибо за такой подробный ответ! Я был убежден, что вы отправили маршрут Factory Pattern, который вы указали. – quakkels

+0

Ты очень рад, я рад, что смогу помочь. –

4

Я полагаю, вы можете проверить и выбросить исключение в конструкторе объекта или в установщике имен, но eeeehhhh, который может содержать множество проблем и смешанных проблем. Я говорю создать объект через фабрику, которая выполняет эту проверку, и возвращает значение null (или красиво названное исключение). Или создайте объект POCO и выполните проверку с помощью отдельного класса/метода.

2

Прежде чем создавать пользователя, вам следует проверить дублирующее имя.

+1

Вы можете получить гонку. – jason

+0

Вы всегда можете получить условия гонки :-) – Steven

6

Ну, вы просто выбросите исключение. Но мне не нравится этот способ решения этой проблемы. Скорее, вы должны создать пользователя через службу и проверить службу, если имя действительно или нет.

2

Лично я запускаю логические проверки до того, как я создаю экземпляр. Например:

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

PreInsertValidation бы все бизнес-логики проверки, основанные на ваших требованиях.

0

Что вы ищете, это шаблон identity map, или что-то в этом роде. Вероятно, неправильно оставить эту ответственность в самом объекте, это должно быть сделано в компоненте, создающем сущности. конечно, карта shoul может быть потокобезопасной, если это необходимо, во избежание условий гонки.

0

Я бы справился с этим в фиксации наличия пользователя в коллекции. Например, если вы редактировали коллекцию пользователей, а затем сохраняли их в базе данных, то уровень достоверности будет отвечать за проверку. Я всегда считал, что плохая практика заключается в том, чтобы объект был ответственным за поддержание всех других объектов, подобных ему. Он вводит родительское дочернее отношение с самим объектом, когда его нет. Я предлагаю внедрить механизм проверки, чтобы справиться с этим.

3

Вместо того, чтобы иметь открытый конструктор, есть метод, как это и частный конструктор:

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; 
} 

Затем ваш вызывающий код может использовать User myUser = User.CreateUser("Steve"); и обрабатывать нулевой обратный/исключение соответственно.

Следует добавить, что любой метод, который вы используете, который хранит имена пользователей, должен быть обновлен, чтобы сказать, что это имя принято в методе CreateUser. В противном случае, если вы подождите некоторое время, прежде чем хранить этот объект в базе данных или что-то еще, у вас все еще будут проблемы. Я обновил код выше, чтобы сделать это более понятным.

+1

Таким образом вы можете использовать статический 'IQueryable', чтобы удерживать всех пользователей (или ссылку на репозиторий всех пользователей) и избегать условий гонки с блокировкой. – SWeko

+0

@SWeko Оба метода и использование конструктора имеют условия гонки, если нет замков, и оба могут удалить условия гонки, добавив соответствующие блокировки. – Servy

0

Вместо того, чтобы выполнять эту проверку внутри самого объекта, поместите создание, проверку и сохранение этого объекта в службе. Эта служба может вызывать ValidationException, когда имя пользователя не является уникальным, и даже может начать транзакцию, чтобы гарантировать, что никакое состояние гонки не может произойти. Хорошей моделью, которую я использую, является модель command/handler. Вот пример:

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); 
    } 
} 

Вы даже можете добавить проверку в свой класс.

Смежные вопросы