5.5. 示例:太多的类

学生最常犯的错误是将他们的代码分成大量的浅层,这导致了类之间的信息泄漏。一个团队使用两种不同的类来接收 HTTP 请求。第一类将来自网络连接的请求读取为字符串,第二类将字符串解析。这是时间分解的一个示例(“首先读取请求,然后解析它”)。发生信息泄漏是因为无法解析大量消息就无法读取 HTTP 请求。例如,Content-Length 标头指定了请求主体的长度,因此必须对标头进行解析才能计算总请求长度。结果,这两个类都需要了解 HTTP 请求的大多数结构,并且解析代码在两个类中都是重复的。

由于这些共享大量信息,因此最好将它们合并为一个同时处理请求读取和解析的类。由于它将请求格式的所有知识隔离在一个类中,因此它提供了更好的信息隐藏,并且还为调用者提供了一个更简单的接口(只是一种调用方法)。

此示例说明了软件设计中的一般主题:通常可以通过使稍大一些来改善信息隐藏。这样做的一个原因是将与特定功能相关的所有代码(例如,解析 HTTP 请求)组合在一起,以便生成的类包含与该功能相关的所有内容。增加类大小的第二个原因是提高接口的级别。例如,与其为计算的三个步骤中的每一个步骤使用单独的方法,不如使用一种方法来执行整个计算。这样可以简化界面。这两个优点都适用于上一段的示例:组合类将与解析 HTTP 请求相关的所有代码组合在一起,并且用一个替换了两个外部可见的方法。

当然,可以将较大的类的概念考虑得太远(例如整个应用程序的单个类)。 第九章:在一起更好还是分开更好? 将讨论将代码分成多个较小的类的合理条件。

The most common mistake made by students was to divide their code into a large number of shallow classes, which led to information leakage between the classes. One team used two different classes for receiving HTTP requests; the first class read the request from the network connection into a string, and the second class parsed the string. This is an example of a temporal decomposition (“first we read the request, then we parse it”). Information leakage occurred because an HTTP request can’t be read without parsing much of the message; for example, the Content-Length header specifies the length of the request body, so the headers must be parsed in order to compute the total request length. As a result, both classes needed to understand most of the structure of HTTP requests, and parsing code was duplicated in both classes. This approach also created extra complexity for callers, who had to invoke two methods in different classes, in a particular order, to receive a request.

Because the classes shared so much information, it would have been better to merge them into a single class that handles both request reading and parsing. This provides better information hiding, since it isolates all knowledge of the request format in one class, and it also provides a simpler interface to callers (just one method to invoke).

This example illustrates a general theme in software design: information hiding can often be improved by making a class slightly larger. One reason for doing this is to bring together all of the code related to a particular capability (such as parsing an HTTP request), so that the resulting class contains everything related to that capability. A second reason for increasing the size of a class is to raise the level of the interface; for example, rather than having separate methods for each of three steps of a computation, have a single method that performs the entire computation. This can result in a simpler interface. Both of these benefits apply in the example of the previous paragraph: combining the classes brings together all of the code related to parsing an HTTP request, and it replaces two externally-visible methods with one. The combined class is deeper than the original classes.

Of course, it is possible to take the notion of larger classes too far (such as a single class for the entire application). Chapter 9 will discuss conditions under which it makes sense to separate code into multiple smaller classes.