Design Pattern/设计模式: Part V

Author Avatar
Cesare (MINGJU LI) Feb 01, 2020

设计模式以及综述

设计模式对于一名软件开发工程师来说可谓是必备的技能和知识,然而在程序员社区中也充斥着不少对于设计模式的反对之音。有人认为设计模式的知识对于工程开发至关重要,但也有许多经验丰富的程序员认为设计模式应当是长期的软件开发的积累所留下的不可名状的抽象艺术,对于市面上不少介绍设计模式的书籍嗤之以鼻。

对于刚入门的软件开发工程师而言,你若问他(包括现在的我自己)“我们为什么要使用工厂模式?使用工厂模式能够带来什么好处?”之类的问题,他也是很难回答的。所以盲目的掌握设计模式中那些拗口的名称,或者干巴巴的“方便继承”之类的描述是没有意义的。

这一系列的博客的目的并不是一蹴而就的讲解全部的设计模式知识,这样的知识没有日积月累的开发经验是很难去对其有深入的体会的。我只是希望能在这一系列的博客中,借助《图解设计模式》一书,跟随书中的顺序对其中的设计模式知识进行某种程度的理解和总结,以至于能够对诸君有所启迪,开发出更加优美和友好的代码。

我的能力有限,若是文中出现什么谬误也请诸君不吝赐教,提前感谢。

《图解设计模式》一书的作者是结城浩先生,全书共计十个部分,预计会分为十篇博客进行介绍。书中的代码为java所写,虽然java并不是我比较擅长的代码,但是为了不引起错误贻笑大方,所以还是按照书中的java进行讨论。本博客介绍其中的第五部分:一致性。因为每一章节中代码都占据了比较多的部分,为了更好地理解相关概念,建议阅读代码优先从Main.java的部分开始。

Chapter 11: Composite模式–容器与内容的一致性

最常见的Composite模式的思想其实就是非常常见的文件系统。一个文件夹下面可以包含若干个文件,当然也可以包含若干个文文件夹,而在性质上子文件夹和父文件夹的性质并无不同。我们在这一章节中需要学习的Composite模式就是创造类似这样结构的模式,能够使容器与内容具有一致性,创造出递归结构的模式就是Composite模式。

例:我们如刚刚所说的设计一个简单的文件系统,我们使用一个抽象的Entry类作为File类和Directory类的子类,每一个Entry类都有getName()方法

// Entry.java: Entry类
public abstract class Entry{
    public abstract String getName();
    public Entry add(Entry entry) throws FileTreatmentException{
        throw new FileTreatmentException();
    }
    public void printList(){
        printList("");
    }
    protected abstract void printList(String prefix);
    public String toString(){
        return getName();
    }
}
// File.java
public class File extends Entry{
    private String name;
    public File(String name){
        this.name = name;
    }
    public String getName(){
        return name;
    }
    protected void printList(String prefix){
        System.out.println(prefix+"/"+this.toString());
        //System.out.println(prefix+"/"+this); 这句话等价于上面的话
    }
}
// Directory.java
public class Directory extends Entry{
    private String name;
    public Directory(String name){
        this.name = name;
    }
    public String getName(){
        return name;
    }
    public Entry add(Entry entry){
        directory.add(entry);
        return this;
    }
    protected void printList(String prefix){
        System.out.println(prefix+"/"+this);
        Iterator it = directory.iterator();
        while(it.hasNext()){
            Entry entry = (Entry)it.next();
            entry.printList(prefix+"/"+name);
        }
    }

}
// FileTreatmentException.java
// 注意,在文件系统中,我们希望文件夹下可以添加新的Entry,但是文件下面则不行,如果这样做的话引起一个Exception,我们这里使用的方法是在父类中定义的add()会直接引起一个Exception,而在Directory类中重写了父类中的add()。但是这里我们需要对这个Exception进行定义
public class FileTreatmentException extends RuntimeException{
    public FileTreatmentException(){

    }
    public FileTreatmentException(String msg){
        super(msg);
    }
}
public class Main(){
    public static void main(String[] args){
        try{
            System.out.println("Making root entried");
            Directory rootdir = new Directory("root");
            Directory bindir = new Directory("bin");
            Directory tmpdir = new Directory("tmp");

            rootdir.add(bindir);
            rootdir.add(tmpdir);

            bin.add(new File("File1.txt"));
            tmp.add(new File("File2.exe"));

            rootdir.printList();
        }catch(FileTreatmentException e){
            e.printStackTrace();
        }
    }
}

