面向对象编程中的单一职责原则

9
在我的应用程序设计中,我通常将对象映射到数据库中重要的表格上。 然后,对象处理与该数据相关的所有内容(包括链接表)。例如,我构建了一个Activity对象,具有属性,例如namedue_date,方法例如load()save() ,并且还有像getParent()getContributors()getTeam()这样返回其他对象(数组)的方法。这违反了单一职责原则,那么这是“糟糕”的面向对象编程吗?

1
你会如何回答“这个类负责什么?”这个问题? - soulmerge
我考虑了一下,这让我想到了一个问题。我会说:处理与活动相关的所有事情。但我不确定这是否有效 :) - Rijk
谢谢您的有趣问题。不是关于“如何对数组进行排序”的问题,这与其他许多问题不同 :) - OZ_
5个回答

5
这取决于具体的情况和你所拥有的代码:你的设计可能会涉及多个职责,但仍可以是一个非常好的面向对象编程(OOP)和可维护的设计。
你在每个类中是否使用了类似的代码处理load()save() ? 或者你是否将这些任务委托给其他用于多个类功能的对象在load()save() 中进行? 这样做可以一定程度上遵循SRP, 且符合你的设计。
如果没有,你的代码看起来确实有点臭。要检查它是否涵盖了多个职责,请问自己:什么因素会导致我的类发生变化?在这种情况下,至少应尝试将不同类中load()save()中类似的代码重构到达到上述描述的情况,以便:
  • 大大提高可维护性,
  • 仍然不需要更改客户端的代码。

1
将数据库交互方法提取到另一个类中,从而创建数据访问层。您的业务逻辑将驻留在模型中,而您的数据库交互则在其他地方进行。 - cloakedninjas
我认为load()save()的代码重用是一个很好且重要的主题,但这只是另一个问题。模型可以根据需要与数据库一起工作 - 使用代码重用(ORM,Mapper),或者不使用 - 取决于具体情况下哪个更好。在这个问题中,我认为主要的主题是与其他模型的协作、依赖关系和责任。 - OZ_
1
我认为职责、依赖关系、代码重用和可维护性都是相互关联的。你不应该只是因为SRP被认为是好的而遵循它,而是要明确为什么这样做(也就是谈论可维护性和代码重用)。 - DaveFar
2
我认为它是可维护的。几乎没有重复代码,并且我使用了一个DB抽象层。在load()方法中,我只运行(特定于类的)查询来获取数据,对文本属性(如name)中的HTML字符进行一些预处理,并将数据分配给对象的属性。 - Rijk
如果您没有重复/类似的预处理代码,那么我会说您具有良好的面向对象编程(OOP)并且已经足够遵循单一职责原则(SRP)。 SRP也不是您必须100%遵循的严格规则,而是一个指导方针。要了解这一点,您可以听听与Uncle Bob关于SOLID设计原则的辩论,这些辩论在hanselminutes和stackoverflow / stackexchange播客中都有。 - DaveFar
显示剩余3条评论

1

嗯...现在很难说。你可以将整个类粘贴到pastbin上,但是...

是的,看起来这是糟糕的面向对象编程。你有同一个类负责与数据库和领域逻辑交互。这会导致类发生两种完全不同的变化原因。

你可能会从探索DataMapper模式中受益。


我会看一下DataMapper模式,谢谢。但是为什么这个类可以进行查询以“填充自身”是如此糟糕?我知道它会使测试变得更加困难,但从设计的角度来看,有什么问题吗? - Rijk
@Rijk,一辆汽车能给自己添加一个发动机吗?这不是一个“设计”问题,而更多的是“常识”问题。 - tereško
关于汽车和引擎的错误类比。模型可以在没有DataMapper的情况下与数据库一起工作,也可以与它一起工作 - 在这两种情况下都没有问题。这只意味着我们是否能够在这种情况下使用DataMapper进行代码重用。 - OZ_
@OZ_,没有人提到任何关于模型的事情。但在这个例子中,模型将是汽车工厂(plant - 工厂的另一个词..为了避免混淆)。 - tereško
@tereško,抱歉,我仍然无法理解您的想法。也许您可以更详细地解释一下? - OZ_
@OZ_ 在聊天中问我或阅读此评论(仅将“书”替换为“汽车”)...并且我重申:该问题与模型无关。 - tereško

1

也许我只是在瞎猜(因为我不是专家),但:

领域对象内的load()和save()方法被称为Active Record另一种描述)。这并不是坏事(尽管我不喜欢它),因为那些可能在你之后或与你一起工作的人将更容易解决如何持久化这些对象的问题。

关于其他方法。如果它在对象领域中并且代表对象行为,那么情况就不会太糟糕。如果设计得好,它可以非常好。领域驱动设计鼓励使用丰富的领域模型,这与贫血的领域模型相反。贫血的领域模型具有仅具有属性和getter和setter的领域对象。因此,只要它在您的对象领域内,将其他方法放入其中并不被认为是坏事。

这是我从阅读的书籍和文章中理解这些概念的程度。

希望能有所帮助..


谢谢!您能分享一些有趣的书籍和文章链接吗? - Rijk
最佳DDD书籍,贫血领域模型,领域模型... - Matjaz Muhic

1

你所描述的是ActiveRecord,众所周知它违反了SRP原则。此外,只有当表行与对象紧密匹配时,ActiveRecord才能正常工作。一旦阻抗失配过大,将使系统更改变得更加困难。

这不一定是坏的面向对象编程,但由于持久性逻辑和领域逻辑之间缺乏分离,因此它是技术债务的一种形式。违反任何SOLID原则通常会导致难以更改的代码、脆弱的代码和不可重用的代码。

其中一些债务并不是问题。当这些债务开始积累利息,例如开始影响其他设计决策时,就会成为问题。换句话说,当您注意到更改系统变得更加困难时,请尝试偿还一些债务,例如重构为更易维护的解决方案。


Martin Fowler真的被引用了很多次 :) - Rijk
@Rijk 嗯,他写了很多有意义的东西。 - Gordon

0

我认为停止认为模型只应该是逻辑和数据库之间的层是很重要的。模型可以与数据库和其他模型一起工作,所有的逻辑都应该在模型中。
我认为有两种方法:

  1. 您的模型可以在getContributors()方法中返回ID数组,然后您可以创建一个新对象(可能是工厂),将这些ID转换为对象。
  2. 您的模型可以返回对象数组,但不使用new关键字,而是通过工厂或依赖容器(我更喜欢DC)。

你到底在哪里看到模型了?!这里没有人认为模型是逻辑和数据库之间的层。有些人只是想把逻辑和 SQL 放在一个地方,因此使用活动记录(反)模式。 - tereško
@tereško,将逻辑和SQL放在一个地方是没有问题的。不要太紧张了。 - OZ_
这意味着,如果数据库结构或类的逻辑发生变化,该类将不得不更改..从而破坏SRP(这实际上是主题所在)。 - tereško
数据库是存储对象数据的地方,因此数据库结构应该反映它,并且只有在对象更改时才应进行更改。 - OZ_

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