OO上的JavaWorld:Getters/Setters vs Builder

wen*_*wen 11 java oop design-patterns builder getter-setter

背景:

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

它解释了类Name/ EmployeeId/ Money应该有一个构造函数接受一个字符串 - 原因是如果你输入它int,然后需要将它改为a long,你将不得不修改类的所有用途,并且你不需要这种模式.

问题1:

我想知道:这不是简单地将问题转移到解析String被抛出的参数吗?例如,如果所有使用EmployeeId(从中接收Exporter)的代码解析String成一个int,并且突然开始导出long值,则需要完全修改使用的数量......如果你开始解析long它可能就好了改为a 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() );
      }

      //...
  }
Run Code Online (Sandbox Code Playgroud)

Dan*_*ler 10

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

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

  • 复杂对象创建

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

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

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

  • 可扩展的API

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

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

  • 不变性

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

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

  • 可读性("Fluent API")

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

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

  • 与使用 setter 相比,“不变性”是最大的优势差异 (2认同)

Aar*_*lla 5

带参数的构造函数存在很多问题(例如,无法分几步构建对象)。此外,如果您需要大量参数,您最终会对参数顺序感到困惑。

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

User user = new User()
.firstName( "John" )
.familyName( "Doe" )
.address( address1 )
.address( address2 )
;
Run Code Online (Sandbox Code Playgroud)

这有几个优点:

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

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

解决方案是进行许多单元测试或专门添加一个“init()”或“done()”方法来执行所有检查并设置一个标志“此实例已正确初始化”。

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

User user = new UserFactory()
.firstName( "John" )
.familyName( "Doe" )
.address( address1 )
.address( address2 )
.build()
;
Run Code Online (Sandbox Code Playgroud)

像Groovy这样的现代语言将其变成了一种语言功能:

User user = new User( firstName: 'John', familyName: 'Doe',
     address: [ address1, address2 ] )
Run Code Online (Sandbox Code Playgroud)