Chap23. PRESENTERS AND HUMBLE OBJECTS 展示器和谦卑对象

Chap23. 展示器和谦卑对象 - 图1

In Chapter 22, we introduced the notion of presenters. Presenters are a form of the Humble Object pattern, which helps us identify and protect architectural boundaries. Actually, the Clean Architecture in the last chapter was full of Humble Object implementations.

在第 22 章中,我们引入了展示器(presenter)的概念,展示器实际上是采用谦卑对象(humble object)模式的一种形式,这种设计模式可以很好地帮助识别和系统架构的边界。事实上,第 22 章所介绍的整洁架构中就充满了大量谦卑对象的实现体。

THE HUMBLE OBJECT PATTERN 谦卑对象模式

The Humble Object pattern1 is a design pattern that was originally identified as a way to help unit testers to separate behaviors that are hard to test from behaviors that are easy to test. The idea is very simple: Split the behaviors into two modules or classes. One of those modules is humble; it contains all the hard-to-test behaviors stripped down to their barest essence. The other module contains all the testable behaviors that were stripped out of the humble object.

谦卑对象模式最初的设计目的是帮助单元测试的编写者区分容易测试的行为与难以测试的行为,并将它们隔离。其设计思路非常简单,就是将这两类行为拆分成两组模块或类。其中一组模块被称为谦卑(Humble)组,包含了系统中所有难以 测试的行为,而这些行为已经被简化到不能再简化了。另一组模块则包含了所有不属于谦卑对象的行为。

For example, GUIs are hard to unit test because it is very difficult to write tests that can see the screen and check that the appropriate elements are displayed there. However, most of the behavior of a GUI is, in fact, easy to test. Using the Humble Object pattern, we can separate these two kinds of behaviors into two different classes called the Presenter and the View.

例如,GUI 通常是很难进行单元测试的,因为让计算机自行检视屏幕内容,并检查指定元素是否出现是非常难的事情。然而,GUI 中的大部分行为实际上是很容易被测试的。这时候,我们就可以利用谦卑对象模式将 GUI 的这两种行为拆分成展示器与视图两部分。

PRESENTERS AND VIEWS 展示器与视图

The View is the humble object that is hard to test. The code in this object is kept as simple as possible. It moves data into the GUI but does not process that data.

视图部分属于难以测试的谦卑对象。这种对象的代码通常应该越简单越好,它只应负责将数据填充到 GUI 上,而不应该对数据进行任何处理。

The Presenter is the testable object. Its job is to accept data from the application and format it for presentation so that the View can simply move it to the screen. For example, if the application wants a date displayed in a field, it will hand the Presenter a Date object. The Presenter will then format that data into an appropriate string and place it in a simple data structure called the View Model, where the View can find it.

展示器则是可测试的对象。展示器的工作是负责从应用程序中接收数据,然后按视图的需要将这些数据格式化,以便视图将其呈现在屏幕上。例如,如果应用程序需要在屏幕上展示一个日期,那么它传递给展示器的应该是一个 Date 对象。然后展示器会将该对象格式化成所需的字符串形式,并将其填充到视图模型中。

If the application wants to display money on the screen, it might pass a Currency object to the Presenter. The Presenter will format that object with the appropriate decimal places and currency markers, creating a string that it can place in the View Model. If that currency value should be turned red if it is negative, then a simple boolean flag in the View model will be set appropriately.

如果应用程序需要在屏幕上展示金额,那么它应该将 Currency 对象传递给展示器。展示器随后会将这个对象按所需的小数位数进行格式化,并加上对应的货币标识符,形成一个字符串存放在视图模型中。如果需要将负数金额显示成红色,那么该视图模型中就应该有一个简单的布尔值被恰当地设置。

Every button on the screen will have a name. That name will be a string in the View Model, placed there by the presenter. If those buttons should be grayed out, the Presenter will set an appropriate boolean flag in the View model. Every menu item name is a string in the View model, loaded by the Presenter. The names for every radio button, check box, and text field are loaded, by the Presenter, into appropriate strings and booleans in the View model. Tables of numbers that should be displayed on the screen are loaded, by the Presenter, into tables of properly formatted strings in the View model.

另外,应用程序在屏幕上的每个按钮都应该有其对应的名称,这些名称也是由展示器在视图模型中设置的。如果某个按钮需要变灰,展示器就应该将相应的开关变量设置成对应的布尔值。同样,菜单中每个菜单项所显示的值,也应该是一个个由展示器加载到视图模型中的字符串。应用程序在屏幕上显示的每个单选项、多选项以及文本框的名字也都如此,在视图模型中都有相应的字符串和布尔值可供展示器做对应的设置。即使屏幕上要加载的是一个数值表,展示器也应该负责把这些数值格式化成具有表格属性的字符串,以供视图使用。

Anything and everything that appears on the screen, and that the application has some kind of control over, is represented in the View Model as a string, or a boolean, or an enum. Nothing is left for the View to do other than to load the data from the View Model into the screen. Thus the View is humble.

总而言之,应用程序所能控制的、要在屏幕上显示的一切东西,都应该在视图模型中以字符串、布尔值或枚举值的形式存在。然后,视图部分除了加载视图模型所需要的值,不应该再做任何其他事情。因此,我们才能说视图是谦卑对象。

TESTING AND ARCHITECTURE 测试与架构

It has long been known that testability is an attribute of good architectures. The Humble Object pattern is a good example, because the separation of the behaviors into testable and non-testable parts often defines an architectural boundary. The Presenter/View boundary is one of these boundaries, but there are many others.

