第六章:更深入的通用模块

设计新模块时,您将面临的最普遍的决定之一就是是以通用还是专用方式实现它。
应该采用通用方法....将实现一种可用于解决广泛问题的机制,而不仅是当前重要的问题....新机制可能会在将来发现意外用途,从而节省时间。通用方法似乎与第3章中讨论的投资思路一致:在这里您花了更多时间在前面,以节省以后的时间

通用优势:
增加可扩展性,为后续节省时间

另一方面,我们知道很难预测软件系统的未来需求,因此通用解决方案可能包含从未真正需要的功能.....如果您实现的东西过于通用,那么可能无法很好地解决您今天遇到的特定问题。

通用劣势:
1. 通用方案可能包含了非真正需要的功能 - 臆想出来的
2. 无法很好地解决当前碰到的问题 - 通用弊端

最好只关注当今的需求,构建您所指的需求,并针对您今天打算使用的方式进行专门化处理。如果您采用特殊用途的方法并在以后发现更多用途,则始终可以对其进行重构使其通用。专用方法似乎与软件开发的增量方法一致。

还是未能较明确的说明该如何平衡部分和通用。
但是后面有一个文本编辑器的例子做了一些说明。
中文版理解歧义太大了。后面阅读了原版

Make classes somewhat general-purpose

In my experience, the sweet spot is to implement new modules in a somewhat general-purpose fashion.
The phrase "somewhat general-purpose" means that the module's functionality should reflect your current needs, but its interface should not. Instead, the interface should be general enough to support multiple uses. The interface should be easy to use for today's needs without being tied specifically to them.
The word "somewhat" is important: don't get carried away and build something so general-purpose that it is difficult to use for your current needs.

分开解释了somewhat general-purpose和 somewhat两个词语。
somewhat general-purpose 意味着模块能力满足当前需求,但是接口不应该被当前需要的绑死。接口需要足够通用来支持其他的场景。
somewhat 又意味着接口不能为了普遍而导致在当前需求下难以使用
需要权衡通用和有些之间的度

The most important (and perhaps surprising) benefit of the general-purpose approach is that it results in simpler and deeper interfaces than a special-purpose approach.
The general-purpose approach can also save you time in the future, if you reuse the class for other purposes.
even if the module is only used for its original purpose, the general-purpose approach is still better because of its simplicity.

与有特殊目的的接口相比,使用通用原则的好处就是可以输出更简单和更专业的接口,而且通用方法会更加简单,容易理解。

上述提倡的是还是通用接口,但是是某个范围(somewhat)里面的通用。

Example : storing text for an editor

...implemented special-purpose APIs for the text class. They knew that the class was going to be used in an interactive editor, so they thought about the features that the editor had to provide and tailored the API of the text class to those specific features.

当使用`special purpose`原则进行开发,将界面操作的动作和接口对应起来。

The students probably thought that it would be easier to implement the user interface of the methods of the text class corresponded to the features visible to users.
In reality, however, this specialization provided little benefit for the user interface code, and it created a high cognitive load for developers working on either the user interface or the text class.
As a result, a developer working on the user interface had to learn about a large number of methods for the text class.

从开发者角度,在了解界面会有什么操作后,针对操作提供一致的接口可以增加一些遍历。
但是它也增加了两边开发者的认知成本。同时增加了维护成本。

This approach created information leakage between the user interface and the text class. Abstractions related to the user interface, such as the selection or the backspace key, were reflected in the text class; this increased the cognitive load for developers working on the text class.
One of the goals in class design is to allow each class to be developed independently, but the specialized approach tied the user interface and text classes together.

special-purpose 将用户界面和后端类绑定太深,导致难以独立更改。
Version 1:
void backspace(Cursor cursor); // 向前删除
void delete(Cursor cursor); // 向后删除
void deleteSelection(Selection selection); // 范围内删除

Version 2:
void insert(Position position, String newText);
void delete(Position satrt, Position end);
Position changePosition(Position position, int numChars)

A more general-purpose API

A better approach is to make the text class more generic. Its API should be defined only in terms of basic text features, without reflecting the higher-level operations that will be implemented with it.

With the general-purpose text API, the code to implement user interface functions such as delete and backspace is a bit longer than with the original approach using a specialized text API.
However, the new code is more obvious than the old code.

使用了gernal-purpose approach,使用方的代码可能会变长,但是新的接口代码会更加易于理解方法的能力。

With the old code, the developer had to go to the text class ans read the documentation and/or code of the backspace method to verify the behavior.

老的代码(special-purpose approach),需要开发者查阅文档或者方法的代码来确认方法的行为是否是自己想要的。