这个模式还是比较好理解的,此处就不多赘述。

Chapter 12: Decorator模式–装饰边框与被装饰物的一致性

Decorator模式允许向一个现有的对象添加新的功能,同时又不改变其结构。举个例子,假如现在有一块戚风蛋糕,我们可以将它涂上奶油,那么这块蛋糕就变成了奶油蛋糕,加上草莓,就变成了草莓奶油蛋糕,如果我们愿意花更大的力气去给这块蛋糕加上巧克力,显然它就变成了巧克力草莓奶油蛋糕,但是诸君注意到重点了没有?不管我们增加了多少Decorator(奶油/草莓/…),核心都不曾发生改变,蛋糕依然是蛋糕。

例:这里我们使用一个给文字添加装饰边框的例子来介绍Decorator模式,我们希望给出一个字符串可以给其加上边框,同时又不限制加边框的次数(这里边框就是奶油/草莓等等,而字符串则是蛋糕本身)

//Display.java 
//是StringDisplay类和Border类的父类
public abstract class Display{
    public abstract int getColumns(){};
    public abstract int getRows(){};
    public abstract String getRowText(int row);//获取第row行的字符串
    public final void show(){
        for(int i=0;i<getRows();i++){
            System.out.println(getRowText(i));
            // 逐行打印
        }
    }
}
//StringDisplay.java
//用于显示单行字符串,这里StringDisplay类就相当于是奶油蛋糕中的核心蛋糕
public class StringDisplay extends Display{
    private String string;
    public StringDisplay(String string){
        this.string = string;
    }
    public int getColumns(){
        return string.getBytes().length;
    }
    public int getRows(){
        return 1;
    }
    public String getRowText(int row){
        if(row==0){
            return string;
        }else{
            return null;
        }
    }
}
// Border.java
// Border类是边框的抽象类(草莓/巧克力的父类),虽然表示边框,但是也是Display的子类
public abstract class Border extends Display{
    protected Display display;
    protected Border(Display display){
        this.display = display;
    }
}
// 注意,这里通过继承,我们使得装饰边框Border与被装饰物StringDisplay具有了相同的方法。这里我们声明的一个protected的Display属性表示被装饰物,它既可以是Border也可以是StringDisplay,这就为我们实现Decorator模式提供了条件
// SideBorder.java
// 可以在内容的两侧加上边框
public class SideBorder extends Border{
    private char borderChar;        //表示边框的字符
    public SideBorder(Display display, char ch){
        super(display);
        this.borderChar = ch;
    }
    public int getColumns(){
        return 1+display.getColumns()+1;
    }
    public int getRows(){
        return display.getRows();
    }
    public String getRowText(int row){
        return borderChar+display.getRowText(row)+borderChar;
    }
}
// FullBorder.java
// 与SideBorder不同,FullBorder会在上下左右两侧都加上装饰边框
public class FullBorder extends Border{
    public FUllBorder(Display display){
        super(display);
    }
    public int getColumns(){
        return 1+display.getColumns()+1;
    }
    public int getRows(){
        return 1+display.getRows()+1;
    }
    public String getRowText(int row){
        if(row==0){
            return "+"+makeLine(display.getColumns())+"+";
        }else if (row==display.getRows()+1){
            return "+"+makeLine(display.getColumns())+"+";
        }else{
            return "|"+display.getRowText(row-1)+"|";
        }
    }
    private String makeLine(int count){
        StringBuffer buf = new StringBuffer();
        for(int i=0;i<count;i++){
            buf.append('-');
        }
        return buf.toString();
    }
}
// Main.java
public class Main{
    public tatic void main(String[], args){
        Display b1 = new StringDisplay("Hello World");
        Display b2 = new SideBorder(b1, '#');
        //为b1两侧加上装饰边框
        Display b3 = new FullBorder(b2);
        //为b2四周加上装饰边框

        b1.show();
        b2.show();
        b3.show();

        Display b4 = new SideBorder(
            new FullBorder(
                new FullBorder(
                    new SideBorder(
                        new FullBorder(
                            new StringDisplay("Hello World");
                        ),'*'
                    )
                )
            ),'/'
        );
        b4.show();
    }
}

注意,在Decorator模式中,最巧妙地就是装饰边框与被装饰物所具有的一致性。在本例中,Boarder类和StringDisplay类都是Display的子类,它们具有相同的接口。得益于这些相同的接口,我们可以使得他们比较方便的不断进行装饰。