工厂应该设置模型属性吗?

17
作为整体S.O.L.I.D.编程努力的一部分,我在基础框架API中创建了一个工厂接口和抽象工厂。
人们已经开始过载工厂的Create方法。问题是人们正在使用模型属性来重载Create方法(因此期望工厂填充它们)。
在我看来,属性设置不应该由工厂完成。我错了吗?
public interface IFactory
{
    I Create<C, I>();
    I Create<C, I>(long id); //<--- I feel doing this is incorrect

    IFactoryTransformer Transformer { get; }
    IFactoryDataAccessor DataAccessor { get; }
    IFactoryValidator Validator { get; }
}

更新 - 对于不熟悉SOLID原则的人,以下是其中几个原则:

单一职责原则
它指出每个对象都应该有一个单一职责,并且该职责应完全封装在类中。

开闭原则
这个原则的意思是,当你收到需要添加到应用程序中的功能请求时,你应该能够处理它,而不需要修改旧的类,只需添加子类和新的实现。

依赖倒置原则
它说你应该解耦你的软件模块。为了实现这一点,你需要隔离依赖关系。

总体而言:
我90%确定我知道答案。然而,我想听听那些已经使用SOLID的人的好的讨论。谢谢你们宝贵的意见。

更新 - 那么我认为一个SOLID工厂应该做什么?

我认为一个符合SOLID原则的工厂会提供适当的对象实例...但是以一种隐藏对象实例化复杂性的方式来实现。例如,如果您有一个员工模型...您将请求工厂获取适当的员工模型。DataAccessorFactory将提供正确的数据访问对象,ValidatorFactory将提供正确的验证对象等。
例如:
var employee = Factory.Create<ExxonMobilEmployee, IEmployee>();
var dataAccessorLdap = Factory.DataAccessor.Create<LDAP, IEmployee>();
var dataAccessorSqlServer = Factory.DataAccessor.Create<SqlServer, IEmployee>();
var validator = Factory.Validator.Create<ExxonMobilEmployee, IEmployee>();

取这个例子进一步说明,我们会...
var audit = new Framework.Audit(); // Or have the factory hand it to you
var result = new Framework.Result(); // Or have the factory hand it to you

// Save your AuditInfo
audit.username = 'prisonerzero';

// Get from LDAP (example only)
employee.Id = 10;
result = dataAccessorLdap.Get(employee, audit);
employee = result.Instance; // All operations use the same Result object

// Update model    
employee.FirstName = 'Scooby'
employee.LastName = 'Doo'

// Validate
result = validator.Validate(employee);

// Save to SQL
if(result.HasErrors)
     dataAccessorSqlServer.Add(employee, audit);

更新 - 那么为什么我坚持这种分离呢?

我认为将职责分离可以使对象更小,单元测试更小,并增强可靠性和维护性。我意识到这样做会产生更多的对象成本...但这就是SOLID工厂保护我的方式...它隐藏了收集和实例化这些对象的复杂性。


3
这怎么不是建设性的? - codingbiz
请记住,我谈论的是与敏捷SOLID编程相关的实践和原则,而不是在S.O.L.I.D.出现之前人们所做的事情。 - Prisoner ZERO
1
将“将id传递给Create方法违反了哪个SOLID原则?SPR?”翻译为中文。哪个SOLID原则被破坏了?Liskov替换原则(LSP)。 - Ilya Ivanov
9
如果工厂没有填充对象,那么它会做什么?除了执行 var foo = new Foo();的操作之外?我可能错了,但我不明白你在工厂里做了什么? - ruffen
1
你能更好地定义你试图隐藏的“复杂性”吗?我发现很多开发人员将所有创造性模式归为“工厂模式”的分类。听起来你设计了一个抽象工厂,以便消费者不知道具体类型(类型为I),而你团队中的其他人则将你的工厂视为构建器或存储库(外观模式)。如果你的意图是让工厂只“new”出一个对象,你可能需要更清楚地记录或以其他方式向你的团队传达这一点。 - Joseph Yaduvanshi
显示剩余5条评论
3个回答

8
我会尽力为您翻译中文。以下是需要翻译的内容:

我认为这是坚持DRY原则,只要它是简单的值绑定,我不认为这会是问题/违规。而不是拥有

var model = this.factory.Create();
model.Id = 10;
model.Name = "X20";

在代码库中分散的对象创建和属性设置通常最好集中在一处。未来的合同更改、重构或新需求将更易处理。

