第七章:不同的层,不同的抽象

In a well-designed system, each layer provides a different abstraction from the layers above and below it; if you follow a single operation as it moves up and down through layers by invoking methods, the abstractions changes with each method call.

设计好的系统:
每一层的上下都提供了不同的抽象。如果从一个操作深入进去,可以发现每次调用的抽象概念是不一样的。

以文件系统为例:
最上层实现了文件抽象概念 - 由可变长度的字节流数组(可读写)
下一层实现了缓存在固定大小的内存中 - 调用者能快速访问
最底层由设备驱动器组成,用来在次级存储设备和内存之间移动块

上面的例子某种程度上可以看出,这三层每层使用的下层的能力和提供到上层的能力的抽象概念都是不一样的。


If a system contains adjacent layers with similar abstractions, this is a red flag that suggests a problem with the class decomposition.

当上下层抽象概念类似时,说明系统的拆分是有问题的

后续阅读目标:
- situations where this happens
	- 包装了一层(pass-through methods 或 decorators)
	- 上下层使用一个概念
	- pass-through arguments
- the problems that result
	- pass-through methods 和 decorators 方式很可能没有提供足够的好处来弥补额外的复杂度(任职成本等)。
	- 如果上层和下层使用了一个概念,可能会增加系统的复杂度
		- 编辑器面向行的接口与面向字符的接口面对多行删除等场景时
	- pass-through arguments 必须得让中间的方法都意识到它的存在
- how to refactor to eliminate the problems
	- 见下文

Pass-through methods

A pass-through method is one that does little except invoke another method, whose signature is similar or identical to that of the calling method.
A pass-through method is one that does nothing except pass its arguments to another method, usually with the same API as the pass-through method.
This typically indicates that there is not a clean division of responsibility between the classes.

Pass-through methods 指那些类似的方法签名类似甚至一致的方法,主要作用是调用其他方法。

很典型地说明了类之间的能力没有清晰的分割开。

Pass-through methods make classes shallower: they increase the interface complexity of the class, which adds complexity, but they don't increase the total functionality of the system.
Pass-through methods also create dependencies between classes
Pass-through methods indicate that there is confusion over the division of responsibility between classes.

pass-through method 带来的问题:

增加了接口的复杂度,但没有增加系统的能力
同时将类之间创建了依赖关系。当被调用的方法发生变更是,调用它的方法也要发生变更。

Pass-through methods are bad because they contribute no new functionality.


You will probably notice that there is an overlap in responsibility between the classes.

The solution is to refator the classes so that each class has a distinct and coherent set of responsibilities.

解决方案:
(a) 出现了Pass-through methods
{b} 将底层的类直接暴露给高层类的调用方,
(c) 将能力在类之间重新分配
(d) 如果类不能分割,最好地方式是合并在一起

When is interface dulication OK?

The important thing is that each new method should contribute significant functionality
One example where it's useful for a method to call another method with the same signature is a dispatcher.

引出**分配器**

A dispatcher is a method that uses its arguments to select one of several other methods to invoke;
then it passes most or all of its arguments to the chosen method.
The signature for the dispatcher is offen the same as the signature for the methods that it calls.
the dispatcher provides useful functionality: it chooses which of several other methods should carry out each task.

什么是调度器

For example, when a Web server receives an incomming HTTP request from a web browser, it invokes a dispatcher that examins the URL in the incoming request and selects a specific method to handle the request.
Another method is interface with multiple implementations, such as disk drivers in an operating system.
When several methods provide different implementations of the same interface, it reduces congnitive load...Methods like this are usually in the same layer and they don't invoke each other.

第一个例子:HTTP请求分发

第二个例子:一个接口的多个实现。可以通过分配器(自己的要素)决定需要执行哪一种实现。

这两个例子都允许重复接口存在。

Decorators

The decorator design pattern (also known as a "wrapper") is one that encourages API duplication across layers.
A decorator object takes an existing object and extends its functionality; it provides an API similar or identical to the underlying object, and its methods invoke the methods of the underlying object.


example:
Java I/O: BufferedInputStream class is a decorator: given a InputStream object, it provides the sampe API but introduces buffering.
sindowing systems: a Window class implements a simple form of window that is not scrollable, an a ScrollableWindow class decorates the Window class by adding horizontal and vertical scrollbars.

The motivation for decorators is to separate special-purpose extensions of a class from a more generic core.

动机

decorator classes tend to be shallow: they introduce a large amount of boilerplate for a small amount of new functionality.
It's easy to overuse the decorator pattern, creating a new class for every small new feature.

弊端

