5

首先,这似乎是一个很长的问题。我不认为它是......代码只是我目前正在做的事情的概述。感觉不对,所以我正在寻找建设性的批评和警告,以了解我可以做什么的陷阱和建议。

我有一个包含业务对象的数据库。
我需要访问父对象的属性。
我需要通过业务对象维护某种状态。

如果您查看这些类,我认为访问修饰符不正确。我不认为它的结构很好。大多数关系都使用公共属性建模。SubAccount.Account.User.ID <--所有这些都是公开的..

有没有比这更好的方法来模拟类之间的关系,所以它不是那么“公开”?

这个问题的另一部分是关于资源:

如果我要创建一个返回列表的 User.GetUserList() 函数,并且我有 9000 个用户,那么当我调用 GetUsers 方法时,它将创建 9000 个用户对象,并且在其中将创建 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;
     }
  }

}
4

3 回答 3

3

这个答案最终包含了很多流行语标题。希望我能解释每一个以及为什么它适用于此。我认为我在下面介绍的每个概念都值得考虑——它们并不总是适用,但我发现它们都是我个人在考虑系统结构时认为有价值的东西。

单一职责

首先考虑每个对象的责任——它的工作是什么?通常,一旦您为每个班级确定了一项工作,您就会找到更好的设计。目前你的很多类都做的太多了,持有应该作为服务真正存在的逻辑。

上面的第一个示例是您的 User 类:

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

} 

为什么这提供了一种从数据源中检索用户的方法?该功能应该移出到用户服务中。

另一个关键示例是 SubAccount 上的 GenerateReport 方法 - 不要让您的报告生成逻辑与 SubAccount 对象紧密相关。将其拆分将为您提供更大的灵活性并减少对您的子帐户的更改更改,从而破坏报告逻辑。

延迟加载

再次查看您的 User 类 - 为什么它会在实例化时加载所有用户帐户?每次与用户一起工作时,是否总是会使用这些对象?

引入延迟加载通常会更好 - 仅在需要时检索帐户。当然,有时您需要预先加载(如果您知道您将很快需要该对象,因此希望减少数据库访问),但您应该能够针对这些异常进行设计。

依赖注入

这种从延迟加载点和单一责任点开始。你有很多硬编码的引用,比如你的 Data 类。这使您的设计更加僵化 - 重构您的数据访问以引入延迟加载,或者更改用户记录的检索方式更加困难,因为许多类都直接访问数据访问逻辑。

摘要对象

给 Cade Roux 的小贴士——我从来没有听说过 Digest 对象这个词,通常称它们为轻量级 DTO。

正如 Cade 所说,如果您所做的只是显示绑定到唯一 ID 的用户名组合框,则没有理由检索包含功能齐全的用户对象的丰富列表。

引入一个只存储非常基本的用户信息的轻量级用户对象。

这又是引入服务/存储库抽象和某种依赖注入的另一个原因。当您将数据检索与实际对象封装在一起,并且没有与数据访问实现紧密绑定时,更改从数据存储中检索的对象类型会变得容易得多。

得墨忒耳法则

您的对象对彼此的内部结构了解太多。允许从用户向下钻取到帐户然后到子帐户会混淆每个对象的责任。您可以从用户对象设置子帐户信息,而您可能不应该这样做。

我总是与这个原则斗争,因为钻探层次结构似乎很方便。问题是它会阻止你对每个对象的封装和角色进行深入思考。

也许不要向用户公开您的 Account 对象 - 而是尝试引入公开 Account 对象相关成员的属性和方法。查看 GetAccount 方法而不是 Account 属性,这样您就可以强迫自己使用帐户对象,而不是将其视为 User 的属性。

于 2010-04-02T03:55:10.403 回答
2

延迟加载 - 不要在用户中创建 AccountCollection 对象,除非它被访问。或者,您可以让它与用户同时检索帐户集合,并避免 1 + 9000 次数据库访问。

有更多选择性的集合检索方法(即你打算对 9000 个用户做什么?如果你需要他们的所有信息,这不是什么浪费)。

具有较小的“摘要”对象,用于下拉列表和查找等长列表 - 这些对象是简单的只读业务对象 - 通常只包含少量信息,可用于检索完全爆炸的对象。

如果要进行分页,是用户列表加载一次并存储在 Web 服务器中,然后从缓存副本中分页,还是每次从数据库加载集合并由控件完成分页?

于 2010-04-02T03:24:20.643 回答
0

好的,只是对您目前拥有的代码进行快速评论。

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 上传递属性来设置属性可能是一个非常好的做法,但是如果您的数据对象要被序列化,即如果它被检索或发送到 Web 服务,它就会失败 - 在这种情况下,您将至少需要该属性的internal设置器。Account

编辑: 您仍然可以在构造函数上使用此 DI 类型行为,但问题是当对象从序列化形式重新水化时(即,当它被传递到/从 Web 服务传递时)不会调用此构造函数。因此,如果您要使用 Web 服务,您还需要有一个无参数的构造函数,以及 Account 属性上的公共或内部设置器(如果您要使用内部设置器,那么您还需要InternalsVisibleToAttribute在您的AssemblyInfo中指定.cs文件。

于 2010-04-02T23:17:00.273 回答