五年多来,Martin Fowler在他著名的TellDontAsk文章中指出了面向对象编程中的最大问题之一。 在他的著作中,他提醒程序员,他们应该信任自己的对象来为他们执行工作,而不是要求对象提供以后可以使用的数据。
我非常同意这一点,但是,当然,仅凭此原则并不能保证我们的代码是面向对象的。 我认为仅仅依靠一个对象来完成工作是不够的–需要做更多的设计工作,以确保该对象以后不会引起程序代码。
让我们来看一个例子:
/*** All employees of a company.* You can hire more, fire some of them, give them a raise etc.\*/
public final class AllEmployees implements Employees {//constructor//other methods from Employees interface (hire, fire, raise etc)@Overridepublic List<Employee> filter(final Map<String, String> skills) {//return the List of those who have the specified skills.}}
上面的类将创建一个尊重福勒先生原则的适当对象:它将照顾员工,甚至会为我们筛选员工,而不会提出任何问题。 但是,这可能会导致周围的损坏,原因如下:执行过滤后,我们将剩下一个可以区分所有人的List
!
那些被筛选的员工永远都不会加薪吗? 他们将永远不会被解雇,还是我们永远不会雇用具有相同技能的人(相同的过滤器)? 当然,我们仍然会愿意加薪,解雇或雇用类似的人,但是我们现在已经不在上下文中了,我们手中只有一个愚蠢的List
:为了使清单上的员工享有相同的权利,并且其余的义务一样,我们将需要编写过程代码(也许很多代码)。
我认为应该这样做:我们应该添加一个新的Employees
实现,称为FilteredEmployees
,该实现会将Map放入其构造函数中,并确保它仅处理具有我们所要求技能的员工。 这样,他们仍然在同一家公司工作,除了现在我们更加了解他们,我们知道他们拥有一些别人没有的技能以外,没有任何改变。 我们不必编写代码来处理或转换 List
,我们仍将有一个Employees
实例。 现在我们的班级看起来像这样:
/*** All employees of a company.* You can hire more, fire some of them, give them a raise etc.\*/
public final class AllEmployees implements Employees {//constructor//other methods from Employees interface (hire, fire, raise etc)@Overridepublic Employees filter(final Map<String, String> skills) {return new FilteredEmployees(..., skills);}}
我想说的是,这种想法本身就是试图实现这种情况,而不是告诉一个对象将您引向上述情况。 也就是说,我们实现了这些已过滤的员工,因为原始对象在维护上下文的同时无法为我们执行过滤。 简单地告诉目标对象去做就会使我们陷入相同的境地(与具有特定技能的人一起工作),但是这些人不再是雇员 ,而只是列表。
我将所有这些视为对TellDontAsk原理的扩展。 我不确定如何确保您朝正确的方向前进。 但是,我认为JDK(或您使用的任何开发套件)的使用是一个很好的指示: 在面向对象的代码库中,该套件应尽可能离散 。 使用开发套件的次数越多,您的代码实际面向对象的次数就越少,或者您的抽象并不是最好的。 另一方面,仅通过使用现有对象(或添加现有接口的新实现)就能添加/修改/删除功能越多,则应用程序的面向对象越多。
PS 这是同一想法的另一个示例。
翻译自: https://www.javacodegeeks.com/2018/11/extension-telldontask.html