值得注意的是,如果这种对象创建并立即属性设置是常见的,则这是您的团队演变出的一种模式,添加重载的开发人员只是对此事实的响应(特别是一个很好的响应)。引入API以简化此过程是应该做的。

再次强调,如果仅涉及简单赋值(如您的示例),我不会犹豫保留重载,特别是如果您经常遇到这种情况。当事情变得更加复杂时,这可能是发现新模式的信号,那么您应该采用其他标准解决方案(例如生成器模式)。


很好的评论,Jimmy!我完全同意将数据访问和模型填充部分委托给另一个对象。我也同意建造者模式可能是另一种方法(我使用我称之为DataAccessor的东西)。我尝试过将工厂“埋藏”到“构建器”中,并发现它在DataAccesser和工厂之间创建了不必要的依赖关系(为什么不将模型传递给构建器呢)。对此有何想法? - Prisoner ZERO
+1因为我总是喜欢维基百科上的GetPizza()例子......而且我同意构建器模式是@PrisonerZERO团队正在发展的方向。 - Joseph Yaduvanshi
1
@PrisonerZERO: 当然,你可以将模型传递给生成器。但是这会创建另一种模式,我相信您的开发人员很快就会注意到:"从工厂获取对象并立即将其传递给生成器" - 这是两个步骤,而不是一个步骤(我相信这就是他们通过引入重载到工厂来“抗争”的原因 - 请参见我的编辑)。此外,工厂已经依赖于DA组件了吧? - k.m
@jimmy_keen 具体工厂确实能理解其他具体实例...但在某个时间点上,有些东西必须接管。这正是具体工厂的确切目的(为给定模型“new”出所需的正确实例)。而且...我“感觉”这就是那种逻辑最好放置的地方。但我也很喜欢看看别人是怎么做的。 - Prisoner ZERO

2

假设您的工厂接口是从应用程序代码(而不是基础设施组合根)中使用的,则它实际上代表了服务定位器,这可以被视为与依赖项注入相对立的反模式。请参见服务定位器:角色与机制

请注意以下类似代码:

var employee = Factory.Create<ExxonMobilEmployee, IEmployee>();

仅仅是语法糖,它并没有消除对具体 ExxonMobilEmployee 实现的依赖。

您可能也会对弱类型与强类型消息通道以及无需服务定位的消息分发感兴趣(这些说明了这种接口如何违反SRP单一职责原则)以及Mark Seemann的其他出版物。


注意:当无法在创建实例时获得某些信息时,我会使用工厂。此类信息将通过参数传递给工厂方法。工厂本身将作为依赖项提供给消费者的 DI 容器。工厂接口可能非常简单,通常只有一个强类型方法;或者,它可以只是 Func<...> 实例。 - Roman Boiko
1
我非常喜欢每篇文章!奇怪的是,作者没有提供可行的替代代码示例。我的类很接近他提到的“Consumer<T>”或“RegisterUserConsumer”模式。因此,目前不可能创建“Send(new Foo())”。另外,除非在构造函数中传递了一个公开可用的接口,否则我不会将其他责任混入我的方法调用中。请多发一些文章!谢谢! :) - Prisoner ZERO
马克有可行的选择。我没有太多时间来筛选最好的帖子,只是挑选了我记得与某种程度上相关的内容。我鼓励阅读更多他们的帖子,其中很多都非常出色。 - Roman Boiko
“抽象工厂”正是我为DataAccessor、Validator和Transformer工厂所做的事情。不过,我还需要更深入地思考根本问题。再次感谢! - Prisoner ZERO
1
APIs像Factory.Validator.Create<ExxonMobilEmployee, IEmployee>()假设在调用此方法时可以替换任何类型。请参见Mark关于定位器和抽象工厂之间差异的帖子。另外,正如我已经提到的,您可能希望避免对ExxonMobilEmployee的静态依赖。 - Roman Boiko
显示剩余3条评论

0

在进行了大约6个月的依赖注入经验后,我只发现了一些情况,即工厂应该设置属性:

  1. 如果setter被标记为internal,并且预计属性仅由工厂设置一次。这通常发生在仅具有getter属性的接口上,其实现预计通过工厂创建。

  2. 当模型使用属性注入时。我很少看到使用属性注入的类(我也尽量避免构建这些类),但是当我确实看到一个类,并且所需服务仅在其他地方可用时,这是您别无选择的情况。

对于底线,请勿在工厂中使用public setters。只设置标记为internal的属性,如果客户端被允许这样做,则让客户端决定需要设置哪些属性。这将使您的工厂不受不必要的函数干扰。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接