Problem:
How to handle alternative based on types? 如何处理基于变化的类型?
How to create pluggable software components? 如何创建可插拔的组建?
Alternatives based on type Conditional variation is a fundamental theme in programs. If a program is designed using if-then-else or case statement conditional logic, then if a new variation arises, it requires modification of the case logic often in many places. This approach makes it difficult to easily extend a program with new variations because changes tend to be required in several places wherever the conditional logic exists.
Pluggable software components Viewing components in client-server relationships, how can you replace one server component with another, without affecting the client?
基于类型条件变化的选择是程序设计的一个基本主题. 如果一个程序是基于if-then-else 或者 case 条件逻辑设计的, 那么如果一个新的变化出现了, 它需要改动条件语句的好几个地方. 这种方法使得程序在应对新的改变的时候变得困难因为每次新改变都需要在原有逻辑的基础上改动数个地方.
可插拔程序组件主要负责监视客户端服务器之间组件的关系, 如何在不影响客户端的情况下替换服务器的组件.
Solution
When related alternatives or behaviors vary by type (class),
Assign responsibility for the hehaviorousing polymorphic operations to the types for which the behavior varies.
当有关的选择或行为由类型改变时, 针对行为多态的操作给行为发生变化的类型分配责任.
Guideline
Unleass there is a default behavior in the superclass, declare a polymorphic operation in the super class to be abstract.
除非在超类中具有默认的行为, 否则将超类中的多态方法声明为abstract.
When to design with interfaces?
Polymorphism implies the presence of abstract superclasses or interfaces in most OO languages. When should you consider using an interface? The gerenral answer is to introduce one when you want to support polymorphism without being committed to a particular class hierarchy. If an abstract superclass AC is used without an interface, any new polymorphic solution must be a subclass of AC, which is very limited in single-inheritance languages such as java or c#. As a rule-of-thumb, if there is a class hierarchy with an abstract superclass C1, consider making an interface I1 that corresponds to the public methods signatures of C1, and then declare C1 to implement the I1 interface. Then, even if there is no immediate motivation to avoid subclassing under C1 for a new polymorphic solution, there is a flexible evolution point for unknown future cases.
多态意味着大部分OO语言中要使用抽象超类或接口. 何时应该考虑使用接口呢? 普遍的答案是, 当你想要支持多态但是又不想约束于特定的类层次结构时, 可以使用接口. 如果使用了抽象超类AC而不是接口, 那么任何新的多态方案都必须是AC的子类, 这对于Java或C#等单根继承的语言来说十分局限. 经验的做法是, 如果有一个具有抽象超类C1的类层次结构, 可以考虑对应于C1的公共方法特征标记来定义接口I1, 然后声明C1来实现接口I1.这样, 对于新的多态方案, 几时没有避免使用C1子类的直接动机, 也能够获得灵活的进化点, 用来应对将来未知的情况.
Benefits
l Extensions required for new variations are easy to add.
l New implementions can be introduced without affecting clients.
Problem
What object should have the responsbility, when you do not want to violate high cohesion and low coupling, or other goals, but solutions offered by Expert(for example) are not appropriate?
当你并不想违背高内聚和低耦合或其他目标, 但是基于专家模式所提供的方案又不合适是, 那些对象应该承担这一职责?
Object-oriented designs are sometimes characterized by implementing as software classes representations of concepts in the real-world problem domain to lower representational gap; For example a Sale and Customer class. However, there are many situtations in which assigning responsiblities only to domain layer software classes leads to problems in terms of poor cohesion or coupling, or low reuse potentional.
面向对象设计有时会被描述为: 实现软件类, 使其表示真实世界问题领域的概念, 以降低表示差异, 例如Sale和Customer类. 然而, 在很多情况下, 只对领域层对象分配职责会导致不良内聚或耦合, 或者会降低复用潜力.
Solution
Assign a highly cohesive set of responsbilities to an atificial or convenience class that does not represent a problem domain concept. Something made up, to support high cohesion, low coupling, and reuse.
对人为制造的类分配一组高内聚的职责, 该类并不代表问题领域的概念-虚构的事物, 用以支持高内聚, 低耦合和复用.
Such a class is a Fabrication of the imagination. Ideally, the responsibilities assigned to this fabrication support high cohesion and low coupling, so the design of the fabrication is very clean, or pure, hence a pure fabrication.
这种类是凭空虚构的, 理想情况下, 分配给这种虚构物的职责要支持高内聚和低耦合, 使这种虚构物清晰或纯粹, 因此称为虚构物.
Finally, in english pure fabrication is an idiom that means making something up, which we do when we are desperate!
最后, 在英文中, 纯虚构这一习语的含义是: 当我们穷途末路时所捏造的事物.
A reasonable solution is to create a new class that is solely responsible for saving objects in some kind of persistent storage medium, such as a relational database; Call it the PersistentStorage. This class is pure fabrication figment of imagination.
合理的方案是创建一个新类,使其独自负责在某种持久性存储介质(例如关系数据库)中保存对象, 对该类命门为PersistentStorage. 该类是纯虚构的臆想之物.
In a real persistent framework, more than a single pure fabrication class is ultimately necessary to create a reasonable design. This object will be a front-end façade on to a number of back-end help classes.
在实际的持久层框架中, 为了创建合理设计, 最终会需要不止一个纯虚构类. 该对象将成为针对大量后台帮助者类的前台外观.
Notice the name: PersistentStorage. This is an understandable concept, yet the name or concept ‘Persistent Storage’is not something one would find in the domain model. And if a designer asked a business-person in a store, ‘Do you work with persistent storage object?’, They would not understand. They understand to concepts such as ‘sale’ and ‘payment’. PersistentStorage is not a domain concept, but something made up or fabricated for the convenience of the software developer.
注意其名称:PersistentStorage. 虽然这个概念是可以被理解的, 但是我们却无法在领域模型中找到这个名称或”持久性存储”的概念. 并且如果开发者询问商店的业务人员:”你使用持久性存储对象吗?”, 他们不会理解. 他们能够理解诸如”销售”和”支付”的概念.而PersistenceStorage并不是领域概念, 它只是为了便于软件开发而凭空捏造或虚构的概念.
Note that, as with all the GRASP patterns, the emphasis is on where responsibilities should be placed. In this example, the responsibilities are shifted from the Sale class(motivated by Expert) to a Pure Fabrication.
需要注意的是, 在所有GRASP模式中, 强调的是责任应该分配给谁. 在这个例子中, 责任从Sale类转到了一个纯虚构类上.
As a final comment worth reiteration: Sometimes a solution offered by Information Expert is not desirable. Even though the object is a candidate for the responsibility by virtue of having much of the information related to the responsbility, in other words, its choice leads to a poor design, usually due to problems in cohesion or coupling.
作为最后一个值得反复强调的评论: 有时信息专家提供的解决方法是不可取的.尽管这样的对象作为一个候选拥有很多同责任相关的信息, 但另一方面, 这个选择通常会因为内聚和耦合方面的问题会导致一个糟糕的设计.
Benefits
High Cohesion is surpported because responsibilities are factored in to a fine-grained class that only focuses on a very specific set of related tasks.
支持高内聚,因为职责被解析为细粒度的类, 这种类只着重于极为特定的一组相关任务.
Reuse Potential may increase because of the presence of fine-grained Pure Fabrication classes whose responsibilities have applicability in other applications.
增加了潜在的复用性, 因为细粒度纯虚构类的职责适用于其它应用.
Taboo
Behavioral decomposition into Pure Fabrication objects is sometimes overused by those new to object design and more familiar with decomposing or organizing software in terms of functions. To exaggerate,functions just become objects. There is nothing inherently wrong with creating ‘function’ or ‘algorithm’ objects, but it needs to be balanced with the ability to design with representational decomposition, such as the ability to apply Information Expert so that a representional class such as Sale also has responsibilities. Information Expert supports the goal of co-locating responsibilities with the objects that know the information needed for those responsibilities, which tends to support lower coupling. If overused, Pure Fabrication could lead to too many behavior objects that have responsibilities not co-located with the information required for their fulfillment, which can adversely affect coupling. The usual symptom is that most of the data inside the objects is being passed to other objects to reason with it.
那些对象设计初学者和更熟悉以功能组织和分解软件的人有时会滥用行为分析及纯虚构对象. 夸张的是, 功能正好变成了对象. 创建”功能”或”算法”对象本身并没有错, 但是这需要平衡于表示解析设计的能力(例如应用信息专家的能力), 这样便能够使诸如Sale等表示类同样具有职责. 信息专家所支持的目标是, 将职责与这些职责所需信息结合起来赋予同一个对象, 以实现对低耦合的支持. 如果滥用纯虚构, 会导致大量行为对象, 其职责与执行职责所需的信息并没有结合起来, 这样会对耦合产生不良影响, 其通常征兆是, 对象内的大部分数据被传递给其它对象用以处理.
Problem
Where to assign a responsibility, to avoid direct coupling between two(or more) things? How to decouple objects so that low coupling is surpported and reuse potential remains higher?
为了避免多个组件之间直接耦合, 应该如何分配职责? 如何解耦以使之支持低耦合且保持高的复用性潜力?
Solution
Assign the responsibility to an intermediate object to mediate between other components or services so that they are not directly coupled.
将责任给在不同的组建或服务之间的中间件, 以使各组件时间不是直接耦合关联.
The intermediary creates an indirection between the other components.
中间件构成了组件间的间接性.
PersistentStorage
The Pure Fabrication example of decoupling the Sale from the relational database services through the introduction of a PersistentStorage class is also an example of assigning responsibilities to support Indirection. The PersistentStorage acts as a intermediary between the Sale and the database.
在纯虚构的例子中通过引入PersistentStorage类来实现对Sale类关系数据库间的解耦. 这也是一个通过将职责分配给中间件达到间接实现的例子. PersistentStorage在这里就扮演了一个Sale类和数据库之间的中间件角色.
Benefits
Lower coupling between components.
降低组件间的耦合性.
Problem
How to design objects, subsystems, and systems so that the variations or instability in these elements does not have an undesirable impact on other elements?
如何设计对象,子系统, 系统, 时它们之间的变化和不稳定性不会对其他组件产生不良影响?
Solution
Identify points of predicted variation or instability; assign responsibilities to create a stable interface around them.
识别预计变化和不稳定之处, 将职责分配给在这些变化之外层的稳定接口上.
Nothe: The term ‘interface’ is used in the broadest sense of an access view; it does not literally only mean something like a Java interface.
提示: 术语”接口”在这里指的是一个更宽泛的含义, 它不是仅仅指在Java或C#语言中专有的那些.
Mechanisms Motivated by Protected Variations
Protected Variations is a root principle motivating most of the mechanisms and patterns in programming and design to provide flexibility and protection from variations. Variations in data, behavior, hardware, software components, operating systems, and more.
防止变异是一个根本原则, 它促成了促成了大部分的编程机制和设计模式, 用来提供灵活性和房子变化. 这些变化包括数据, 行为, 硬件, 软件组件, 操作系统, 等等…
Liskov Substitution Principle(LSP)
LSP formalizes the principle of protection against variations in different implementations of an interface, or subclass extensions of a superclass.
LSP为实现了某个接口或一个类的子类来达到防止变异的效果提供了原则.
The LSP can be paraphrased as follows:
LSP可以用如下定义来解释:
Subtypes must be substitutable for their base types.
子类型对于基础类型来说必须是可替换的.
What is wanted here is something like the following substitution property: If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.
LSP可以简单的解释为: 如果对于类型S的每个对象O1存在类型T的对象O2, 那么对于所有定义了T的程序P来说, 当用O1替换O2并且S为T的子类型时, P的行为不会发生改变.
A Simple Example of a Violation of the LSP
package com.cyh.sample.lsp;
enum ShapeType {
SQUARE, CICLE
}
abstract class Shape {
private ShapeType shapeType;
Shape(ShapeType shapeType) {
this.shapeType = shapeType;
}
ShapeType getShapeType() {
return shapeType;
}
void setShapeType(ShapeType shapeType) {
this.shapeType = shapeType;
}
}
class Cicle extends Shape {
int center, radius;
Cicle(ShapeType shapeType) {
super(shapeType);
}
void draw() {
System.out.println("draw cicle in Cicle class.");
}
}
class Square extends Shape {
int coordinate, sideWidth;
Square(ShapeType shapeType) {
super(shapeType);
}
void draw() {
System.out.println("draw cicle in Square class.");
}
}
public class DrawTest {
public DrawTest(Shape shape) {
this.drawShape(shape);
}
void drawShape(Shape shape) {
String shapeType = shape.getShapeType().toString();
if (ShapeType.CICLE.toString().equals(shapeType)) {
((Cicle) shape).draw();
} else if (ShapeType.SQUARE.toString().equals(shapeType)) {
((Square) shape).draw();
}
}
public static void main(String[] args) {
new DrawTest(new Square(ShapeType.SQUARE));
}
}
Consider Joe the Engineer. Joe has studied object-oriented technology and has come to the conclusion that the overhead of polymorphism is too high to pay. Therefore, he defined class Shape without any virtual functions. The classes Square and Circle derive from Shape and have Draw() functions, but they dont overide a function in Shape. Since Cicle and Square are not substitutable for Shape, DrawShape must inspect its incoming Shape, determine its type, and then call the appropriate Draw() function.
The fact that Square and Circle cannot be substituted for Shape is a violation of the LSP. This violation forced the violation of the OCP by drawShape(). Thus, a violation of LSP is a latent violation of OCP.
Square and Rectangle, a More Subtle Violation
package com.cyh.sample.lsp;
public class AreaTest {
private int getArea(Rectangle rectangle) {
int width = rectangle.getWidth();
int height = rectangle.getHeight();
return width * height;
}
}
class Rectangle {
private int pointX, pointY;
private int width, height;
int getHeight() {
return height;
}
void setHeight(int height) {
this.height = height;
}
int getWidth() {
return width;
}
void setWidth(int width) {
this.width = width;
}
}
class Square extends Rectangle {
void setWidth(int width) {
super.setWidth(width);
super.setHeight(width);
}
void setHeight(int height) {
super.setHeight(height);
super.setHeight(height);
}
}
Square and Rectangle now appear to work. No matter what you do to a Square object, it will remain a mathematical rectangle. Moreover, you can pass a Square into a function that accepts a pointer or reference to a Rectangle, and the Square will still act like a square and will remain consistent.
Thus, we might conclude that the design is now self-consistent and correct. However, this conclusion would be amiss. A design that is self-consistent is not necessarily consistent with all its users! Consisder the following function:
private int calcArea(Rectangle rectangle) {
int width = rectangle.setWidth(4);
int height = rectangle.setHeight(5);
assert(r.getArea() == 20);
}
This function invokes the setWidth() and setHeight() memebers of what it believes to be a Rectangle. The function works just fine for a Rectangle, but it declares an assertion error if passed a Square. So here is the real problem: The author of calcArea() assumed that changing the width of Rectangle leaves its height unchanged.
Function clacArea() shows that there exist functions that take pointers or references to Rectangle objects, but that can not operate properly on Square objects. Since, for these functions, Square is not substitutable for Rectangle, the relationship between Square and Rectangle violates the LSP.
Open-Closed Principle
The Open-Closed Principle(OCP), described by Bertrand Meyer, is essentially equivalent to the PV pattern and to Information Hiding. A definition of OCP is:
Modules should both open(for extension; adaptable) and closed(the module is close for modification the ways that affect clients).
OCP and PV are essentially two expressions of the same principle, with different emphasis: protection at variation and evolution points. In OCP, module includes all discrete software elements, including methods, classes, subsystems, applications, and so forth.