我刚刚在
之前审核了一些看起来像这样的代码public class ProductChecker
{
// some std stuff
public ProductChecker(int AccountNumber)
{
var account = new AccountPersonalDetails(AccountNumber);
//Get some info from account and populate class fields
}
public bool ProductACriteriaPassed()
{
//return some criteria based on stuff in account class
//but now accessible in private fields
}
}
现在已经添加了一些额外的标准,这些标准需要不在AccountPersonalDetails类中的数据
新代码看起来像这样
public class ProductChecker
{
// some std stuff
public ProductChecker(int AccountNumber)
{
var account = new AccountPersonalDetails(AccountNumber);
var otherinfo = getOtherInfo(AccountNumber)
//Get some info from account and populate class fields
}
public bool ProductACriteriaPassed()
{
//return some criteria based on stuff in account class
// but now accessible in private fields and other info
}
public otherinfo getOtherInfo(int AccountNumber)
{
//DIRECT CALL TO DB TO GET OTHERINFO
}
}
我对数据库部分感到困扰,但人们可以告诉我为什么这是错误的?或者是吗?
答案 0 :(得分:6)
在系统的分层视图中,看起来ProductChecker
属于业务规则/业务逻辑层,因此它不应该被“污染”用户交互功能(属于图层上面)或 - 这与您的案例密切相关 - 存储功能(属于下面的图层 >)。
“其他信息”应该封装在自己的存储层类中,该类应该是处理持久化/检索功能的类(就像我想象AccountPersonalDetails
正在为自己的东西做的那样)。 “个人详细信息”和“其他信息”最好是作为单独的课程保存还是加入一个我无法从所提供的信息中分辨出来,但该选项应该经过严格考虑并仔细权衡。
保持图层分离的经验法则有时可能会感觉很僵硬,并且通常很容易通过图层的错误来快速添加功能 - 但是为了保持系统的可维护性和清洁,我会做每当出现这样的设计问题时,总会争论层分离。在OOP术语中,它表示“强大的凝聚力,但弱耦合”;但在某种意义上它比OOP更基础,因为它也适用于其他编程范例及其混合! - )
答案 1 :(得分:1)
似乎getOtherInfo
中抓取的额外数据应该封装为AccountPersonalDetails
类的一部分,因此在创建{时,已经是构造函数中account
变量的一部分{1}}对象。您将new AccountPersonalDetails
传递给两者,为什么不让AccountNumber
收集所有您需要的信息?那么你就不必像现在这样在外部处理额外的东西了。
答案 2 :(得分:1)
看起来这个类的设计肯定会出现问题......但如果不了解应用程序的完整架构,很难说清楚。
首先,如果OtherInfo对象属于Account而不是您正在检查的产品...它会向您的班级引入不应该存在的责任。
其次,如果您有数据访问层......那么ProductChecker类应该使用数据访问层从数据库中检索数据,而不是直接调用以检索所需的数据。
第三,我不确定GetOtherInfo方法是否需要公开。它看起来像是应该只在你的类内部使用的东西(事实上,它实际上属于那里开始)。在这种情况下,您也不需要传递accountId(您的类应该已经存在于某处)。
但是......如果OtherInfo与您正在检查的产品有关并且您没有真正的数据访问层,那么我可以看到这可能是一个有效的设计。
尽管如此,我仍然站在你一边。我不喜欢它。
答案 3 :(得分:0)
考虑到将accountNumber传递给构造函数,您不必将其传递给另一个类似的方法。
答案 4 :(得分:0)
几点
getOtherInfo()
看起来像是AccountPersonalDetails
的责任,所以应该在那个班级AccountPersonalDetails
而不是使用构造函数getOtherInfo()
也可能与此重构有关,因此数据库逻辑不是嵌入在域对象中,而是嵌入在服务类(Façade/ Repository)中ProductACriteriaPassed()
位于正确的位置答案 5 :(得分:0)
我建议这样做:
public class AccountPersonalDetails
{
public OtherInfo OtherInfo { get; private set; }
}
public class ProductChecker
{
public ProductChecker(AccountPersonalDetails) {}
}
// and here's the important piece
public class EitherServiceOrRepository
{
public static AccountPersonalDetails GetAccountDetailsByNumber(int accountNumber)
{
// access db here
}
// you may also feel like a bit more convinience via helpers
// this may be inside ProductCheckerService, though
public static ProductChecker GetProductChecker(int accountNumber)
{
return new ProductChecker(GetAccountDetailsByNumber(accountNumber));
}
}
我不是域驱动设计的专家,但我相信这就是DDD的意义所在。您可以保持逻辑清除数据库关注点,将其移至外部服务/存储库。如果有人纠错我会很高兴。
答案 6 :(得分:0)
为每个帐号创建一个新的类实例“感觉”很笨拙。构造函数参数应该是类正常运行所必需的。它是类的参数,而不是依赖项。它导致在构造函数中做很多工作的诱惑。该类的用法应如下所示:
result = new ProductChecker().ProductACriteriaPassed(accountNumber)
我会快速重命名以表明它确实有用。
result = new ProductChecker().PassesProductACriteria(accountNumber)
其他一些人提到您可能想要拆分数据库逻辑。如果你想要快速的单元测试,你想要这样做。大多数程序都需要单元测试(除非你只是玩游戏),如果它们很快就会更好。当您可以将数据库排除在外时,它们很快。
让我们创建一个表示数据库结果的虚拟对象,并将其传递给确定产品是否通过的方法。如果不是为了证明,这将是私人的。可测试性获胜。假设我想验证一条规则,例如“如果帐号为素数,则产品必须为绿色”。这种单元测试方法在没有基础设施的情况下运行良好。
// Maybe this is just a number of items.
DataRequiredToEvaluateProduct data = // Fill in data
// Yes, the next method call could be static.
result = new ProductChecker().CheckCriteria(accountNumber, data)
// Assert result
现在我们需要连接数据库。数据库是一个依赖项,它是类正常运行所必需的。它应该在构造函数中提供。
public class ProductRepository {} // Define data access here.
// Use the ProductChecker as follows.
result = new ProductChecker(new ProductRepository()).CheckCriteria(accountNumber)
如果构造函数变得非常冗长(可能必须读取配置文件以查找数据库),请创建一个工厂来为您排序。
result = ProductCheckerFactory().GimmeProductChecker().CheckCriteria(accountNumber)
到目前为止,我还没有使用任何基础设施代码。通常情况下,我们使用模拟和依赖注入使上述更容易和更漂亮(我使用rhinomocks和autofac)。我不会进入那个。如果你已经有了它,那就更容易了。