OO上的JavaWorld:Getters / Setters vs Builder

时间:2011-08-03 13:35:19

标签: java oop design-patterns builder getter-setter

背景:

我在JavaWorld上找到了this article,其中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.员工:构建器上下文


  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() );
      }

      //...
  }

4 个答案:

答案 0 :(得分:10)

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

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

  • 复杂对象创建

    当您处理具有大量属性的非常复杂的对象时 你最好只在对象创建时设置一次,这样做 常规构造函数可能变得难以阅读,因为构造函数会 有一长串参数。将其作为API发布并不是一种好的风格 因为每个人都必须仔细阅读文档并确保 他们不会混淆参数。

    相反,当你提供建筑师时,只有你必须应对(私人) 获取所有参数的构造函数,但是您的类的消费者可以 使用更易读的个别方法。

    Setter不是一回事,因为它们会允许你改变对象 创建后的属性。

  • 可扩展API

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

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

  • 不变性

    软件开发趋向于并行执行 多线程。在这种情况下,最好使用不能的对象 在创建它们之后进行修改(不可变对象),因为这些 不会导致来自多个线程的并发更新问题。这是 为什么不能选择安装人员。

    现在,如果你想避免多参数公共构造函数的问题, 这使得建设者成为一种非常方便的选择。

  • 可读性(“Fluent API”)

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

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

答案 1 :(得分:2)

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

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

答案 2 :(得分:2)

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

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

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

这有几个好处:

  1. 它非常易读。
  2. 您可以更改参数的顺序而不会破坏任何内容
  3. 它可以处理单值和多值参数(address)。
  4. 主要缺点是,当实例“准备好”使用时,您不再知道。

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

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

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

    Groovy等现代语言将此转换为语言功能:

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

答案 3 :(得分:0)

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