Чрезмерно доступные и невероятно ресурсоемкие отношения между бизнес-объектами. Как я могу это исправить? - PullRequest
5 голосов
/ 02 апреля 2010

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

У меня есть база данных с бизнес-объектами.
Мне нужно получить доступ к свойствам родительских объектов.
Мне нужно поддерживать какое-то состояние через бизнес-объекты.

Если вы посмотрите на классы, я не думаю, что модификаторы доступа являются правильными. Я не думаю, что это структурировано очень хорошо. Большинство отношений смоделированы с публичными свойствами. SubAccount.Account.User.ID <- все они общедоступны .. </p>

Есть ли лучший способ смоделировать отношения между классами, чем этот, чтобы он не был таким "публичным"?

Другая часть этого вопроса о ресурсах:

Если бы я должен был создать функцию User.GetUserList (), которая возвращает список, и у меня было 9000 пользователей, когда я вызываю метод GetUsers, он создает 9000 объектов User, а внутри него 9000 новых объектов AccountCollection. Что я могу сделать, чтобы этот проект не был так ресурсоемким?

Пожалуйста, найдите код ниже и разорвите его в клочья.

public class User {

   public string ID {get;set;}
   public string FirstName {get; set;}
   public string LastName {get; set;}
   public string PhoneNo {get; set;}

  public AccountCollection accounts {get; set;}

  public User {
     accounts = new AccountCollection(this);
  }

  public static List<Users> GetUsers() {
     return Data.GetUsers();
  }

}

public AccountCollection : IEnumerable<Account> {
  private User user;

  public AccountCollection(User user) {
     this.user = user;
  }

  public IEnumerable<Account> GetEnumerator() {
     return Data.GetAccounts(user);
  }
}


public class Account {

   public User User {get; set;}  //This is public so that the subaccount can access its Account's User's ID
   public int ID;
   public string Name;

   public Account(User user) {
      this.user = user;
   }

}

public SubAccountCollection : IEnumerable<SubAccount> {
  public Account account {get; set;}

  public SubAccountCollection(Account account) {
     this.account = account;
  }

  public IEnumerable<SubAccount> GetEnumerator() {
     return Data.GetSubAccounts(account);
  }
}


public class SubAccount {
   public Account account {get; set;}    //this is public so that my Data class can access the account, to get the account's user's ID.

   public SubAccount(Account account) {
      this.account = account;  
   }

   public Report GenerateReport() {
       Data.GetReport(this);
   }

}


public static class Data {

  public static List<Account> GetSubAccounts(Account account) {

      using (var dc = new databaseDataContext()) {
          List<SubAccount> query = (from a in dc.Accounts
                                where a.UserID == account.User.ID  //this is getting the account's user's ID
                                select new SubAccount(account) {
                                    ID = a.ID,
                                    Name = a.Name,
                                }).ToList();
      }

  }

  public static List<Account> GetAccounts(User user) {

     using (var dc = new databaseDataContext()) {
         List<Account> query = (from a in dc.Accounts
                               where a.UserID == User.ID  //this is getting the user's ID
                               select new Account(user) {
                                   ID = a.ID,
                                   Name = a.Name,
                               }).ToList();
     }
  }

  public static Report GetReport(SubAccount subAccount) {

     Report report = new Report();
     //database access code here
     //need to get the user id of the subaccount's account for data querying.
     //i've got the subaccount, but how should i get the user id.
     //i would imagine something like this:
     int accountID = subAccount.Account.User.ID;
     //but this would require the subaccount's Account property to be public.
     //i do not want this to be accessible from my other project (UI).
     //reading up on internal seems to do the trick, but within my code it still feels
     //public. I could restrict the property to read, and only private set.

     return report;
  }

  public static List<User> GetUsers() {

     using (var dc = new databaseDataContext()) {
         var query = (from u in dc.Users
                     select new User {
                       ID = u.ID,
                       FirstName = u.FirstName,
                       LastName = u.LastName,
                       PhoneNo = u.PhoneNo
                     }).ToList();

         return query;
     }
  }

}

Ответы [ 3 ]

3 голосов
/ 02 апреля 2010

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

Одиночная ответственность

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

Первый пример приведенного выше - ваш класс пользователя:

public class User { 

   public string ID {get;set;} 
   public string FirstName {get; set;} 
   public string LastName {get; set;} 
   public string PhoneNo {get; set;} 

  public AccountCollection accounts {get; set;} 

  public User { 
     accounts = new AccountCollection(this); 
  } 

  public static List<Users> GetUsers() { 
     return Data.GetUsers(); 
  } 

} 

Почему это обеспечивает метод, который извлекает пользователей из источника данных? Эту функциональность следует перенести в службу пользователей.

Другим ключевым примером этого является метод GenerateReport в SubAccount - не привязывайте логику генерации отчетов к объекту SubAccount. Отказ от этого даст вам больше гибкости и сократит изменения в вашем дополнительном аккаунте, нарушая логику отчета.

Ленивая загрузка

Снова глядя на ваш класс User - почему он загружает все учетные записи пользователей при создании экземпляра? Всегда ли эти объекты будут использоваться каждый раз, когда вы работаете с пользователем?

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

Внедрение зависимостей

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

Дайджест объектов

Шляпа для Cade Roux - я никогда не слышал термин «дайджест-объекты», обычно их называли легковесными DTO.

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

Представьте легкий пользовательский объект, в котором хранится только самая основная пользовательская информация.

Это снова еще одна причина для введения абстракции сервисов / репозитория и своего рода внедрения зависимостей. Изменение типов объектов, извлекаемых из хранилища данных, становится намного проще, когда вы инкапсулировали извлечение данных от реальных объектов и когда вы не тесно связаны с реализацией доступа к данным.

Закон Деметры

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

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

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

2 голосов
/ 02 апреля 2010

Ленивая загрузка - не создавайте объекты AccountCollection внутри Пользователей, если к ним нет доступа. В качестве альтернативы, вы можете заставить его извлекать коллекции учетных записей одновременно с пользователями и избегать 1 + 9000 обращений к базе данных.

Имеют более избирательные методы поиска коллекции (то есть, что вы собираетесь делать с 9000 пользователей? Если вам нужна вся их информация, это не такая большая трата).

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

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

0 голосов
/ 03 апреля 2010

Хорошо, просто быстрый комментарий к коду, который у вас есть на данный момент.

public class SubAccount {
   public Account account {get; set;}    //this is public so that my Data class can access the account, to get the account's user's ID.

    public SubAccount(Account account) {
        this.account = account;  
    }
    [snip]
}

Вам даже не нужен установщик для свойства Account, если он передается в ctor, а затем присваивается вспомогательному полю.

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

Редактировать: вы все еще можете использовать это поведение типа DI в конструкторе, но проблема в том, что этот конструктор не будет вызываться, когда объект повторно гидратируется из сериализованной формы (т.е. когда он был передан в / из веб-службы). Поэтому, если вы собираетесь использовать веб-сервисы, вам дополнительно понадобится конструктор без параметров, а также общедоступный или внутренний установщик в свойстве Account (если вы собираетесь использовать внутренний сеттер, вам также потребуется указать InternalsVisibleToAttribute в вашем AssemblyInfo.cs файле.

...