日期:2021年8月17日标签:DeveloperHandbook

设计模式 #

面向对象的目的、方法:

  • 业务逻辑与界面逻辑分离,减小耦合。这样业务逻辑很可能再未来被复用。
  • 个人感觉面向对象的终极目标就是开闭原则(open for extensions, close for modifications)

一.简单工厂模式 #

简单工厂模式uml图

  • Client: 产品使用者,有一个抽象类型Product的变量。
  • Factory: 创建一个具体的产品,可供client使用。
  • Product: 产品抽象类,定义了一些抽象方法。
  • Concrete Product: 具体的某个产品。

Client可以使用抽象的Product的方法,而不用关心这个product的具体实现和具体类型。

class Client {
    public Product product;
    ...
}
abstract class Product
{
    public abstract Product createProduct();
    ...
}

class OneProduct extends Product
{
    ...
    static
    {
        ProductFactory.instance().registerProduct("ID1", new OneProduct());
    }
    public OneProduct createProduct()
    {
        return new OneProduct();
    }
    ...
}

class ProductFactory
{
    public Product createProduct(String ProductID) {
        ...
    }
    ...
}

createProduct的实现 #

条件方式 #

ProductFactory负责生产product,常用的方式可以通过switch/case(if/else)语句判断:

class ProductFactory
{
    public Product createProduct(String id) {
        if (id == ID1) {
            return new OneProduct();
        }
        if (id == ID2) {
            return new AnotherProduct();
        }

        return null;
    }
    ...
}

这个方式违背了开闭原则,每次有新的product类,都需要更改createProduct方法。

Class注册方式 #

可以通过反射去实现,但是先跳过反射的方法。

ProductFactory类中使用一个HashMap存储产品的type和产品Id。具体实现如下。

abstract class Product
{
    public abstract Product createProduct();
    ...
}

class OneProduct extends Product
{
    ...
    static
    {
        ProductFactory.instance().registerProduct("ID1", new OneProduct()); // 每次添加一个产品类,都需要在工厂进行注册。
    }
    public OneProduct createProduct()
    {
        return new OneProduct();
    }
    ...
}

class ProductFactory {
    private HashMap m_RegisteredProducts = new HashMap(); // 存储产品id和对应的product

    public void registerProduct(String productID, Product p) {
        this.m_RegisteredProducts.put(productID, p);
    }

    public Product createProduct(String productID) {
        ((Product)m_RegisteredProducts.get(productID)).createProduct();
    }
}

这种方式的确保证了开闭原则,但是每次有新的product类, 都需要主动注册,也有点不方便。

二.策略模式 #

策略模式uml图

  • Strategy: 定义了策略的接口,所有具体的策略都要去实现这个接口
  • ConcreteStrategy: 具体的策略,实现了具体的算法
  • Context: 保存了一个Strategy对象的引用,可能定义了一个接口可以获取Strategy类中的数据。通过Strategy成员可以使用不同的ConcreteStrategy。Context不需要知道策略是如何实现的。

Context对象接受客户端的请求,为客户端做代理,使用Strategy。

我的理解:策略模式与工厂模式有些类似,使用工厂模式的话,客户端最终获得的是Product,然后通过Product类执行Product中的方法。而使用策略模式,客户端通过使用Context使用具体的算法行为。

何时使用 #

当你遇到某些类,仅他们的行为(算法)不同时,将不同的算法分离在不同的类中,在使用的时候根据需要去选择不同的类来使用对应的算法。

目的 #

定义一些列算法,独立封装这些算法中的每一个,并且可以自由切换算法。策略模式使算法独立于使用这些算法的用户(client),用户端可以自由切换算法,保证了业务逻辑和界面分离,减少代码耦合性。

代码实现 #

/* 以机器人的行为为例 */

// Strategy——抽象行为接口
public interface IBehaviour {
    public int moveCommand();
}

// ConcreteStrategy——具体的行为:agressive behaviour
public class AgressiveBehaviour implements IBehaviour{
    public int moveCommand()
    {
        System.out.println("\tAgressive Behaviour: if find another robot attack it");
        return 1;
    }
}

// ConcreteStrategy——具体的行为:defensive behaviour
public class DefensiveBehaviour implements IBehaviour{
    public int moveCommand()
    {
        System.out.println("\tDefensive Behaviour: if find another robot run from it");
        return -1;
    }
}