Before creating a decorator class, consider alternatives such as the following:

  • Could you add the new functionality directly to the underlying class, rather than creating a decorator class?
    This makes sense if the new functionality is relatively general-purpose, or if it is logically related to the underlying class, or if moset uses of the underlying class will alse use the new functionality.
  • If the new functionality is specialized for a particular use case, would it make sense to merge it with the use case, rather than creating a separate class?
  • Could you merge the new functionality with an existing decorator, rathar than creating a new decorator?
    This would result in a single deeper decorator class rathar than multiple shallow ones.
  • whether the new functionality really needs to wrap the existing functionality: could your implement it as a stand-alone class that is independent of the base class?
有时装饰者模式是好的,但是通常会有更好的替代方案。
在每次创建封装类之前,思考一下上面的问题

Interface versus implementation

the interface of a class should normally be different from its implementation: the representations used internally should be different from the abstractions that appear in the interface.
If the two have similar abstractions, then the class probably isn't very deep

设计一个编辑器,
面向行的接口与面向字符的接口
面向行的接口会导致上层使用时,如果涉及行中插入以及多行删除的问题,会很被动
面向字符的接口能更加了灵活

突然发现了,这个想说的点:
如果上下层使用了一个概念,并且实现。后续扩展会增加很大的复杂度。
同时,接口和实现是可以分离的。接口展示的能力和内部实现的方式不一定要相同。

但是例子说明的是接口设计问题,设计的接口应该是面向字段的,而不是面向行的。没有点出类的接口通常和实现不一致的点。\裂开
所以这个例子只是为了说明建议将上层的展示和下层的实现分割开来?

Pass-through variables

Another form of API duplication accross layers is a pass-through variable, which is a variable that is passed down through a long chain of methods.

7.2(a)中的示例,说明了cert需要一直传,传到m3中才会被用到,但是m1,m2都需要哦感知到它的存在

Pass-through variables add complexity becaouse they force all of the intermidiate methods to be aware of their existence, even though the methods have no use for the variables.
Furthermore, if a new variable comes into existence, you may have to modify a large number of interfaces and methods to pass the variable through all of the relevant paths.


消除Pass-through variables
shared object

One approach is to see if there is already an object shared between the topmost and bottommost methods.

global variable

Another approach is to store the information in a global variable.
global variables make it impossible to create two independent instances of the same system in the same process, since accesses to the global variables will confilct.

context object

introduce a context object.
stores all of the application's global state.
The context allows multiple instances of the system to coexist in a single process, each with its own context.
th class containing m3 stores a reference to the context as an instance variable in its objects. When a new object is created, the creating method retrieves the context reference from its object and passes it to the constructor for the new object.
the context is available every where, but it only appears as an explicit argument in constructors.

The context object unifies the handling of all system-global information and eliminates the need for pass-through variables.
If a new variable needs to be added, it can be added to the context object; no existing code is affected except for the constructor and destructor for the context.
The context is also convenient for testing: test code can change the global configuration of the application by modifying fields in the context.

Contexts are far from an idea solution.The variables stored in a context have most of the disadvantages of global variables;

  • it may not be obvious why a particular variable is present, or where it is used.
  • without discipline, a context can turn intu a huge grab-bag of data that creates nonobvious dependencies throughout the system
  • may also create thread-safety issues; the best way to avoid problems is for variables in a context tob immutable.
列举了三种解决Pass-through variables的方法,
共有对象
全局变量
上下文对象

根据上面的图示中显示,共有对象与上下文对象存在的差异在于,共有对象是在main和m3有访问权限,上下文对象是在创建新对象时构造器传入的。
核心都是另辟一个地方存放共有对象,在后面需要的地方用到。

Conclusion

Each piece of design infrastructure added to a system, such as an interface, argument, function, class, or definition, adds complexity, since developers must learn about this element.
In order for an element to provide a net gain against complexity, it must eliminate some complexity that would be present in the absence of the disign elemnt. Otherwise, you are better off implementing the system without that particular element.

任何元素的增加都会增加系统的复杂度 —— 增加认知成本
为了让添加元素带来的净收益高于复杂度,新增的元素必须比没有这个元素带来的复杂度更低,那个才有加这个元素的价值。否则,就不要加了。

The "different layer, different abstraction" rule is just an application of this idea: if different layers have the same abstraction ,such as pass-through methods or decorators, then there's a good chance that they haven't provided enough benefit to compensate for the additional infrastructure they represent.

如果不同层有相同的抽象,那么它们很有可能没有提供足够的好处来弥补它们带来的额外的基础建设

pass-through arguments必须得让那些方法意识到它们的存在,但是没有贡献额外的功能。