Design Pattern/设计模式: Part IV
设计模式以及综述
设计模式对于一名软件开发工程师来说可谓是必备的技能和知识,然而在程序员社区中也充斥着不少对于设计模式的反对之音。有人认为设计模式的知识对于工程开发至关重要,但也有许多经验丰富的程序员认为设计模式应当是长期的软件开发的积累所留下的不可名状的抽象艺术,对于市面上不少介绍设计模式的书籍嗤之以鼻。
对于刚入门的软件开发工程师而言,你若问他(包括现在的我自己)“我们为什么要使用工厂模式?使用工厂模式能够带来什么好处?”之类的问题,他也是很难回答的。所以盲目的掌握设计模式中那些拗口的名称,或者干巴巴的“方便继承”之类的描述是没有意义的。
这一系列的博客的目的并不是一蹴而就的讲解全部的设计模式知识,这样的知识没有日积月累的开发经验是很难去对其有深入的体会的。我只是希望能在这一系列的博客中,借助《图解设计模式》一书,跟随书中的顺序对其中的设计模式知识进行某种程度的理解和总结,以至于能够对诸君有所启迪,开发出更加优美和友好的代码。
我的能力有限,若是文中出现什么谬误也请诸君不吝赐教,提前感谢。
《图解设计模式》一书的作者是结城浩先生,全书共计十个部分,预计会分为十篇博客进行介绍。书中的代码为java所写,虽然java并不是我比较擅长的代码,但是为了不引起错误贻笑大方,所以还是按照书中的java进行讨论。本博客介绍其中的第四部分:分开考虑。因为每一章节中代码都占据了比较多的部分,为了更好地理解相关概念,建议阅读代码优先从Main.java的部分开始。
Chapter 9:Bridge模式–将类的功能层次结构与实现层次结构分离
在学习Bridge模式的时候,我们首先需要了解的就是如下的两个概念
- 类的功能层次结构
- 类的实现层次结构
这两个概念的共通点么就是“层次结构”这四个字了,我们使用类间的层次结构一般是为了这么几个目的,一个是希望增加一个新的功能,比如我们已经有一个Car类,现在想增加一个RunningVeryFast()/TransportingGoods()功能,一个非常容易想到的办法就是编写一个Car类的子类(我们称之为SportsCar/Truck类),这样的话就会有如下的层次结构
Car
—SportsCar: RunningVeryFast()
—Truck: TransportingGoods()
在这种情况下,我们在父类中具有一些基本的功能,而可以在子类中添加新的功能,以上的这种层次结构称为类的功能层次结构。
在另一种非常常见的情况下,我们会在抽象的父类中声明抽象方法定义接口,然后交给子类去实现这些方法,比如说我们已经有一个抽象的Shape类,在Shape类中我们声明一个抽象的Area()来计算这个Shape的面积,当然了不同的图形的面积计算方法是不同的,所以具体怎么实现则交给子类Triangle类/Square类去实现,具有如下的层次结构
Shape: Abstract-Area()
—Triangle: Area()
—Square: Area()
这里的层次结构并非是为了添加新的功能,其真正作用是为了抽象父类并提供统一接口,所以这种层次结构我们成为类的实现层次结构。
考虑到具体的工程之中,我们在编写类的时候,无疑要同时面对这两种结构,这一章节的内容就是将其分离成为两个独立的类层次结构。
例:我们有一个作为桥接的DrawAPI接口和实现了DrawAPI接口的实体类RedCircle、GreenCircle。Shape是一个抽象类,将使用DrawAPI的对象。main.java使用Shape类来画出不同颜色的圆。
//DrawAPI.java
public interface DrawAPI{
public void drawCircle(int radius, int x, int y);
}
//RedCircle.java
public class RedCircle implements DrawAPI{
@Override
public void drawCircle(int radius, int x, int y){
System.out.println("Drawing Circle[ color: red, radius: " + radius +", x: " +x+", "+ y +"]");
}
}
@Override:注意,@Override是伪代码,表示下面的这个方法从父类/接口中继承过来,需要重写。我们在程序中不写这段代码的话完全可以,但是写上的话有以下两点好处:其一,可以当作注释用,增加程序的可读性;其二,编译器会去验证@Override后面的方法是否是父类中所有的,没有的话会报错。
// GreenCircle.java
public class GreenCircle implements DrawAPI {
@Override
public void drawCircle(int radius, int x, int y) {
System.out.println("Drawing Circle[ color: green, radius: " + radius +", x: " +x+", "+ y +"]");
}
}
// Shape.java:通过DrawAPI接口(一个实现了DrawAPI接口的类)来创建抽象类Shape
public abstract class Shape {
protected DrawAPI drawAPI;
protected Shape(DrawAPI drawAPI){
this.drawAPI = drawAPI;
}
public abstract void draw();
}
// Circle.java
public class Circle extends Shape {
private int x, y, radius;
public Circle(int x, int y, int radius, DrawAPI drawAPI) {
super(drawAPI);
this.x = x;
this.y = y;
this.radius = radius;
}
public void draw() {
drawAPI.drawCircle(radius,x,y);
}
}
// Main.java
public class Main {
public static void main(String[] args) {
Shape redCircle = new Circle(100,100, 10, new RedCircle());
Shape greenCircle = new Circle(100,100, 10, new GreenCircle());
redCircle.draw();
greenCircle.draw();
}
}
这里我们可以看到,在Main.java中,我们通过Shape类声明了其子类Circle类的实例redCircle和greenCircle,这里不难看出,Shape类中我们设计了abstractd的draw()并将具体的实现交给了子类,这就是我们提到的实现的层次分离(父类抽象,子类实现),而同时我们通过DrawAPI接口,并设计了实现了这个接口的RedCircle类/GreenCircle类,在其中才进行了功能的真正实现,这也就是我们提到的功能的层次分离。
注意本例中,在Circle类中不包含具体的功能(可将功能理解为RedCircle类和GreenCircle类中的代码),只是进行了实现的层次分离,功能的具体实现交给了另外的类(RedCircle类和GreenCircle类)。
Chapter 10: Strategy模式–整体的替换算法
在程序的设计中,我们常常需要编写特定的算法来解决问题,使用Strategy模式可以整体的替换算法的实现部分,使我们能够轻松的通过不同的算法去解决同一个问题,这种模式就是Strategy模式。这种模式的核心思想就是将一系列算法一个个封装起来,并使得他们可以相互替换。
// Strategy.java
public interface Strategy{
public int doOperation(int num1, int num2);
}
// 注意,这里Strategy接口的定义至关重要,因为所有被封装的算法的输入和输出都是在这个接口中决定的
// OperationAdd.java: Algorithm 1
public class OperationAdd implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}
// OperationSubstract.java: Algorithm 2
public class OperationSubstract implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 - num2;
}
}
// OperationMultiply.java: Algorithm 3
public class OperationMultiply implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 * num2;
}
}
// Context.java: 是负责使用不同算法组件的类
public class Context {
private Strategy strategy;
public Context(Strategy strategy){
this.strategy = strategy;
}
public int executeStrategy(int num1, int num2){
return strategy.doOperation(num1, num2);
}
public SetStrategy(Strategy strategy){
this.strategy = strategy;
}
}
// Main.java
public class Main {
public static void main(String[] args) {
Context context = new Context(new OperationAdd());
System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
context = new Context(new OperationSubstract());
System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
context = new Context(new OperationMultiply());
System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
// 我们也可以使用以下的方式
// Context context = new Context();
// context.SetStrategy(new OperationAdd());
// System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
// context.SetStrategy(new OperationSubstract());
// System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
// context.SetStrategy(new OperationMultiply());
// System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
}
}