// ConcreteStrategy——具体的行为:normal behaviour
public class NormalBehaviour implements IBehaviour{
    public int moveCommand()
    {
        System.out.println("\tNormal Behaviour: if find another robot ignore it");
        return 0;
    }
}

// Context-机器人
public class Robot {
    IBehaviour behaviour; // Strategy的引用
    String name;

    public Robot(String name)
    {
        this.name = name;
    }

    public void setBehaviour(IBehaviour behaviour)
    {
        this.behaviour = behaviour;
    }

    public IBehaviour getBehaviour()
    {
        return behaviour;
    }

    public void move()
    {
        System.out.println(this.name + ": Based on current position" +
                     "the behaviour object decide the next move:");
        int command = behaviour.moveCommand();
        // ... send the command to mechanisms
        System.out.println("\tThe result returned by behaviour object " +
                    "is sent to the movement mechanisms " + 
                    " for the robot '"  + this.name + "'");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

// client
public class Main {

    public static void main(String[] args) {

        Robot r1 = new Robot("Big Robot");
        Robot r2 = new Robot("George v.2.1");
        Robot r3 = new Robot("R2");

        r1.setBehaviour(new AgressiveBehaviour());
        r2.setBehaviour(new DefensiveBehaviour());
        r3.setBehaviour(new NormalBehaviour());

        r1.move();
        r2.move();
        r3.move();

        System.out.println("\r\nNew behaviours: " +
                "\r\n\t'Big Robot' gets really scared" +
                "\r\n\t, 'George v.2.1' becomes really mad because" +
                "it's always attacked by other robots" +
                "\r\n\t and R2 keeps its calm\r\n");

        r1.setBehaviour(new DefensiveBehaviour());
        r2.setBehaviour(new AgressiveBehaviour());

        r1.move();
        r2.move();
        r3.move();
    }
}

三.单一职责 #

这里的职责可以认为是为改变一个类的因子的。单一职责表示,如果我们有两个因子可能会改变一个类,那么我们需要将这个类分离成两个类。每个类分别对这两个因子负责。当其中一个因子发生变化,需要去修改类时,只需要修改其中一个类,而不会影响另一个类,这个过程叫做职责分离。

目的 #

一个类仅有一个需要改变的因子。

案例 #

假设有如下发送邮件的程序:

// single responsibility principle - bad example
interface IEmail {
    public void setSender(String sender);
    public void setReceiver(String receiver);
    public void setContent(String content);
}

class Email implements IEmail {
    public void setSender(String sender) {// set sender; }
    public void setReceiver(String receiver) {// set receiver; }
    public void setContent(String content) {// set content; }
}

Email类其实包含了两个职责,一个是处理邮件协议(sender和receiver),另一个是需要处理内容(content)。现在这个内容是字符串形式,假如以后要支持html格式或者markdown格式,那么修改setContent可能会影响到其他部分。所以最好的方式是将职责单一化,再创建一个类用于处理content。

// single responsibility principle - good example
interface IEmail {
    public void setSender(String sender);
    public void setReceiver(String receiver);
    public void setContent(IContent content);
}

interface Content {
    public String getAsString(); // used for serialization
}

class Email implements IEmail {
    public void setSender(String sender) {// set sender; }
    public void setReceiver(String receiver) {// set receiver; }
    public void setContent(IContent content) {// set content; }
}

一般我们写软件时,习惯将界面操作部分和逻辑部分的代码分离,这也是遵守职责单一原则的例子。将职责分离,更有利于代码的维护,增加代码的复用性。

如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当变化发生时,设计会遭受意想不到的破坏。

四.开闭原则 #

开闭原则(Open Close Principle):对扩展是开放的,对修改是封闭的。软件设计或者写代码必须遵循这一原则,新功能的添加必须最小化的影响旧代码,允许我们使用添加类的方式添加新功能,保持旧代码不变。因为旧代码都是已经测试过的代码,如果修改可能会产生新的错误。

目的 #

类、模块和函数都应该遵守对扩展是开放的,对修改是封闭的这一原则。

案例 #

首先看一个错误的例子:

// Open-Close Principle - Bad example
class GraphicEditor {
 
     public void drawShape(Shape s) {
         if (s.m_type==1)
             drawRectangle(s);
         else if (s.m_type==2)
             drawCircle(s);
     }
     public void drawCircle(Circle r) {....}
     public void drawRectangle(Rectangle r) {....}
}
 
class Shape {
     int m_type;
}
 
class Rectangle extends Shape {
     Rectangle() {
         super.m_type=1;
     }
}
 
class Circle extends Shape {
     Circle() {
         super.m_type=2;
     }
} 

上例中由如下几个缺点:

  • 每次添加一个新的ShapeGraphicEditor的单元测试必须重新执行
  • 当一个新的Shape添加,写代码的人必须要理解GraphicEditor的代码逻辑,因为他需要修改drawShape函数。
  • 添加一个新的Shape可能会影响已经存在的功能。

将上面的例子修改如下,遵守开闭原则:

// Open-Close Principle - Good example
class GraphicEditor {
     public void drawShape(Shape s) {
         s.draw();
     }
}
 
class Shape {
     abstract void draw();
}
 
class Rectangle extends Shape  {
     public void draw() {
         // draw the rectangle
     }
}

每次添加新的Shape,只需要增加一个类,继承Shape类,完成draw函数即可。

五.依赖倒转原则 #

抽象不应该依赖细节,细节应该依赖于抽象。即,针对接口编程,不要对实现编程。

  • 高层模块不应该依赖底层模块。两个都应该依赖抽象。
  • 抽象不应该依赖细节,细节应该依赖抽象。

目的 #

大部分程序员在写代码时,为了使代码得以复用,一般都会把这些代码写成许许多多函数的程序,这样在做新项目时,去调用这些底层的函数就可以了。比如很多项目需要访问数据库,所以就把访问数据库的代码写成了函数,每次做新项目时就去调用这些函数,这就叫高层模块依赖低层模块。但是当要做新项目时,法线业务逻辑的高层模块都是一样的,但是客户希望使用不同的数据库或存储信息方式,这时麻烦来了,我们希望复用这些高层模块,但高层模块都是与低层的访问数据库绑定在一起的,没办法复用这些高层模块,这就非常糟糕了。所以应该针对接口编程,不要对实现编程, 高层模块和低层模块都应该依赖抽象。

所以,我们需要在高层模块和低层模块之间添加一个抽象层。

High Level Classes --> Abstraction Layer --> Low Level Classes

案例 #

首先,分析下面的错误代码示范。它违背了依赖倒转原则。有一个Manager类(高层类)和一个Worker类(低层类),可以看到Manager此时是依赖于Worker类的。假设我们需要添加一个新模块,叫SuperWorker。这时我们不得不去更改Manager的代码,如果Manager代码特别复杂,那这个工作量就很大了,而且很容易出错。

所以这段代码,有如下几个缺点:

  • 我们必须去更改Manager类(假设Manager类的逻辑很复杂,这是一个很花时间的工作)。
  • Manager已有的功能可能会受到影响。
  • 更改完成后,Manager类的单元测试需要重新来一遍。
// Dependency Inversion Principle - Bad example
class Worker {
    public void work() {
        // ....working
    }
}

class Manager {
    Worker worker;
    public void setWorker(Worker w) {
        worker = w;
    }

    public void manage() {
        worker.work();
    }
}

class SuperWorker {
    public void work() {
        //.... working much more
    }
}

下面是优化后的代码。我们添加了一个IWorker接口,解决了上面存在的问题。

  • 添加SuperWorkers时,Manager类无需做出更改。
  • 因为Manager类没有更改,已有的功能不会受到影响。
  • Manager类的测试工作,不必重新去做。
// Dependency Inversion Principle - Good example
interface IWorker {
    public void work();
}

class Worker implements IWorker{
    public void work() {
        // ....working
    }
}

class SuperWorker  implements IWorker{
    public void work() {
        //.... working much more
    }
}

class Manager {
    IWorker worker;

    public void setWorker(IWorker w) {
        worker = w;
    }

    public void manage() {
        worker.work();
    }
}

六.装饰模式(Decorator Pattern) #

动机 #

通常我们可以通过继承的方式,扩展一个类的功能。但是有时候我们需要动态的扩展一个类的功能形成一个新的object。

想像一个比较经典的图形窗口的例子。为了向图形窗口添加一个特定的边框,需要继承Window类,创建一个FrameWindow类,为了创建一个有边框的窗口,我们需要使用FrameWindow创建一个新的object。然而通过这种方式,我们无法扩展一个普通的图形窗口,从而得到一个有特定边框的窗口。

目的 #

此模式的目的是动态地向对象添加额外的职责。

实现 #

装饰模式(decorator pattern)

  • Component: 一个接口,可以向实现该接口的类对象,动态的添加职责(功能)。
  • ConcreteComponent:定义可以向其添加额外职责的对象。
  • Decorator:保留一个Component对象的引用,并定义Component的接口。
  • Concrete Decorators: 具体的装饰类,通过添加状态和行为扩展Component Object的功能(职责)。

描述 #

当需要动态地向类添加和删除责任,以及由于可能产生大量子类而无法进行子类化时,可以使用装饰器模式。

案例 #

用Java语言,利用装饰模式实现一个图形窗口程序,给窗口动态的添加scroll。 装饰模式(Decorator Pattern) 下面的代码实现了一个简单的Window接口:

package decorator;

/**
 * Window Interface 
 * 
 * Component window
 */
public interface Window {

    public void renderWindow();
    
}

创建 一个具体的window实现类:

package decorator;

/**
 * Window implementation 
 * 
 * Concrete implementation
 */
public class SimpleWindow implements Window {

    @Override
    public void renderWindow() {
        // implementation of rendering details

    }
}

接下来创建DecorateWindow类,表示一个window的装饰类。值得注意的是类里面有一个privateWindowReference成员,表示需要装饰(增加功能)的窗口。

package decorator;

/**
 *
 */
public class DecoratedWindow implements Window{

    /**
     * private reference to the window being decorated 
     */
    private Window privateWindowRefernce = null;
    
    public DecoratedWindow( Window windowRefernce) {
    
        this.privateWindowRefernce = windowRefernce;
    }

    @Override
    public void renderWindow() {

        privateWindowRefernce.renderWindow();
        
    }
}

继承DecoratedWindow,实现一个为窗口添加滚动功能的装饰窗口子类:

package decorator;

/**
 * Concrete Decorator with extended state 
 * 
 * Scrollable window creates a window that is scrollable
 */
public class ScrollableWindow extends DecoratedWindow{
    /**
     * Additional State 
     */
    private Object scrollBarObjectRepresentation = null;
    
    public ScrollableWindow(Window windowRefernce) {
        super(windowRefernce);
    }
    
    @Override
    public void renderWindow() {
    
        // render scroll bar 
        renderScrollBarObject();
        
        // render decorated window
        super.renderWindow();
    }

    private void renderScrollBarObject() {

        // prepare scroll bar 
        scrollBarObjectRepresentation = new  Object();
        
        // render scrollbar 
        
    }	
}

最后,客户端程序创建一个可滚动的窗口:

package decorator;

public class GUIDriver {

    public static void main(String[] args) {
        // create a new window 
        Window window = new ConcreteWindow();
        window.renderWindow();
        
        // at some point later 
        // maybe text size becomes larger than the window 
        // thus the scrolling behavior must be added 
        // decorate old window 
        window = new ScrollableWindow(window);
        
        //  now window object 
        // has additional behavior / state 
        window.renderWindow();
    }
}

总结 #

问:装饰功能有什么用?

答:装饰模式可以在程序运行时,动态的给被装饰的类对象添加其他功能(通常为装饰性功能,非核心功能)。

问:什么时候应该用装饰模式?

答:当一个类需要新的功能的时候,你可以向旧类添加新的代码,或者继承旧类创建一个新的类。第一种方式需要更改旧代码,很可能影响原有的核心功能,显然这不符合我们的预期。第二种方式是一种静态的,无法达到动态增加原有类对象功能的效果,就像上面的例子,我们需要动态的增加ConcreteWindow对象的功能,显然第二种方式也无法满足我们的需求。这时就需要使用装饰者模式了。

问:装饰者模式有什么有点?

答:装饰模式可以将类中的装饰性功能从类中移除,从而在运行时动态的添加,这样做有效的把核心职责和装饰功能区分开了,而且可以去除相关类中的重复逻辑。装饰模式还有一个重要的优点,就是当有很多个装饰类时,你可以自由的组合这些装饰类的顺序,还是以上面的图形窗口为例,我们有一个ScrollableWindow装饰类,可以使window具有滚动功能,还可以增加ToolbarWindow装饰类,使window具有工具栏,ScrollableWindowToolbarWindow的装饰顺序是任意的,你可以自由安排。

Window window = new ConcreteWindow();
window.renderWindow();
window = new ScrollableWindow(window);
window.renderWindow();

window = new ToolbarWindow(window);
window.renderWindow();

七.代理模式 #

动机 #

某些时候我们需要可以控制对象访问的能力。例如,如果我们只需要使用一个复杂对象(该对象内部比较复杂,方法很多,成员很多)的几个方法,我们不需要直接实例化这个对象,可以使用一些轻量级的对象作为代理,代替复杂对象,通过这些轻量级对象访问我们需要的方法。这些轻量级的对象就是代理(proxy)

需要使用代理的场景有很多:控制何时需要实例化和初始化开销较大的对象。可以使用多个代理访问复杂对象的不同成员,来控制复杂对象的访问权限。代理还提供了一种访问运行在其他程序或进程中甚至是其他机器上的对象的方法。

以图像查看程序,思考下。 一个图像查看程序必须能够列出和显示文件夹中的高分辨率照片对象,但人们多久打开文件夹并查看里面的所有图像。 有时您将寻找一个特定的照片,有时您只希望看到一个图像名称。图像查看器必须能够列出所有的照片对象,但是照片对象只有在需要渲染时才能加载到内存中。

目的 #

此模式的目的是为对象提供一个占位符(代理),以控制对该对象的引用。

实现 #

uml of proxy pattern

  • SubjectRealSubject和它的代理需要实现的接口。
  • ProxyRealSubject的代理,内部保留了RealSubject的引用。可以在任何可以使用RealSubject的地方,作为RealSubject的替代对象。控制RealSubject的访问,并且可以控制RealSubject的创建和删除。
  • RealSubjectProxy代理的真实Object。

类型 #

代理可以分为如下几种类型:

  • 虚拟代理(Virtual Proxies):用于延迟复杂对象(实例化耗费比较大的对象)的创建和初始化,直到真的需要使用该对象的方法时才去创建该对象。例如当打开一个很大的HTML网页时,里面可能有很多文字和图片,但是还是可以很快打开它,此时你能看到的所有的文字,但是图片却是一张一张的下载后才能看到,那些未打开的图片框,就是通过虚拟代理来替代了真实的图片,此时代理存储了真实图片的路径和尺寸。
  • 远程代理(Remote Proxies):为远程对象(运行在其他进程、机器上的对象)提供一个本地代理,可以通过本地代理访问远程对象。典型的例子有.Net中的WebService应用。
  • 安全代理(Protection Proxies):为安全对象提供不同的代理控制安全对象的访问权限。
  • 智能引用(Smart References):当调用真实对象时,代理处理另外一些事。例如计算真实对象的引用次数,可以在代理对象中设置一个引用计数,当引用计数达到某个值时,禁制访问真实对象。

案例 #

一个虚拟代理的例子。

需要构建一个图片查看器程序,该程序需要列出所有图片并且展示高分辨率图片。再打开该程序时,需要立刻看到所有图片列表,但是并不需要立刻展示高分辨率图片,仅在需要绘制高分辨率图片时才会加载高分辨率图片,例如当用户点击某个图片查看,此时需要真正的渲染高分辨率图片。

proxy pattern例子

Image接口,表示Subject,接口有一个ShowImage方法。

package proxy;

/**
 * Subject Interface
 */
public interface Image {

    public void showImage();
}

虚拟代理类,该类只有在showImage方法被调用时,才会实例化高分辨率图片,这样节省了加载图片的消耗。

package proxy;

/**
 * Proxy
 */
public class ImageProxy implements Image {

    /**
     * Private Proxy data 
     */
    private String imageFilePath;
    
    /**
     * Reference to RealSubject
     */
    private Image proxifiedImage;
    
    
    public ImageProxy(String imageFilePath) {
        this.imageFilePath= imageFilePath;	
    }
    
    @Override
    public void showImage() {

        // create the Image Object only when the image is required to be shown
        proxifiedImage = new HighResolutionImage(imageFilePath);
        
        // now call showImage on realSubject
        proxifiedImage.showImage();
    }
}

高分辨率图片RealSubject的实现:

package proxy;

/**
 * RealSubject
 */
public class HighResolutionImage implements Image {

    public HighResolutionImage(String imageFilePath) {
        
        loadImage(imageFilePath);
    }

    private void loadImage(String imageFilePath) {
        // load Image from disk into memory
        // this is heavy and costly operation
    }

    @Override
    public void showImage() {

        // Actual Image rendering logic
    }

}

客户端代码,ImageViewer

package proxy;

/**
 * Image Viewer program
 */
public class ImageViewer {
    public static void main(String[] args) {
        // assuming that the user selects a folder that has 3 images	
        //create the 3 images 	
        Image highResolutionImage1 = new ImageProxy("sample/veryHighResPhoto1.jpeg");
        Image highResolutionImage2 = new ImageProxy("sample/veryHighResPhoto2.jpeg");
        Image highResolutionImage3 = new ImageProxy("sample/veryHighResPhoto3.jpeg");
        
        // assume that the user clicks on Image one item in a list
        // this would cause the program to call showImage() for that image only
        // note that in this case only image one was loaded into memory
        highResolutionImage1.showImage();
        
        // consider using the high resolution image object directly
        Image highResolutionImageNoProxy1 = new HighResolutionImage("sample/veryHighResPhoto1.jpeg");
        Image highResolutionImageNoProxy2 = new HighResolutionImage("sample/veryHighResPhoto2.jpeg");
        Image highResolutionImageBoProxy3 = new HighResolutionImage("sample/veryHighResPhoto3.jpeg");
        
        
        // assume that the user selects image two item from images list
        highResolutionImageNoProxy2.showImage();
        
        // note that in this case all images have been loaded into memory 
        // and not all have been actually displayed
        // this is a waste of memory resources
    }
}

八.工厂方法模式 #

动机 #

工厂方法模式(Factory Method),定义了一个创建object的接口,但是将创建什么样的object的权利丢给了子类。举个形象的例子,以旅馆为例,当你进入旅馆,前台会给你一把钥匙,这个钥匙就类似一个工厂接口,可以用来创建一个房间(object),但是你不知道房间是什么样的,你或许需要让某个服务员(具体的工厂子类)带你去具体的房间,这大概就是工厂方法的具体过程。

目的 #

  • 定义了一个创建(生产)objects的接口,将具体实现、具体创建什么样的object的过程给予子类实现。
  • 通过一个通用的类型接口,获得新创建的object。

实现 #

uml图如下:

工厂方法模式(Factory Method)

说明:

  • Product: 定义了工厂将要创建的产品(objects)的类的接口。
  • ConcreteProduct: 实现Product接口,具体的产品。
  • Creator: 工厂接口,定义了生产产品的FactoryMethod,所有子工厂类必须实现该方法。
  • ConcreteCreator: 实现了Creator接口,用于生产具体的产品。

工厂方法模式的模板实现如下:

public interface Product {  }

public abstract class Creator 
{
    public void anOperation() 
    {
        Product product = factoryMethod();
    }
    
    protected abstract Product factoryMethod();
}

public class ConcreteProduct implements Product {  }

public class ConcreteCreator extends Creator 
{
    protected Product factoryMethod() 
    {
        return new ConcreteProduct();
    }
}

public class Client 
{
    public static void main( String arg[] ) 
    {
        Creator creator = new ConcreteCreator();
        creator.anOperation();
    }
}

案例 #

以一个文件应用为例。假设你需要设计一个应用,需要生成不同格式的文档,例如普通文本格式的文档、图形格式文档等。此时可以设计两个基础类(或者接口):DocCreatorDocumentDocCreator定义了一系列的操作,例如createDocument,如果要添加一个图形格式文档,需要继承Document创建一个PaintDocument类,还需要创建一个具体的document creator类:PaintDocCreatorPaintDocCreator继承自DocCreator

上例中,各种类在工厂方法模式里的角色如下:

  • DocCreator:工厂方法模式里的Creator(Factory)。
  • PaintDocCreator:工厂方法模式里的ConcreteCreator,负责实例化具体的产品。
  • Document:工厂方法模式里的Product。
  • PaintDocument:工厂方法模式里的ConcreteProduct。

思考 #

工厂方法模式使简单工厂模式的进一步抽象和推广。由于使用了多态性,工厂方法模式保持了简单工厂模式的优点,而克服了它的缺点。但缺点是每添加一个产品,就需要加一个产品工厂的类,增加了额外的开发量。

九.适配器模式 #

动机 #

举个例子来说明适配器模式(adapter design pattern),大概10年左右我买了个电子阅读器,电子阅读器存储比较小,所以它支持外插存储卡扩充空间,它支持的存储卡比一般手机的存储卡尺寸要大那么一点,所以我有一张手机存储卡,但是没法使用,所以我花了2块钱在商店买了一个卡套,将卡套套在手机存储卡上,然后插入电子阅读器中,完美解决了存储空间不够的问题。这里的卡套其实就是适配器。

联想到编程,当你遇到这么一个场景,你希望有一个特定类型的Object,但是你只能获取到一个object,它提供了同样的功能,完全满足你现在需要的功能,仅仅因为它实现的接口不同,导致你无法直接使用这个object。此时你就想使用这个object,不愿意去新写一个类,所以此时应该用适配器模式。

实现 #

uml图如下:

适配器模式(adapter design pattern)

  • Target:定义了Client需要使用的主要接口。
  • Adapter:将Adaptee适配成Target接口。
  • Adaptee:定义了将要被适配的对象接口。
  • Client:使用Target的客户类。

Adapter的作用本质上就是ClientAdaptee之间的一座桥梁。

十.模板方法 #

动机 #

假设有一个动作,需要分成几个步骤完成,但是这些步骤之间的顺序又是固定的。例如解析一个Excel文件,分为read、parse、print三个步骤,可以定义一个ParseExcel类如下。

class ParseExcel {

    ...

    public progress(string path) {
        read(path);
        parse();
        print();
    }
    public void read(string path) {
        //
    }

    public void parse() {
        // 
    }

    public void print() {
        //
    }
}

处理excel文件,你需要调用ParseExcel.progress方法。但是如果你有一个读取txt文件,你需要再创建一个ParseTxt类,如下。

class ParseTxt {

    ...

    public progress(string path) {
        read(path);
        parse();
        print();
    }
    public void read(string path) {
        //
    }

    public void parse() {
        // 
    }

    public void print() {
        //
    }
}

然后调用ParseTxt.progress方法。按照模板方法模式(template method design pattern),我们需要创建一个基类实现算法步骤(progress方法),但是具体的实现步骤需要再子类中定义。模板方法模式让子类实现算法的每一个步骤,把算法的步骤结构交给父类控制。

实现 #

uml图如下:

模板方法模式(template method pattern)

还是以处理文件为例。

public class ProgressFile() {
    public void progress(string path) {
        read(path);
        parse();
        print();
    }

    public abstract void read(string path) {
        //
    }

    public abstract void parse() {
        // 
    }

    public abstract void print() {
        //
    }
}

class ParseExcel: ProgressFile {

    public void read(string path) {
        //
    }

    public void parse() {
        // 
    }

    public void print() {
        //
    }
}

class ParseTxt: ProgressFile {

    public void read(string path) {
        //
    }

    public void parse() {
        // 
    }

    public void print() {
        //
    }
}

十一.外观模式 #

动机 #

外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。

当遇到非常复杂的系统时(系统由很多类共同实现),客户端使用该系统非常困难,为了简化客户端的使用,可以增加一个外观类,外观类只提供客户端需要使用的接口,向客户端隐藏系统的复杂性。

实现 #

实现一个绘图程序,可绘制圆形、方形和矩形。

// 子系统
public interface Shape {
   void draw();
}

public class Rectangle implements Shape {
 
   @Override
   public void draw() {
      System.out.println("Rectangle::draw()");
   }
}

public class Square implements Shape {
 
   @Override
   public void draw() {
      System.out.println("Square::draw()");
   }
}

public class Circle implements Shape {
 
   @Override
   public void draw() {
      System.out.println("Circle::draw()");
   }
}

// 高层次的外观类
public class ShapeMaker {
   private Shape circle;
   private Shape rectangle;
   private Shape square;
 
   public ShapeMaker() {
      circle = new Circle();
      rectangle = new Rectangle();
      square = new Square();
   }
 
   public void drawCircle(){
      circle.draw();
   }
   public void drawRectangle(){
      rectangle.draw();
   }
   public void drawSquare(){
      square.draw();
   }
}

// 客户端只需要使用外观类,无需关心子系统
public class FacadePatternDemo {
   public static void main(String[] args) {
      ShapeMaker shapeMaker = new ShapeMaker();
 
      shapeMaker.drawCircle();
      shapeMaker.drawRectangle();
      shapeMaker.drawSquare();      
   }
}

十二.状态模式 #

状态模式(state design pattern),当Object的状态不同时,它的行为也不同,即行为由状态决定。例如当一个人很累时,你和他对话,他也许会简单的说“对不起,我很累”,当一个人喝醉了,他会说不清话“嗯。。啊。哈哈”,而当一个人状态正常时,既不困也没有喝酒,和他对话,他也许会跟你唠上一唠。

实现 #

例如,要实现一个绘图程序,当笔(鼠标)的状态不同时,移动鼠标,点击鼠标,它的行为时不一样的。

public partial class Pen {
    private State _state = new Idle();

    private void SetState(State state) => _state = state;

    public  void OnClick(Pen pen) => _state.OnClick(this);

    public void OnClickFinish(Pen pen) => _state.OnClickFinish(this);

    public void OnMove(Pen pen) => _state.OnMove(this);
}

public partial class Pen {
    private interface State {
        void OnMove(Pen pen);
        void OnClick(Pen pen);
        void OnClickFinish(Pen pen);
    }

    // 空闲状态
    private class Idle: State
    {
        public void OnClick(Pen pen) {
            pen.setState(new Writing());
        }

        public void OnClickFinish(Pen pen) {
            // do nothing
        }

        public void OnMove(Pen pen) {
            // do nothing
        }
    }

    // 书写状态
    private class Writing: State
    {
        public void OnClick(Pen pen) {
            // DRAW HARDER
        }

        public void OnClickFinish(Pen pen) {
            pen.SetState(new Idle());
        }
        
        public void OnMove(Pen pen) {
            // draw on canvas
        }
    }
}

十三.责任链模式 #

责任链模式(Chain of Responsibility Patttern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。

这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,以此类推。

实现 #

以数据验证为例。我们需要验证一个人的信息,包括姓名、年龄、性别。

// ---需要验证的数据类型:Person
class Person {
    public string Name {get; set;}
    public int Age {get; set;}
    public float Income {get; set;}
}

// ---责任链中的每个验证步骤
interface IHandler {
    void SetNextHandler(IHandler handler);
    void Progress(Request request);
}

class BaseHandler: IHandler {
    protected IHandler _nextHandler;
    public BaseHandler() {
        _nextHandler = null;
    }

    public void SetNextHandler(IHandler handler) {
        _nextHandler = handler;
    }

    public virtual void Process(Request request) {
        throw new NotImplementedException();
    }
}

class MaxAgeHandler: BaseHandler {
    public override void Process(Request request) {
        if (request.Data is Person person) {
            if (person.Age > 55) request.ValidationMessages.Add("Invalid Age");
            if (_nextHandler != null) _nextHandler.Process(request);
        } else {
            throw new Exception("Invalid message data.");
        }
    }
}

class MaxNameLengthHandler: BaseHandler {
    public override void Process (Request request) {
        if (request.Data is Person person) {
            if (person.Name.length > 10) request.ValidationMessages.Add("Invalid Name length");
            if (_nextHandler != null) _nextHandler.Process(request);
        } else {
            throw new Exception("Invalid message data.");
        }
    }
}

class MaxIncomeHandler: BaseHandler {
    public override void Process (Request request) {
        if (request.Data is Person person) {
            if (person.Income > 1000) request.ValidationMessages.Add("Invalid Income");
            if (_nextHandler != null) _nextHandler.Process(request);
        } else {
            throw new Exception("Invalid message data.");
        }
    }
}

// ---请求
class Request {
    public object Data {get; set;}
    public List<string> ValidationMessages;

    public Request() {
        ValidationMessages = new List<string>();
    }
}

// ---客户端代码
Person person = new Person(){
    Name = "John Doe";
    Age = 60;
    Income = 1500f;
}

// 验证的请求,包含验证的数据
Request request = new Request() {Data = person};

// 创建验证责任链的类
MaxAgeHandler maxAgeHandler = new MaxAgeHandler();
MaxNameLengthHandler maxNameLengthHandler = new MaxNameLengthHandler();
MaxIncomeHandler maxIncomeHandler = new MaxIncomeHandler();

// 设置责任顺序,成链
maxAgeHandler.SetNextHandler(maxNameLengthHandler);
maxNameLengthHandler.SetNextHandler(maxIncomeHandler);

// 开始验证
maxAgeHandler.process(request);

// 打印验证信息
foreach (string msg in request.ValidataionMessages) {
    Console.WriteLine(msg);
}

(未完待续)

目录