众所周知,强大的可测试性是一个架构的设计是否优秀的显著衡量标准之一。谦卑对象模式就是这方面的一个非常好的例子。我们将系统行为分割成可测试和不可测试两部分的过程常常就也定义了系统的架构边界。展示器与视图之间的边界只是多种架构边界中的一种,另外还有许多其他边界。

DATABASE GATEWAYS 数据库网关

Between the use case interactors and the database are the database gateways.2 These gateways are polymorphic interfaces that contain methods for every create, read, update, or delete operation that can be performed by the application on the database. For example, if the application needs to know the last names of all the users who logged in yesterday, then the UserGateway interface will have a method named getLastNamesOfUsersWhoLoggedInAfter that takes a Date as its argument and returns a list of last names.

对于用例交互(interactor)与数据库中间的组件,我们通常称之为数据库网关。这些数据库网关本身是一个多态接口,包含了应用程序在数据库上所要执行的创建、读取、更新、删除等所有操作。例如,如果应用程序需要知道所有昨天登录系统的用户姓,那么 UserGateway 接口就应该包含一个 getLastNamesOfUsersWhoLoggedlnAfter 方法,接收一个 Date 参数,并返回一个包含姓的列表。

Recall that we do not allow SQL in the use cases layer; instead, we use gateway interfaces that have appropriate methods. Those gateways are implemented by classes in the database layer. That implementation is the humble object. It simply uses SQL, or whatever the interface to the database is, to access the data required by each of the methods. The interactors, in contrast, are not humble because they encapsulate application-specific business rules. Although they are not humble, those interactors are testable, because the gateways can be replaced with appropriate stubs and test-doubles.

另外,我们之前说过,SQL 不应该出现在用例层的代码中,所以这部分的功能就需要通过网关接口来提供,而这些接口的实现则要由数据库层的类来负责。显然,这些实现也应该都属于谦卑对象,它们应该只利用 SQL 或其他数据库提供的接口来昉问所需要的数据。与之相反,交互器则不属于谦卑对象,因为它们封装的是特定应用场景下的业务逻辑。不过,交互器尽管不属于谦卑对象,却是可测试的,因为数据库网关通常可以被替换成对应的测试桩和测试替身类。

DATA MAPPERS 数据映射器

Going back to the topic of databases, in which layer do you think ORMs like Hibernate belong?

让我们继续数据库方面的话题,现在我们来思考一下 Hibernate 这类的 ORM 框架应该属于系统架构中的哪一层呢?

First, let’s get something straight: There is no such thing as an object relational mapper (ORM). The reason is simple: Objects are not data structures. At least, they are not data structures from their users’ point of view. The users of an object cannot see the data, since it is all private. Those users see only the public methods of that object. So, from the user’s point of view, an object is simply a set of operations.

首先我们要弄清楚一件事:对象关系映射器(ORM)事实上是压根就不存在的。带来很简单,对象不是数据结构。至少从用户的角度来说,对象内部的数据应该都是私有的,不可见的,用户在通常情况下只能看到对象的公有函数。因此从用户角度来说,对象是一些操作的集合,而不是简单的数据结构体。

A data structure, in contrast, is a set of public data variables that have no implied behavior. ORMs would be better named “data mappers,” because they load data into data structures from relational database tables.

与之相反,数据结构体则是一组公开的数据变量其中不包含任何行为信息。所以 ORM 更应该被称为“数据映射器”,因为它们只是将数据从关系型数据库加载到了对应的数据结构中。

Where should such ORM systems reside? In the database layer of course. Indeed, ORMs form another kind of Humble Object boundary between the gateway interfaces and the database.

那么,这样的 ORM 系统应该属于系统架构中的哪一层呢?当然是数据库层。ORM 其实就是在数据库和数据库网关之间构建了另一种谦卑对象的边界。

SERVICE LISTENERS 服务监听器

What about services? If your application must communicate with other services, or if your application provides a set of services, will we find the Humble Object pattern creating a service boundary?

如果我们的应用程序需要与其他服务进行某种交互,或者该应用本身要提供某一套服务,我们在相关服务的边界处也会看到谦卑对象模式吗?

Of course! The application will load data into simple data structures and then pass those structures across the boundary to modules that properly format the data and send it to external services. On the input side, the service listeners will receive data from the service interface and format it into a simple data structure that can be used by the application. That data structure is then passed across the service boundary.

答案是肯定的。我们的应用程序会将数据加载到简单的数据结构中,并将这些数据结构跨边界传输给那些能够将其格式化并传递其他外部服务的模块。在输入端,服务监听器会负责从服务接口中接收数据,并将其格式化成该应用程序易用的格式。总而言之,上述数据结构可以进行跨服务边界的传输。

CONCLUSION 本章小结

At each architectural boundary, we are likely to find the Humble Object pattern lurking somewhere nearby. The communication across that boundary will almost always involve some kind of simple data structure, and the boundary will frequently divide something that is hard to test from something that is easy to test. The use of this pattern at architectural boundaries vastly increases the testability of the entire system.

在每个系统架构的边界处,都有可能发现谦卑对象模式的存在。因为跨边界的通信肯定需要用到某种简单的数据结构,而边界会自然而然地将系统分割成难以测试的部分与容易测试的部分,所以通过在系统的边界处运用谦卑对象模式,我们可以大幅地提高整个系统的可测试性。