软件构造系列学习笔记(6.2)————可维护性设计模式

可维护性设计模式

目录

  • 创造性模式
  • 结构化模式
  • 行为化模式

创造性模式

创造性模式(Creational patterns)一共分为三种模式,分别是:

  • 工厂方法模式:创建对象,而不指定要创建的确切类。
  • 抽象工厂模式:将具有共同主题的对象工厂分组。
  • Builder模式:通过分离构造和表示来构造复杂的对象。

下面对这三种模式一一进行描述

工厂方法模式(Factory Method pattern)

工厂方法模式也被称为虚拟构造器。当client不知道要创建哪个具体类的实例,或者不想在client代码中指明要具体创建的实例时,用工厂方法。 定义一个用于创建对象的接口,让其子类来决定实例化哪一个类,从而使一个类的实例化延迟到其子类。

软件构造系列学习笔记(6.2)————可维护性设计模式_第1张图片

下面来看一个具体例子:
首先定义一个接口,可视为Abstract product

public interface Trace{
    // turn on and off debugging 
    public void setDebug( boolean debug ); 
    // write out a debug message 
    public void debug( String message ); 
    // write out an error message 
    public void error( String message );
}

接着实现这个接口,这个接口下有两个具体的product
Concrete product 1:

public class FileTrace implements Trace {          
    private PrintWriter pw; 
    private boolean debug; 
    public FileTrace() throws IOException { 
        pw = new PrintWriter( new FileWriter( "t.log" ) ); 
    } 
    public void setDebug( boolean debug ) { 
        this.debug = debug; 
    } 
    public void debug( String message ) { 
        if( debug ) { 
            pw.println( "DEBUG: " + message ); 
            pw.flush(); 
        } 
    } 
    public void error( String message ) { 
        pw.println( "ERROR: " + message ); 
        pw.flush(); 
    } 
}

Concrete product 2

public class SystemTrace implements Trace { 
    private boolean debug; 
    public void setDebug( boolean debug ) { 
        this.debug = debug; 
    } 
    public void debug( String message ) { 
        if( debug ) 
            System.out.println( "DEBUG: " + message ); 
    } 
    public void error( String message ) { 
        System.out.println( "ERROR: " + message ); 
    } 
}

客户端代码:

//... some code ... 
Trace log = new SystemTrace(); 
log.debug( "entering log" );
Trace log2 = new FileTrace(); 
log.debug(“...”);

这么写会使得客户代码与具体产品紧密结合。如果采用工厂方法模式,可将两者分离开。
首先定义工厂的接口

interface TraceFactory { 
    public Trace getTrace(); 
    public Trace getTrace(String type); 
    void otherOperation(){}; 
}

不仅包含 factory method, 还可以实现其他功能。
接下来具体实现工厂,每一个具体产品对应一个工厂。