furthermore, the general-purpose approach has less code overall than the specialized approach, since it replaces a large number of special-purpose methods in the text class with a smaller number of general-purpose ones.

此外,general-purpose 会比 special-purpose approach 写更少的代码,因为special-purpose虽然提供了一些更高级的(更接近操作)的接口,但是内部的操作方法都被隐藏起来的,真正操作的方法并不会减少,只是被重新封装成了特殊目的的接口,这样会导致整个类的实现变多(隐藏的方法变多)。
如果使用了general-purpose approach可以用少量的代码低缓 special-purpose approach的长代码

A text class implemented with the general-purpose interface could potentially be used for other purposes besides an interactive editor.

通用接口可以更方便的被使用到其他的目标场景中。

Generality leads to better information hiding

The general-purpose approach provides a cleaner separation between the text and user interafce classes, which results in better information hiding.

文本类不需要关注用户界面的特性。具体的动作细节可以包装在用户界面类中。
就如同文本类不需要知道backspace按键被按下之后的动作有哪些。

The general-purpose interface also reduces cognitive load: a developer working on the user interface only needs to learn a few simple methods, which can be reused for a variety of purposes.

降低认知成本。只要学习了几个操作文本的方法,就可以实现不同的用户操作,比如删除,复制,转移等等。
如果接口封装太死了,会导致后面开发时需要重新认识一下这个接口,确认里面的细节,增加了认知成本。

One of the most important elements of software design is determining who needs to know what, and when.

软件设计最重要的元素之一是:决定谁(开发/上游)需要知道何物(业务)和何时(调用场景)。
如果细节比较重要,那就尽量让它非常明显,且易于认知。

When the details are important, it is better to make them explicit and as obvious as possible, such as the revised implementation of the backspace operation.

其实没有很好get这个小结的内容,这个小结的标题是说通用性可以通向有更好的信息隐藏。

比如第一个方案,直接由文本提供删除方法,然后用户界面直接调用,理论上这样信息泄露是最小的。因为只需要传入游标位置就可以删除游标之前的文字。方案二则需要传入更多参数,需要暴露更多的信息。
我所感知到的优势在于:
1. 提升代码复用率
2. 约束文本类涉及的能力(文本类感知的是文本的位置,而不是用户界面上游标的位置) - 这个才是我觉得通用方法更好的点

Questions to ask yourself

What is the simplest interface that will cover all my current needs?
If you reduce the number of methods in an API without reducing its overall capabilities, then you are probably creating more general-purpose methods.
Reducing the number of methods makes sense only as long as the API for each individual method stays simple; if you have to introduce lots of additional arguments in order to reduce the number of methods, then you may not really be simplifying things.

版本一中删除需要有三个方法,spaceback,delete,deleteSelection,但是版本二中只需要一个方法就能涵盖这些能力。
这种方法的减少并不会影响整体的能力。
只有在在每个独立方法中API保持简单,才会让减少方法数量的行为有道理。如果必须放入很多额外的参数来减少方法数量,那么可能并不是在简化事情。

In how many situations will this method be used?
If a method is designed for on particular use, that is a red flag that it may be too special-purpose.
See i you can replace several special-purpose methods with a single general-purpose method.

相近能力的接口是否可以合并到一个接口中,从特定方法转变成通用方法。

Is this API easy to use for my current needs?
This question can help you to determine when you have gone too far in making an API simple and general-purpose.
If you have to write a lot of additional code to use a class for youre current purpose, that's a red flag that the interface doesn't provide the right functionality.

是否在当前场景下简单易用可以让自己的方法定义得不要太偏离当前场景

比如:新增一个字段或者删除一个字段的方法,看方法本身满足的通用和简单的原则,但是当场景需要新增或删除多个文字时,需要重复调用这个方法,这对于大操作而言是非常低效的。所以需要提供一个批量新增或删除的操作给大字段使用。
上面的三个问题需要在我们定义每个方法的时候问一下自己,
这个是包含你所有场景的最简单的接口了吗? - 集中核心能力
这个方法在多少场景中被使用? - 确认方法general-purpose
在当前需求场景是否简单易用? - 确认方法的somewhat

Conclusion

They tend to be simpler, with fewer methods that are deeper. They also provide a cleaner separation between classes, whereas special-purpose interfaces tend to leak information between classes.
Making your modules somewhat general-purpose is one of the best ways to reduce overall system conplexity.

通用接口比特殊接口会更加简单(伴随着更深入的方法),同时提供了更加赶紧的拆分类的方式,鉴于特殊目的接口容易泄露信息