Overly accessible and incredibly resource hungry relationships between business objects.我该如何解决这个问题?

发布于 2024-08-27 19:58:15 字数 4117 浏览 3 评论 0原文

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

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

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

是否有比这更好的方法来建模类之间的关系,使其不那么“公共”?

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

如果我要创建一个返回列表的 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;
     }
  }

}

Firstly, This might seem like a long question. I don't think it is... The code is just an overview of what I'm currently doing. It doesn't feel right, so I am looking for constructive criticism and warnings for pitfalls and suggestions of what I can do.

I have a database with business objects.
I need to access properties of parent objects.
I need to maintain some sort of state through business objects.

If you look at the classes, I don't think that the access modifiers are right. I don't think its structured very well. Most of the relationships are modelled with public properties. SubAccount.Account.User.ID <-- all of those are public..

Is there a better way to model a relationship between classes than this so it's not so "public"?

The other part of this question is about resources:

If I was to make a User.GetUserList() function that returns a List, and I had 9000 users, when I call the GetUsers method, it will make 9000 User objects and inside that it will make 9000 new AccountCollection objects. What can I do to make this project not so resource hungry?

Please find the code below and rip it to shreds.

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

}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

芯好空 2024-09-03 19:58:15

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

单一职责

首先考虑每个对象的职责 - 它的工作是什么?一般来说,一旦您决定为每个类别指定一个作业,您就会找到更好的设计。目前,您的许多类做得太多了,保留了真正应该作为服务存在的逻辑。

上面的第一个示例是您的 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 类 - 为什么它在实例化时加载所有用户帐户?每次与用户一起工作时是否总是会使用这些对象?

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

依赖注入

这种类型是从延迟加载点和单一责任点开始的。您有很多对数据类等内容的硬编码引用。这使得您的设计更加严格 - 重构数据访问以引入延迟加载,或者更改用户记录的检索方式变得更加困难,因为许多类都直接访问数据访问逻辑。

摘要对象

向 Cade Roux 致敬 - 我从未听说过“摘要对象”这个术语,通常将其称为轻量级 DTO。

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

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

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

得墨忒耳定律

你的物体对彼此的内部结构了解太多。允许从用户深入到帐户,然后再深入到子帐户会混淆每个对象的职责。您可以从用户对象设置子帐户信息,但实际上您不应该这样做。

我总是在这个原则上挣扎,因为钻探层次结构似乎很方便。问题是它会阻止你仔细思考每个对象的封装和角色。

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

This answer has ended up containing a lot of buzz word headings. Hopefully I explain each one and why it applies here. I think each concept I introduce below is worth considering - they aren't always applicable but I find they are all things I personally find valuable when I think about the structure of a system.

Single Responsibility

Start by thinking about the responsibility of each object - what is its job? Generally you'll find a better design once you decide on a single job for each class. Currently a lot of your classes are doing too much, holding logic that should really exist as services.

The first example of the above is your User class:

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

} 

Why does this provide a method that retrieves users from the data source? That functionality should be moved out into a users service.

Another key example of this is the GenerateReport method on the SubAccount - don't have your report generation logic so tightly tied to the SubAccount object. Splitting this out will give you more flexibility and reduce the change of changes to your SubAccount breaking the report logic.

Lazy Loading

Again looking at your User class - why does it load all the users accounts on instantiation? Are these objects always going to be used every time you work with a User?

It would generally be better to introduce lazy loading - only retrieve an account when you need it. Of course there are times when you want eager loading (if you know you will want the object soon so want ot reduce database access) but you should be able to design for these exceptions.

Dependency Injection

This sort of follows on from both the lazy loading point and the single responsibility point. You have a lot of hard coded references to things like your Data class. This is making your design more rigid - refactoring your data access to introduce lazy loading, or changing the way that user records is retrieve is much harder now that many classes are all directly accessing the data access logic.

Digest objects

Hat tip to Cade Roux - I'd never heard the term Digest objects, usually called them light weight DTOs.

As Cade says, there is no reason to retrieve a rich list containing fully functioning user objects if all you are doing is displaying a combo box of user names that is bound to the unique ids.

Introduce a light weight user object that only stores the very basic user information.

This is again another reason to introduce a services/repository abstraction, and some sort of dependency injection. Changing the types of objects retreived from the data store becomes much easier when you have encapsulated your data retrieval away from your actual objects, and when you are not tightly bound to your data access implementation.

Law of Demeter

Your objects know too much about the internal structure of each other. Allowing drill down through a User to the Accounts and then to the SubAccount is muddling the responsibility of each object. You are able to set sub account information from the user object when arguably you shouldn't be.

I always struggle with this principle, since drilling through the heirarchy seems very convenient. The problem is that it will stop you thinking proberly about the encapsulation and role of each object.

Perhaps don't expose your Account object from user - instead try introducing properties and methods that expose the relevant members of the Account object. Look at a GetAccount method instead of a Account property, so that you force yourself to work with an account object rather than treat it as a property of the User.

爱殇璃 2024-09-03 19:58:15

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

拥有更具选择性的集合检索方法(即您要对 9000 个用户做什么?如果您需要他们的所有信息,那也不算浪费)。

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

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

Lazy loading - do not make the AccountCollection objects inside the Users unless it is accessed. Alternatively, you can have it retrieve the account collections at the same time as the users and avoid 1 + 9000 database trips.

Have more selective collection retrieval methods (i.e. what are you going to do with 9000 users? If you need all their information, it's not so much of a waste).

Have smaller "digest" objects which are used for long lists like dropdowns and lookups - these objects are simple read-only business objects - usually containing only a small amout of information and which can be used to retrieve fully blown objects.

If you are doing pagination, is the list of users loaded once and stored in the web server and then paginated from the cached copy, or is the collection loaded every time from the database and pagination done by the control?

避讳 2024-09-03 19:58:15

好的,现在就对代码进行快速评论。

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 上,然后将其分配给支持字段,则您甚至不需要在该属性上设置 setter。

通过将属性传递给 ctor 来设置属性可能是一个非常好的实践,但是如果您的数据对象要被序列化,即如果它被检索或发送到 Web 服务,那么它就会失败 - 在这种情况下,您将Account 属性至少需要一个内部 setter。

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

Okay, just a quick comment on the code as you have it at the moment.

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

You don't even need a setter on the Account property if it is passed in on the ctor and then assigned to the backing field.

Setting a property by passing it on on the ctor can be a very good practice to follow, but it falls over if your data object is going to be serialized, i.e. if it is retrieved or sent to a web service - in this case you will need at least an internal setter on the Account property.

Edit: you can still use this DI type behaviour on the constructor, but the problem is that this constructor will not get called when the object is rehydrated from a serialized form (i.e. when it has been passed to/from a web service). So if you are going to use web services you will additionally need to have a parameterless constructor, and either a public or internal setter on the Account property (if you are going for an internal setter then you will also need to specify the InternalsVisibleToAttribute in your AssemblyInfo.cs file.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文