JavaWorld on OO:Getters/Setters 与 Builder
背景:
我找到了这篇文章 在 JavaWorld 上,Allen Holub 解释了 Getters/Setter 的替代方案,它保持了对象的实现应该隐藏的原则(他的示例代码也可以在下面找到)。
据解释,类 Name
/EmployeeId
/Money
应该有一个采用单个字符串的构造函数 - 原因是,如果您将其键入为一个 int
,稍后需要将其更改为 long
,您将必须修改该类的所有用途,而使用此模式则不必这样做。
问题1:
我想知道:这不是简单地将问题转移到解析被抛掷的String
参数上吗?例如,如果所有使用 EmployeeId
的代码(从 Exporter
接收)将 String
解析为 int
,然后突然开始导出 long
值,您需要修改与许多用途完全相同的内容...如果您开始将其解析为 long
,它很可能必须更改到 double
(即使这不会导致id 的意义)...并且如果您无法确定将 String
解析为什么,则您无法实现任何内容。
问题 2:
除了这个问题,我还有另一个问题:我意识到这篇文章已有七年多了,所以有人可以向我指出一些有关 OO 设计的最新概述,特别是有关 getter/setter 和实现隐藏争论的想法吗?
清单 1. Employee:构建器上下文
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() );
}
//...
}
Background:
I found this article on JavaWorld, where Allen Holub explains an alternative to Getters/Setters that maintains the principle that the implementation of an object should be hidden (his example code can also be found below).
It is explained that the classes Name
/EmployeeId
/Money
should have a constructor taking a single string - the reasoning is that if you type it as an int
, and later need to change it to a long
, you will have to modify all the uses of the class, and with this pattern you don't have to.
Question 1:
I was wondering: doesn't this simply move the problem to the parsing of the String
parameters being tossed about? For example, if all the code using the EmployeeId
(received from the Exporter
) parses the String
into an int
, and suddenly you start exporting long
values, you need to modify exactly as many uses... and if you start out parsing it as a long
it might well have to change to a double
(even though that makes no sense for id's)... and if you can't be sure what to parse the String
into, you can't implement anything.
Question 2:
Besides this question, I have another: I realise that the article is over seven years old, so could anyone point me to some recent overviews concerning OO-design, and specifically to ideas concerning the getter/setter and implementation hiding debate?
Listing 1. Employee: The 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() );
}
//...
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
问题 1:
字符串解析看起来很奇怪。恕我直言,您只能做这么多来预测未来的增强功能。您可以从一开始就使用
long
参数来确定,或者考虑稍后添加其他构造函数。或者,您可以引入可扩展的参数类。见下文。问题 2:
构建器模式在多种场景下都非常有用。
复杂对象创建
当您处理具有大量属性的非常复杂的对象时
您最好只在对象创建时设置一次,使用
常规构造函数可能会变得难以阅读,因为构造函数将
有一个很长的参数列表。将其作为 API 发布并不是一种好的风格
因为每个人都必须仔细阅读文档并确保
它们不会混淆参数。
相反,当您提供构建器时,只有您必须应对(私人)
构造函数接受所有参数,但类的使用者可以
使用更具可读性的单独方法。
Setter 不是一回事,因为它们允许您更改对象
创建后的属性。
可扩展的API
当您仅为您的类及更高版本发布多参数构造函数时
决定您需要添加新的(可选)属性(例如在软件的更高版本中)
您必须创建与第一个构造函数相同的第二个构造函数,但是
还需要一个参数。否则 - 如果您只是将其添加到现有的
构造函数 - 你会破坏与现有代码的兼容性。
使用构建器,您只需为新属性添加一个新方法,以及所有现有的
代码仍然兼容。
不变性
软件开发强烈倾向于并行执行
多线程。在这种情况下,最好使用不能
创建后可以修改(不可变对象),因为这些
不会导致多个线程的并发更新出现问题。这是
为什么 setter 不是一个选择。
现在,如果您想避免多参数公共构造函数的问题,
这使得构建器成为一种非常方便的选择。
可读性(“Fluent API”)
如果构建器的方法是,基于构建器的 API 可以非常容易阅读
巧妙地命名,您可以写出读起来几乎像英语句子的代码。
一般来说,构建器是一种有用的模式,并且根据您所使用的语言,对于 API 的提供者来说,它们要么非常易于使用(例如 Groovy),要么稍微乏味(例如在 Java 中)。然而,对于消费者来说,它们也同样容易。
Question 1:
String parsing seems strange. IMHO you can only do so much to anticipate future enhancements. Either you use a
long
parameter right from the start to be sure, or consider adding additional constructors later. Alternatively you can introduce an extensible parameter class. See below.Question 2:
There are several scenarios in which the builder pattern can be useful.
Complex Object creation
When you are dealing with very complex object that have lots of properties
that you would preferably only set once at object creation, doing this with
regular constructors can become hard to read, because the constructor will
have a long list of parameters. Publishing this as an API is not good style
because everyone will have to read the documentation carefully and make sure
they do not confuse parameters.
Instead when you offer a builder, only you have to cope with the (private)
constructor taking all the arguments, but the consumers of your class can
use much more readable individual methods.
Setters are not the same thing, because they would allow you to change object
properties after its creation.
Extensible API
When you only publish a multi-parameter constructor for your class and later
decide you need to add a new (optional) property (say in a later version of your software)
you have to create a second constructor that is identical to the first one, but
takes one more parameter. Otherwise - if you were to just add it to the existing
constructor - you would break compatibility with existing code.
With a builder, you simply add a new method for the new property, with all existing
code still being compatible.
Immutability
Software development is strongly trending towards parallel execution of
multiple threads. In such scenarios it is best to use objects that cannot
be modified after they have been created (immutable objects), because these
cannot cause problems with concurrent updates from multiple threads. This is
why setters are not an option.
Now, if you want to avoid the problems of the multi-parameter public constructors,
that leaves builders as a very convenient alternative.
Readability ("Fluent API")
Builder based APIs can be very easy to read, if the methods of the builder are
named cleverly, you can come out with code that reads almost like English sentences.
In general, builders are a useful pattern, and depending on the language you are using, they are either really easy to use (e. g. Groovy) or a little more tedious (e. g. in Java) for the provider of an API. For the consumers, however, they can be just as easy.
带参数的构造函数存在很多问题(例如,无法分几步构建对象)。此外,如果您需要大量参数,您最终会对参数顺序感到困惑。
最新的想法是使用“流畅的界面”。它与返回
this
的 setter 一起使用。通常,方法名称中会省略set
。现在您可以编写:这有几个优点:
地址
)。主要缺点是您不再知道实例何时“准备好”使用。
解决方案是进行许多单元测试或专门添加一个“init()”或“done()”方法来执行所有检查并设置一个标志“此实例已正确初始化”。
另一个解决方案是一个工厂,它在
build()
方法中创建实际实例,该方法必须是链中的最后一个:现代语言,如 Groovy 将其转变为语言功能:
There are many problems with constructors that take arguments (for example, you can't build the object in several steps). Also if you need lots of arguments, you will eventually get confused about parameter order.
The latest idea is to use a "fluent interface". It works with setters that return
this
. Often,set
is omitted from the method name. Now you can write:This has several advantages:
address
).The major drawback is that you don't know anymore when the instance is "ready" to be used.
The solution is to have many unit tests or specifically add an "init()" or "done()" method which does all the checks and sets a flag "this instance is properly initialized".
Another solution is a factory which creates the actual instance in a
build()
method which must be the last in the chain:Modern languages like Groovy turn this into a language feature:
您可以以更简洁的方式实现 Builders。 ;) 我经常发现手工编写构建器非常乏味且容易出错。
如果您有一个生成数据值对象及其构建器(和编组器)的数据模型,它可以很好地工作。在这种情况下,我相信使用 Builders 是值得的。
You can implement Builders is a more concise manner. ;) I have often found writing Builders by hand tedious and error prone.
It can work well if you have a data model which generates your Data Value objects and their Builders (and marshallers). In that case I believe using Builders is worth it.
当您需要对象的构造函数(以类似的方式考虑工厂)时,您可以强制使用对象的代码将基本要求传递给构造函数。越明确越好。
您可以将可选字段保留为稍后使用设置器设置(注入)。
When you require a constructor (consider factories in a similar way) for an object, you force the code using your object to pass the essential requirements to the constructor. The more explicit the better.
You can leave the optional fields to be set later (injected) using a setter.