public class Factory1 implements TraceFactory { 
    public Trace getTrace() { 
        return new SystemTrace(); 
    } 
}
public class Factory2 implements TraceFactory { 
    public getTrace(String type) { 
        if(type.equals(“file”) 
            return new FileTrace(); 
        else if (type.equals(“system”) 
            return new SystemTrace(); 
    } 
}

根据类型创建哪个具体产品,有新的具体产品类加入时,可以在工 厂类里修改或增加新的工厂函数 (OCP),不会影响客户端代码。

客户端:

Trace log1 = new Factory1().getTrace(); 
log1.setDebug(true); 
log1.debug( "entering log" ); 
Trace log2 = new Factory2().getTrace("system"); 
log2.setDebug(false); 
log2.debug("...");

Client使用 “工厂方法” 来创建实例, 得到实例的类型是抽象接口而非具体类。

除此之外,还有静态工厂方法,它既可以在ADT 内部实现,也可以构造单独的工厂类。

public class TraceFactory1 { 
    public static Trace getTrace() { 
        return new SystemTrace(); 
    } 
}
public class TraceFactory2 { 
    public static Trace getTrace(String type) { 
        if(type.equals(“file”) 
            return new FileTrace(); 
        else if (type.equals(“system”) 
            return new SystemTrace(); 
    } 
}

客户端可以这样调用:

//... some code ... 
Trace log1 = TraceFactory1.getTrace(); 
log1.setDebug(true); 
log1.debug( "entering log" );
Trace log2 = TraceFactory2.getTrace(“system”); 
log2.setDebug(true); 
log2.debug(“...”);

优点:

  • 消除了将特定于应用程序的类绑定到代码的需要。
  • 代码仅处理产品接口(Trace),因此它可以与任何用户的ConcreteProduct(FileTraceSystemTrace)一起使用。

抽象工厂模式(Abstract Factory)

考虑两个实际应用场景:

  • 一个UI,包含多个窗口控件,这些控件在不同的OS中实现不同
  • 一个仓库类,要控制多个设备,这些设备的制造商各有不同,控制接口有差异

我们可以使用抽象工厂模式处理这种情况。
抽象工厂模式:提供接口以创建一组相关/相互依赖的对象, 但不需要指明其具体类。

以下面窗口滚动条为例:
客户端想要一个产品,由窗口和滚动条组成。于是可以交给一个抽象工厂来做,这个工厂负责将产品的组件组装起来成一个完整的产品。不同的产品继承这个抽象工厂接口,实现自己的工厂方法。
软件构造系列学习笔记(6.2)————可维护性设计模式_第2张图片

下面是具体的实现,贴出一部分代码
软件构造系列学习笔记(6.2)————可维护性设计模式_第3张图片

客户端根据要创建的“组合产品”的类型, 构建不同的抽象工厂子类,然后再利用辅助类进行具体构建。
软件构造系列学习笔记(6.2)————可维护性设计模式_第4张图片

创建的不是一个完整产品,而是“产品族”(遵循 固定搭配规则的多类产品的实例),得到的结果是:多个不同产品的 object,各产品创建过程对client可见,但“搭配”不能改变。

本质上,Abstract Factory是把多类产品的factory method组合在一起。

构造器模式(Builder)

构造器模式:创建复杂对象,包含多个组成部分 。
例如:将文档转换为多种不同的格式 。写出文档的步骤是相同的,但每个步骤的细节取决于格式。
就像你在麦当劳点一种组合食品一样。
软件构造系列学习笔记(6.2)————可维护性设计模式_第5张图片

注意:client要的不是一堆零散的objects (abstract factory那样的结果),而是一个完整的产品,client不关心其中的细节组成部分是什么、如何创建。

具体UML图如下:
软件构造系列学习笔记(6.2)————可维护性设计模式_第6张图片

具体例子如下:
我们需要一个Pizza的产品,该Pizza产品的part是以三个属性的形式体现,其builder就相当于给三个属性赋值(也可更复杂)。

/* "Product" */ 
class Pizza { 
    private String dough = ""; 
    private String sauce = ""; 
    private String topping = "";
    public void setDough(String dough) { 
        this.dough = dough; 
    } 
    public void setSauce(String sauce) { 
        this.sauce = sauce; 
    } 
    public void setTopping(String topping) { 
        this.topping = topping; 
    }
}

Builder抽象类有三个抽象方法,分别创建三个 part。

/* "Abstract Builder" */ 
abstract class PizzaBuilder { 
    protected Pizza pizza; 
    public Pizza getPizza() { 
        return pizza; 
    } 
    public void createNewPizzaProduct() { 
        pizza = new Pizza(); 
    } 
    public abstract void buildDough(); 
    public abstract void buildSauce(); 
    public abstract void buildTopping(); 
}

具体的builder子类中,override三个抽象方法,分别构建三个parts,这里我们实现两个不同的builder子类,构建三个parts的内容有差异。

/* "ConcreteBuilder 1" */ 
class SpicyPizzaBuilder extends PizzaBuilder { 
    public void buildDough() { 
        pizza.setDough("pan baked"); 
    } 
    public void buildSauce() { 
        pizza.setSauce("hot"); 
    } 
    public void buildTopping() { 
        pizza.setTopping("pepperoni+salami"); 
    } 
}
/* "ConcreteBuilder 2" */ 
class HawaiianPizzaBuilder extends PizzaBuilder { 
    public void buildDough() { 
        pizza.setDough("cross"); 
    } 
    public void buildSauce() { 
        pizza.setSauce("mild"); 
    } 
    public void buildTopping() { 
        pizza.setTopping("ham+pineapple"); 
    } 
}

Director中定义要delegate的builder

/* "Director" */ 
class Waiter { 
    private PizzaBuilder pizzaBuilder; 
    public void setPizzaBuilder(PizzaBuilder pb) { 
        pizzaBuilder = pb; 
    } 
    //得到最终builder的结果
    public Pizza getPizza() { 
        return pizzaBuilder.getPizza(); 
    } 
    //在这里具体构建各部分
    public void constructPizza() { 
        pizzaBuilder.createNewPizzaProduct(); 
        pizzaBuilder.buildDough(); 
        pizzaBuilder.buildSauce(); 
        pizzaBuilder.buildTopping(); 
    } 
}

Client使用不同的builder子类创建不同的产品

/* A customer ordering a pizza. */ 
public class PizzaBuilderDemo { 
    public static void main(String[] args) { 
        Waiter waiter = new Waiter(); 
        PizzaBuilder hawaiianPizzabuilder = new HawaiianPizzaBuilder(); 
        PizzaBuilder spicyPizzaBuilder = new SpicyPizzaBuilder();

        //建立waiter和builder之间的delegation关系
        waiter.setPizzaBuilder( hawaiianPizzabuilder ); 
        //Bingo! 构建细节完全对client隐藏
        waiter.constructPizza(); 
        Pizza pizza = waiter.getPizza();
    }
}

Comparison: Abstract Factory vs Builder

Abstract Factory创建的不是一个完整产品,而是“产品族”(遵循 固定搭配规则的多类产品实例),得到的结果是:多个不同产品的实例object,各产品创建过程对client可见,但“搭配”不能改变。

Builder创建的是一个完整的产品,有多个部分组成,client不需了解每个部分是怎么创建、各个部分怎么组合,最终得到一个产品的完整 object 。

Comparison: Template Method vs Builder

Template Method: a behavioral pattern 目标是为了复用算法的公共结构(次序)。

  • 定义了一个操作中算法的骨架(steps),而将具体步骤的实现延迟到子类中, 从而复用算法的结构并可重新定义算法某些特定步骤的实现逻辑。
  • 复用算法骨架,强调步骤的次序
  • 子类override算法步骤

Builder: a creationalpattern 目标是“创建复杂对象”,灵活扩展

  • 将一个复杂对象的构造方法与对象内部的具体表示分离出来,同样的构造方法可以建立不同的表现。
  • 不强调复杂对象内部各部分的“次序”
  • 子类override复杂对象内部各部分的“创建”
  • 适应变化:通过派生新的builder来构造新的对象(即新的内部表示),OCP

结构化模式

桥接模式(Bridge)

Bridge是OOP最基本的structural pattern,通过delegation+inheritance 建立两个具体类之间的关系(DIP依赖转置,抽象依赖于抽象)。
软件构造系列学习笔记(6.2)————可维护性设计模式_第7张图片

例子:
各种Shape,希望具备绘图功能, delegate到专门的绘图。

Createbridge implementerinterface:

//绘图类的抽象接口
public interface DrawAPI { 
    public void drawCircle(int radius, int x, int y); 
}

Create concrete bridge implementer classes:

//绘图类的各种实现
public class DrawRedCircle implements DrawAPI { 
    @Override 
    public void drawCircle(int radius, int x, int y) { 
        System.out.println(“Color: red " + radius + x + y); 
    } 
}
public class DrawGreenCircle implements DrawAPI { 
    @Override 
    public void drawCircle(int radius, int x, int y) {
        System.out.println(“Color: green " + radius + x + y); 
    } 
}

Create an abstract class Shape using the DrawAPI interface:

public abstract class Shape { 
    //永久保存delegation并在其他场合使用
    protected DrawAPI drawAPI; 
    //动态传入,建立delegation link
    protected Shape(DrawAPI drawAPI){ 
        this.drawAPI = drawAPI; 
    } 
    public abstract void draw(); 
} 

Create concrete class implementingthe Shape interface

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; 
    } 
    //将draw这个责任delegate到drawAPI这个接口的具体实例去执行
    public void draw() { 
        drawAPI.drawCircle(radius,x,y); 
    }
}

Use the Shape and DrawAPI classes to draw different colored circles

public class BridgePatternDemo {
    public static void main(String[] args) { 
        //Circle是上面左侧继承树中的子类
        //DrawRedCircle 和 DrawGreenCircle右侧继承树中的子类
        //运行时在二者之间动态建立delegation关系
        Shape redCircle = new Circle(100,100, 10, new DrawRedCircle()); 
        Shape greenCircle = new Circle(100,100, 10, new DrawGreenCircle());

        //这里调用draw()已经不再是本模式关注的重点了
        redCircle.draw(); 
        greenCircle.draw();
    }
}

Bridge vs. Strategy

Bridge: structural pattern 强调双方的run time delegation linking :

一个类A的对象中有其他类B的对象作为其组成部分,但A的对象具体绑定到B的哪个具体子类的实现?在运行时通过delegation加以组合, 并永久保存这种delegation关系。

Bridge强调结构关系的动态绑定,举例来说,类A需要完成“通讯”功能,它有一个组成部分Device类,这是个抽象接口(模式中的implementor),有多种实现方式(PC, tablet, phone,即ConcreteImplementor),该模式在运行时为类A的具体子类型实例设定具体的Device的具体实现(强调对A和Device两个抽象类的各自实例之间如何建立delegation),进而在其他各项功能中利用其通讯功能(这不再是bridge关注的目标)。

Strategy: behavioral pattern 强调一方run-time使用另一方的“算法” :

“算法”通常实现为“类的某个方法”的形式,strategy的目的并非在“调用算法的类”与“被调用算法所在的类”之间建立起永久联系,而只是帮助前者临时使用后者中的“算法”,前者无需永久保存后者的实例。

举例子来说,Strategy强调算法的动态调用,但前提是动态绑定。使用螺丝刀的时候,针对不同的工作任务,选取不同的“刀头”,但目的并非将 螺丝刀与刀头组合起来建立永久的delegation, 而只是临时通过delegation完成任务(即调用刀 头的“算法”),然后二者再无联系。

代理模式(Proxy)

针对的情况是某个对象比较“敏感”/“私密”/“贵重”,不希望被client直接访问到,故设置proxy,在二者之间建立防火墙。

软件构造系列学习笔记(6.2)————可维护性设计模式_第8张图片

例子:
软件构造系列学习笔记(6.2)————可维护性设计模式_第9张图片

软件构造系列学习笔记(6.2)————可维护性设计模式_第10张图片

软件构造系列学习笔记(6.2)————可维护性设计模式_第11张图片

Proxy vs. Adaptor

Adaptor: structural pattern
目的:消除不兼容,目的是B以客户端期望的统一的方式与A建立起联系。

Proxy: behavioral pattern
目的:隔离对复杂对象的访问,降低难度/代价,定位在“访问/使用行为”。

组合模式( Composite)

意图:将对象组成树结构来表示整体部分的层次结构。

软件构造系列学习笔记(6.2)————可维护性设计模式_第12张图片

例子:

public class Employee { 
    private String name; 
    private List subordinates;
    public Employee(String name) { 
        this.name = name; 
        subordinates = new ArrayList(); 
    } 
    public void add(Employee e) { 
        subordinates.add(e); 
    } 
    public void remove(Employee e) { 
        subordinates.remove(e); 
    } 
    public List getSubordinates(){ 
        return subordinates; 
    } 
    public String toString(){
        …
    }   
}
Employee CEO = new Employee("John"); 
Employee headSales = new Employee("Robert"); 
Employee clerk1 = new Employee("Laura"); 
Employee clerk2 = new Employee("Bob");

CEO.add(headSales); 
CEO.add(headMarketing); 
clerk1.add(clerk1); 
clerk1.add(clerk2);

System.out.println(CEO); 
for (Employee headEmployee : CEO.getSubordinates()) {
    System.out.println(headEmployee); 
    for (Employee employee : headEmployee.getSubordinates()) { 
        System.out.println(employee); 
    } 
}

Composite vs Decorator

Composite: structural pattern
目的是在同类型的对象之间建立起树型层次结构,一个上层对象可包含多个下层对象 。

Decorator: structural pattern
强调的是同类型对象之间的“特性增加”问题, 它们之间是平等的,区别在于 “拥有特性”的多少,每次decoration 只能作用于一个object。

行为化模式(Behavioral patterns)

Observer

举例子来说,“粉丝”对“偶像”感兴趣,希望随时得知偶像的一举一动 ,因此粉丝可以到偶像那里注册,偶像一旦有新闻发生,就推送给已注册的粉丝 (回调callback粉丝的特定功能)。

软件构造系列学习笔记(6.2)————可维护性设计模式_第13张图片

软件构造系列学习笔记(6.2)————可维护性设计模式_第14张图片

软件构造系列学习笔记(6.2)————可维护性设计模式_第15张图片

软件构造系列学习笔记(6.2)————可维护性设计模式_第16张图片

Visitor

对特定类型的object的特定操作(visit),在运行时将二者动态绑定到一起,该操作可以灵活更改,无需更改被visit的类。本质上:将数据和作用于数据上的某种/些特定操作分离开来。
软件构造系列学习笔记(6.2)————可维护性设计模式_第17张图片

软件构造系列学习笔记(6.2)————可维护性设计模式_第18张图片

软件构造系列学习笔记(6.2)————可维护性设计模式_第19张图片

Visitor vs Iterator

Iterator: behavioral pattern
迭代器:以遍历的方式访问集合数据而无需暴露其内部表示,将“遍历”这项功能delegate到外部的iterator对象。

Visitor: behavioral pattern
在特定ADT上执行某种特定操作,但该操作不在ADT内部实现,而是delegate到独立的visitor对象,客户端可灵活扩展/改变visitor的操作算法,而不影响ADT。

Strategy vs visitor

二者都是通过delegation建立两个对象的动态联系

  • 但是Visitor强调是的外部定义某种对ADT的操作,该操作于ADT自身关系不大(只是访问ADT),故ADT内部只需要开放accept(visitor)即可,client 通过它设定visitor操作并在外部调用。
  • 而Strategy则强调是对ADT内部某些要实现的功能的相应算法的灵活替换。 这些算法是ADT功能的重要组成部分,只不过是delegate到外部strategy类而已。

区别:visitor是站在外部client的角度,灵活增加对ADT的各种不同操作(哪怕ADT没实现该操作),strategy则是站在内部ADT的角度, 灵活变化对其内部功能的不同配置。

Mediator

多个对象之间要进行交互,不是直接交互,而是通过mediator,实现消息的广播,从而将对象之间松散耦合,利于变化 。

软件构造系列学习笔记(6.2)————可维护性设计模式_第20张图片

软件构造系列学习笔记(6.2)————可维护性设计模式_第21张图片

软件构造系列学习笔记(6.2)————可维护性设计模式_第22张图片

软件构造系列学习笔记(6.2)————可维护性设计模式_第23张图片

Observer vs Mediator

Observer pattern:
一组object对另一个object B的状 态变化感兴趣(1对多),所以对其进行观察。B维持着一个对自己感 兴趣的object list,一旦自己发生变化,就通知这些object。双方地位 并不对等,一方只是“通知”,另一方接到通知后执行特定行为。

Mediator pattern:
一组同类型的object,彼此之间相互 发/收消息(多对多),不存在谁观察谁的问题,所有object都对等。 每个object都维持一个mediator,将通讯的任务delegate给它,自己只 负责send和receive即可,无需了解其他object,实现了“解耦”。

Observer:自己来广播,其他对象接收;
Mediator:第三方中介负责广播(类似于“邮件列表”)。

Command

将“指令”封装为对象,指令的所有细节对client隐藏,在指令 内对具体的ADT发出动作(调用ADT的细节操作),解决的问题是客户端希望执行指令,但不想知道指令的 细节,也不想知道指令的具体作用对象。

软件构造系列学习笔记(6.2)————可维护性设计模式_第24张图片

将所有对client提供的指令与内部执行的ADT操作彻底分开,指令对外看来是“黑盒”——进一 步“变本加厉”的封装!

软件构造系列学习笔记(6.2)————可维护性设计模式_第25张图片

软件构造系列学习笔记(6.2)————可维护性设计模式_第26张图片

软件构造系列学习笔记(6.2)————可维护性设计模式_第27张图片

Façade vs. Command

均强调对某个复杂系统内部提供的功能的“封装”,对外提供简单的 调用接口,简化client的使用,“隐藏”细节 。

Command:强调将指令封装为了“对象”,提供了统一的对外接口 (execute) 。
Façade:没有显式的“对象”,仍然通过类的方法加以调用。

Chain of responsibility (职责链模式)

针对一个请求,可能有多个处理模块 。各种不确定情况存在,不能以“硬编码”的方式指明按何种次序调用处理模块 。

避免在请求方和各handler之间的紧耦合:构造流水线,请求在其上传递,直到被处理为止。

客户端只需在流水线的入口发出请求即可,请求自动在流水线上传递,直到被处理。
软件构造系列学习笔记(6.2)————可维护性设计模式_第28张图片

处理器对象的数目和类型事先都不确定,需要动态配置。使用递归组合的方式,将多个 handlers连成“职责链” 。请求的发起者无需维护所有可能的handler对象,只需记录职责链的 入口;每个handler只需要维护“下一个”handler即可,从而简化了各 handlers之间的连接关系。

软件构造系列学习笔记(6.2)————可维护性设计模式_第29张图片

软件构造系列学习笔记(6.2)————可维护性设计模式_第30张图片

软件构造系列学习笔记(6.2)————可维护性设计模式_第31张图片

软件构造系列学习笔记(6.2)————可维护性设计模式_第32张图片

软件构造系列学习笔记(6.2)————可维护性设计模式_第33张图片

Visitor vs. Chain of Responsibility

不是像传统类设计中将操作与数据捆绑在一起形成ADT,这两个设计模式都是将“数据”与“作用于数据上的客户端定制操作”分离开来。

原因:操作可以灵活增加、运行时使用的操作可以动态配置、多个操作的执行次序可以动态变化。

区别1:visitor只定义了一个操作,chain of responsibility定义了一组操作及其之间的次序。
区别2:visitor中,client创建visitor之后传入ADT,ADT再将执行权 delegate到visitor;chain of responsibility中,控制权完全在各个 handler之间流转,ADT(request对象)完全感受不到。

Clues for use of Design Patterns

软件构造系列学习笔记(6.2)————可维护性设计模式_第34张图片

软件构造系列学习笔记(6.2)————可维护性设计模式_第35张图片

Summary

软件构造系列学习笔记(6.2)————可维护性设计模式_第36张图片

你可能感兴趣的:(软件构造系列学习笔记)