OO上的JavaWorld:Getters / Setters vs Builder

背景:

我在JavaWorld上发现了这篇文章 ,其中Allen Holub解释了Getters / Setters的替代方法,它保留了隐藏对象实现的原则(他的示例代码也可以在下面找到)。

它解释了类Name / EmployeeId / Money应该有一个构造函数采用单个字符串 – 原因是如果你键入它作为int ,后来需要将它更改为long ,你将不得不修改所有用途这个模式,你没有必要。

问题1:

我想知道:这不是简单地将问题转移到解析被抛出的String参数吗? 例如,如果使用EmployeeId所有代码(从Exporter接收)将String解析为int ,并且突然开始导出long值,则需要修改完全相同的数量…并且如果您开始将其解析为很long它可能必须改为double (即使这对id没有意义)……如果你不能确定将String解析为什么,你就无法实现任何东西

问题2:

除了这个问题,我还有另一个问题:我意识到这篇文章已有七年多了,所以有人能指出我最近关于OO设计的一些概述,特别是关于getter / setter和实现隐藏辩论的想法吗?

清单1. Employee:Builder Context


  public class Employee { private Name name; private EmployeeId id; private Money salary; public interface Exporter { void addName ( String name ); void addID ( String id ); void addSalary ( String salary ); } public interface Importer { String provideName(); String provideID(); String provideSalary(); void open(); void close(); } public Employee( Importer builder ) { builder.open(); this.name = new Name ( builder.provideName() ); this.id = new EmployeeId( builder.provideID() ); this.salary = new Money ( builder.provideSalary(), new Locale("en", "US") ); builder.close(); } public void export( Exporter builder ) { builder.addName ( name.toString() ); builder.addID ( id.toString() ); builder.addSalary( salary.toString() ); } //... } 

问题1 :字符串解析看起来很奇怪。 恕我直言,你只能做很多事情来预测未来的增强function。 您可以从一开始就使用long参数来确定,或者考虑稍后添加其他构造函数。 或者,您可以引入可扩展的参数类。 见下文。

问题2 :有几种情况可以使用构建器模式。

  • 复杂对象创建

    当您处理具有许多属性的非常复杂的对象时,您最好只在对象创建时设置一次,使用常规构造函数执行此操作可能会变得难以阅读,因为构造函数将具有很长的参数列表。 将其作为API发布并不是一种好的风格,因为每个人都必须仔细阅读文档并确保它们不会混淆参数。

    相反,当您提供构建器时,只需要处理获取所有参数的(私有)构造函数,但是类的使用者可以使用更易读的单个方法。

    Setter不是一回事,因为它们允许您在创建后更改对象属性。

  • 可扩展的API

    当您只为类发布多参数构造函数并稍后决定需要添加新的(可选)属性时(例如在软件的更高版本中),您必须创建与第一个相同的第二个构造函数,但需要多一个参数。 否则 – 如果您只是将它添加到现有构造函数中 – 您将破坏与现有代码的兼容性。

    使用构建器,您只需为新属性添加新方法,所有现有代码仍然兼容。

  • 不变性

    软件开发强烈倾向于并行执行多个线程。 在这种情况下,最好使用在创建后无法修改的对象(不可变对象),因为这些对象不会导致来自多个线程的并发更新问题。 这就是为什么不能选择setter的原因。

    现在,如果您想避免多参数公共构造函数的问题,那么将构建器作为一种非常方便的替代方法。

  • 可读性(“Fluent API”)

    基于构建器的API可以非常容易阅读,如果构建器的方法被巧妙地命名,您可以使用几乎像英语句子的代码。

通常,构建器是一种有用的模式,根据您使用的语言,它们要么非常容易使用(例如Groovy),要么对于API的提供者来说更乏味(例如在Java中)。 然而,对于消费者来说,他们可以同样轻松。

您可以以更简洁的方式实现Builders。 ;)我经常发现手工编写建设者是单调乏味且容易出错的。

如果您有一个生成数据值对象及其构建器(和编组器)的数据模型,它可以很好地工作。 在这种情况下,我相信使用Builders是值得的。

构造函数有很多问题需要参数(例如,你不能在几个步骤中构建对象)。 此外,如果您需要大量参数,您最终会对参数顺序感到困惑。

最新的想法是使用“ 流畅的界面 ”。 它适用于返回this setter。 通常,方法名称中省略了set 。 现在你可以写:

 User user = new User() .firstName( "John" ) .familyName( "Doe" ) .address( address1 ) .address( address2 ) ; 

这有几个好处:

  1. 它非常易读。
  2. 您可以更改参数的顺序而不会破坏任何内容
  3. 它可以处理单值和多值参数( address )。

主要缺点是,当实例“准备好”使用时,您不再知道。

解决方案是进行许多unit testing或专门添加“init()”或“done()”方法,该方法执行所有检查并设置标记“此实例已正确初始化”。

另一个解决方案是在build()方法中创建实际实例的工厂,该方法必须是链中的最后一个:

 User user = new UserFactory() .firstName( "John" ) .familyName( "Doe" ) .address( address1 ) .address( address2 ) .build() ; 

像Groovy这样的现代语言将其转化为语言function:

 User user = new User( firstName: 'John', familyName: 'Doe', address: [ address1, address2 ] ) 

当您需要对象的构造函数(以类似方式考虑工厂)时,可以使用对象强制代码将基本要求传递给构造函数。 越明显越好。 您可以使用setter保留稍后(注入)的可选字段。