Java——设计模式(结构型模式)

1、适配器模式(不兼容结构的协调)

在适配器模式中引入了一个被称为适配器(Adapter)的包装类,而它所包装的对象称为适配者(Adaptee),即被适配的类。适配器的实现就是把客户类的请求转化为对适配者的相应接口的调用。也就是说:当客户类调用适配器的方法时,在适配器类的内部将调用适配者类的方法,而这个过程对客户类是透明的,客户类并不直接访问适配者类。所以,适配器让那些因为接口不兼容而不能交互的类能够一块儿工做。(实际上客户端拿到的对象,已是适配器的对象了)html

适配器模式能够将一个类的接口和另外一个类的接口匹配起来,而无须修改原来的适配者接口和抽象目标类接口。适配器模式定义以下:java

适配器模式(Adapter Pattern):将一个接口转换成客户但愿的另外一个接口,使接口不兼容的那些类能够一块儿 工做,其别名为包装器(Wrapper)。适配器模式既能够做为类结构型模式,也能够做为对象结构型模式。(实现不改代码,只添加类和修改配置文件完成调用其它对象方法)web

  • Target(目标抽象类):目标抽象类定义客户所需接口,能够是一个抽象类或接口,也能够是具体类。
  • Adapter(适配器类):适配器能够调用另外一个接口,做为一个转换器,对Adaptee和Target进行适配,适配器类是适配器模式的核心,在对象适配器中,它经过继承Target并关联一个Adaptee对象使两者产生联系。
  • Adaptee(适配者类):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口须要适配,适配者类通常是一个具体类,包含了客户但愿使用的业务方法,在某些状况下可能没有适配者类的源代码。

根据对象适配器模式结构图,在对象适配器中,客户端须要调用 request() 方法,而适配者类 Adaptee 没有该 方法,可是它所提供的 specificRequest() 方法倒是客户端所须要的。为了使客户端可以使用适配者类,须要提 供一个包装类 Adapter,即适配器类。这个包装类包装了一个适配者的实例,从而将客户端与适配者衔接起 来,在适配器的 request() 方法中调用适配者的 specificRequest() 方法。由于适配器类与适配者类是关联关 系(也可称之为委派关系),因此这种适配器模式称为对象适配器模式。典型的对象适配器代码以下所示:数据库

/**
 * @author x5456
 */
public class AdaptorPattern {

    //抽象成绩操做类:目标接口
    interface ScoreOperation {
        public int[] sort(int array[]); //成绩排序

        public int search(int array[], int key); //成绩查找
    }

    //快速排序类:适配者
    class QuickSort {
        public int[] quickSort(int array[]) {
            sort(array, 0, array.length - 1);
            return array;
        }

        public void sort(int array[], int p, int r) {
            int q = 0;
            if (p < r) {
                q = partition(array, p, r);
                sort(array, p, q - 1);
                sort(array, q + 1, r);
            }
        }

        public int partition(int[] a, int p, int r) {
            int x = a[r];
            int j = p - 1;
            for (int i = p; i <= r - 1; i++) {
                if (a[i] <= x) {
                    j++;
                    swap(a, j, i);
                }
            }
            swap(a, j + 1, r);
            return j + 1;
        }

        public void swap(int[] a, int i, int j) {
            int t = a[i];
            a[i] = a[j];
            a[j] = t;
        }
    }

    //二分查找类:适配者
    class BinarySearch {
        public int binarySearch(int array[], int key) {
            int low = 0;
            int high = array.length - 1;
            while (low <= high) {
                int mid = (low + high) / 2;
                int midVal = array[mid];
                if (midVal < key) {
                    low = mid + 1;
                } else if (midVal > key) {
                    high = mid - 1;
                } else {
                    return 1; //找到元素返回1
                }
            }
            return -1; //未找到元素返回-1
        }
    }

    //操做适配器:适配器
    class OperationAdapter implements ScoreOperation {
        private QuickSort sortObj; //定义适配者QuickSort对象
        private BinarySearch searchObj; //定义适配者BinarySearch对象

        public OperationAdapter() {
            sortObj = new QuickSort();
            searchObj = new BinarySearch();
        }

        public int[] sort(int array[]) {
            return sortObj.quickSort(array); //调用适配者类QuickSort的排序方法
        }

        public int search(int array[], int key) {
            return searchObj.binarySearch(array, key); //调用适配者类BinarySearch的查找方法
        }
    }
}

// 调用者
class Client {
    public static void main(String args[]) {
        ScoreOperation operation; //针对抽象目标接口编程
        operation = (ScoreOperation) XMLUtil.getBean(); //读取配置文件,反射生成对象(只须要修改配置文件,改为OperationAdapter就好了)
        int scores[] = {84, 76, 50, 69, 90, 91, 88, 96}; //定义成绩数组
        int result[];
        int score;
        System.out.println("成绩排序结果:");
        result = operation.sort(scores);
        //遍历输出成绩 
        for (int i : scores) {
            System.out.print(i + ",");
        }
        System.out.println();
        System.out.println("查找成绩90:");
        score = operation.search(result, 90);
        if (score != -1) {
            System.out.println("找到成绩90。");
        } else {
            System.out.println("没有找到成绩90。");
        }
        System.out.println("查找成绩92:");
        score = operation.search(result, 92);

        if (score != -1) {
            System.out.println("找到成绩92。");
        } else {
            System.out.println("没有找到成绩92。");
        }
    }
}

类适配器

除了对象适配器模式以外,适配器模式还有一种形式,那就是类适配器模式,类适配器模式和对象适配器模式最大的区别在于适配器和适配者之间的关系不一样,对象适配器模式中适配器和适配者之间是关联关系,而类适配器模式中适配器和适配者是继承关系编程

class Adapter extends Adaptee implements Target { 
    public void request() {
        specificRequest();
    }
}

因为 Java、C# 等语言不支持多重类继承,所以类适配器的使用受到不少限制,例如若是目标抽象类 Target 不 是接口,而是一个类,就没法使用类适配器;此外,若是适配者 Adapter 为最终(Final)类,也没法使用类适 配器。在 Java 等面向对象编程语言中,大部分状况下咱们使用的是对象适配器,类适配器较少使用。(类适配受java中没法多继承的限制)设计模式

优势

不管是对象适配器模式仍是类适配器模式都具备以下优势:数组

  • (1) 将目标类和适配者类解耦,经过引入一个适配器类来重用现有的适配者类,无须修改原有结构。
  • (2) 增长了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,并且提 高了适配者的复用性,同一个适配者类能够在多个不一样的系统中复用。
  • (3) 灵活性和扩展性都很是好,经过使用配置文件,能够很方便地更换适配器,也能够在不修改原有代码的基础上 增长新的适配器类,彻底符合“开闭原则”。

具体来讲,类适配器模式还有以下优势:app

  • 因为适配器类是适配者类的子类,所以能够在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。

对象适配器模式还有以下优势:编程语言

  • (1) 一个对象适配器能够把多个不一样的适配者适配到同一个目标;
  • (2) 能够适配一个适配者的子类,因为适配器和适配者之间是关联关系,根据“里氏代换原则”,适配者的子类也 可经过该适配器进行适配。

缺点

类适配器模式的缺点以下:ide

  • (1) 对于 Java、C# 等不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者;
  • (2) 适配者类不能为最终类,如在 Java 中不能为 final 类,C# 中不能为 sealed 类;
  • (3) 在 Java、C# 等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有必定的局限性。

对象适配器模式的缺点以下:

  • 与类适配器模式相比,要在适配器中置换适配者类的某些方法比较麻烦。若是必定要置换掉适配者类的一个或多个方法,能够先作一个适配者类的子类,将适配者类的方法置换掉,而后再把适配者类的子类当作真正的适配者进行适配,实现过程较为复杂。

适用场景

  • (1) 系统须要使用一些现有的类,而这些类的接口(如方法名)不符合系统的须要,甚至没有这些类的源代码。
  • (2) 想建立一个能够重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在未来引进的类一 起工做。

2、桥接模式(处理多维度变化)

引文

在正式介绍桥接模式以前,我先跟你们谈谈两种常见文具的区别,它们是毛笔和蜡笔。假如咱们须要大中小 3 种 型号的画笔,可以绘制 12 种不一样的颜色,若是使用蜡笔,须要准备 3×12 = 36 支,但若是使用毛笔的话,只需 要提供 3 种型号的毛笔,外加 12 个颜料盒便可,涉及到的对象个数仅为 3 + 12 = 15,远小于36,却能实现与 3 6 支蜡笔一样的功能。若是增长一种新型号的画笔,而且也须要具备 12 种颜色,对应的蜡笔需增长 12 支,而毛 笔只需增长一支。为何会这样呢?经过分析咱们能够得知:在蜡笔中,颜色和型号两个不一样的变化维度(即两 个不一样的变化缘由)融合在一块儿,不管是对颜色进行扩展仍是对型号进行扩展都势必会影响另外一个维度;但在毛 笔中,颜色和型号实现了分离,增长新的颜色或者型号对另外一方都没有任何影响。若是使用软件工程中的术 语,咱们能够认为在蜡笔中颜色和型号之间存在较强的耦合性(从而违反了单一原则:一个类只干一件事,不然若是另外一件事变了,那么你这个类又要修改),而毛笔很好地将两者解耦,使用起来很是灵 活,扩展也更为方便。在软件开发中,咱们也提供了一种设计模式来处理与画笔相似的具备多变化维度的情 况,即本章将要介绍的桥接模式。

桥接模式(JDBC的设计采用该模式)

桥接模式是一种很实用的结构型设计模式,若是软件系统中某个类存在两个独立变化的维度,经过该模式能够将这两个维度分离出来,使二者能够独立扩展,让系统更加符合“单一职责原则”。与多层继承方案不一样,它将两个独立变化的维度设计为两个独立的继承等级结构,而且在抽象层创建一个抽象关联,该关联关系相似一条链接两个独立继承结构的桥,故名桥接模式。

桥接模式用一种巧妙的方式处理多层继承存在的问题,用抽象关联取代了传统的多层继承,将类之间的静态继承关系转换为动态的对象组合关系,使得系统更加灵活,并易于扩展,同时有效控制了系统中类的个数。桥接定义以下:

桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们均可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。

桥接模式的结构与其名称同样,存在一条链接两个继承等级结构的桥,桥接模式结构如图所示:

  • Abstraction(抽象类):用于定义抽象类的接口,它通常是抽象类而不是接口,其中定义了一个 Implementor(实现类接口)类型的对象并能够维护该对象,它与 Implementor 之间具备关联关系,它既能够包含抽象业务方法,也能够包含具体业务方法。
  • RefinedAbstraction(扩充抽象类):扩充由 Abstraction 定义的接口,一般状况下它再也不是抽象类而是具体类,它实现了在 Abstraction 中声明的抽象业务方法,在 RefinedAbstraction 中能够调用在 Implementor 中定义的业务方法。
  • Implementor(实现类接口):定义实现类的接口,这个接口不必定要与 Abstraction 的接口彻底一致,事 实上这两个接口能够彻底不一样,通常而言,Implementor 接口仅提供基本操做,而 Abstraction 定义的接口 可能会作更多更复杂的操做。Implementor 接口对这些基本操做进行了声明,而具体实现交给其子类。经过 关联关系,在 Abstraction 中不只拥有本身的方法,还能够调用到 Implementor 中定义的方法,使用关联 关系来替代继承关系。
  • ConcreteImplementor(具体实现类):具体实现 Implementor 接口,在不一样的 ConcreteImplementor 中提供基本操做的不一样实现,在程序运行时,ConcreteImplementor 对象将替换其父类对象,提供给抽象 类具体的业务操做方法。

桥接模式是一个很是有用的模式,在桥接模式中体现了不少面向对象设计原则的思想,包括“单一职责原则”、“开闭原则”、“合成复用原则”、“里氏代换原则”、“依赖倒转原则”等。熟悉桥接模式有助于咱们深刻理解这些设计原则,也有助于咱们造成正确的设计思想和培养良好的设计风格。

在使用桥接模式时,咱们首先应该识别出一个类所具备的两个独立变化的维度,将它们设计为两个独立的继承等级结构,为两个维度都提供抽象层,并创建抽象耦合。一般状况下,咱们将具备两个独立变化维度的类的一些普通业务方法和与之关系最密切的维度设计为“抽象类”层次结构(抽象部分),而将另外一个维度设计为“实现类”层次结构(实现部分)。例如:对于毛笔而言,因为型号是其固有的维度,所以能够设计一个抽象的毛笔类,在该类中声明并部分实现毛笔的业务方法,而将各类型号的毛笔做为其子类;颜色是毛笔的另外一个维度,因为它与毛笔之间存在一种“设置”的关系,所以咱们能够提供一个抽象的颜色接口,而将具体的颜色做为实现该接口的子类。在此,型号可认为是毛笔的抽象部分,而颜色是毛笔的实现部分,结构示意图如图所示:

public class BridgingPattern {

    //像素矩阵类:辅助类,各类格式的文件最终都被转化为像素矩阵,不一样的操做系统提供不一样的方式显示像素矩阵
    class Matrix {
        //此处代码省略
    }

    //抽象图像类:抽象类
    abstract class Image {
        protected ImageImp imp;

        public void setImageImp(ImageImp imp) {
            this.imp = imp;
        }

        public abstract void parseFile(String fileName);
    }

    //抽象操做系统实现类:实现类接口
    interface ImageImp {
        public void doPaint(Matrix m); //显示像素矩阵
    }

    //Windows操做系统实现类:具体实现类
    class WindowsImp implements ImageImp {
        public void doPaint(Matrix m) { //调用Windows系统的绘制函数绘制像素矩阵
            System.out.print("在Windows操做系统中显示图像:");
        }
    }

    //Linux操做系统实现类:具体实现类
    class LinuxImp implements ImageImp {
        public void doPaint(Matrix m) { //调用Linux系统的绘制函数绘制像素矩阵
            System.out.print("在Linux操做系统中显示图像:");
        }
    }

    //Unix操做系统实现类:具体实现类
    class UnixImp implements ImageImp {
        public void doPaint(Matrix m) { //调用Unix系统的绘制函数绘制像素矩阵
            System.out.print("在Unix操做系统中显示图像:");
        }
    }

    //JPG格式图像:扩充抽象类
    class JPGImage extends Image {
        public void parseFile(String fileName) {
            //模拟解析JPG文件并得到一个像素矩阵对象m;
            Matrix m = new Matrix();
            imp.doPaint(m);
            System.out.println(fileName + ",格式为JPG。");
        }
    }

    //PNG格式图像:扩充抽象类
    class PNGImage extends Image {
        public void parseFile(String fileName) {
            //模拟解析PNG文件并得到一个像素矩阵对象m;
            Matrix m = new Matrix();
            imp.doPaint(m);
            System.out.println(fileName + ",格式为PNG。");
        }
    }
}


// 客户端调用
class Client {
    public static void main(String args[]) {
        Image image = (Image) XMLUtil.getBean("image");
        ImageImp imp = (ImageImp) XMLUtil.getBean("os");
        image.setImageImp(imp);
        image.parseFile("小龙女");
    }
}

适配器模式与桥接模式的联用

在某系统的报表处理模块中,须要将报表显示和数据采集分开,系统能够有多种报表显示方式也能够有多种数据采集方式,如能够从文本文件中读取数据,也能够从数据库中读取数据,还能够从 Excel 文件中获取数据。若是须要从 Excel 文件中获取数据,则须要调用与Excel 相关的 API,而这个 API 是现有系统所不具有的,该 API 由厂商提供。使用适配器模式和桥接模式设计该模块。

在设计过程当中,因为存在报表显示和数据采集两个独立变化的维度,所以可使用桥接模式进行初步设计;为了 使用 Excel 相关的 API 来进行数据采集则须要使用适配器模式。系统的完整设计中须要将两个模式联用,如图所示:

优势

(1)分离抽象接口及其实现部分。桥接模式使用“对象间的关联关系”解耦了抽象和实现之间固有的绑定关系,使 得抽象和实现能够沿着各自的维度来变化。所谓抽象和实现沿着各自维度的变化,也就是说抽象和实现再也不在同 一个继承层次结构中,而是“子类化”它们,使它们各自都具备本身的子类,以便任何组合子类,从而得到多维 度组合对象。

(2)在不少状况下,桥接模式能够取代多层继承方案,多层继承方案违背了“单一职责原则”,复用性较差,且类 的个数很是多,桥接模式是比多层继承方案更好的解决方法,它极大减小了子类的个数。

(3)桥接模式提升了系统的可扩展性,在两个变化维度中任意扩展一个维度,都不须要修改原有系统,符合“开闭原则”。

缺点

(1)桥接模式的使用会增长系统的理解与设计难度,因为关联关系创建在抽象层,要求开发者一开始就针对抽象层进行设计与编程。

(2)桥接模式要求正确识别出系统中两个独立变化的维度,所以其使用范围具备必定的局限性,如何正确识别两个独立维度也须要必定的经验积累。

适用场景

(1)若是一个系统须要在抽象化和具体化之间增长更多的灵活性,避免在两个层次之间创建静态的继承关系,经过 桥接模式可使它们在抽象层创建一个关联关系。

(2)“抽象部分”和“实现部分”能够以继承的方式独立扩展而互不影响,在程序运行时能够动态将一个抽象化子 类的对象和一个实现化子类的对象进行组合,即系统须要对抽象化角色和实现化角色进行动态耦合。

(3)一个类存在两个(或多个)独立变化的维度,且这两个(或多个)维度都须要独立进行扩展。

(4)对于那些不但愿使用继承或由于多层继承致使系统类的个数急剧增长的系统,桥接模式尤其适用。

3、组合模式(树形结构的处理)

pass

4、装饰模式(扩展系统功能)

装饰模式能够在不改变一个对象自己功能的基础上给对象增长额外的新行为,在现实生活中,这种状况也处处存在,例如一张照片,咱们能够不改变照片自己,给它增长一个相框,使得它具备防潮的功能,并且用户能够根据须要给它增长不一样类型的相框,甚至能够在一个小相框的外面再套一个大相框。

装饰模式是一种用于替代继承的技术,它经过一种无须定义子类的方式来给对象动态增长职责,使用对象之间的关联关系取代类之间的继承关系。在装饰模式中引入了装饰类,在装饰类中既能够调用待装饰的原有类的方法,还能够增长新的方法,以扩充原有类的功能。

装饰模式定义以下:

装饰模式(Decorator Pattern):动态地给一个对象增长一些额外的职责,就增长对象功能来讲,装饰模式比 生成子类实现更为灵活。装饰模式是一种对象结构型模式。

在装饰模式中,为了让系统具备更好的灵活性和可扩展性,咱们一般会定义一个抽象装饰类,而将具体的装饰类做为它的子类,装饰模式结构如图所示:

public class EncodingFilter implements Filter{
 
     
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
         
        //被加强的对象
        HttpServletRequest req = (HttpServletRequest) request;
        //加强对象
        EnhanceRequest enhanceRequest = new EnhanceRequest(req);
         
         
        chain.doFilter(enhanceRequest, response);
         
    }
 
 
}
 
class EnhanceRequest extends HttpServletRequestWrapper{     // 1>与要加强的类(HttpServletRequest类)继承/实现同一个类/接口
     
    private HttpServletRequest request;
 
    public EnhanceRequest(HttpServletRequest request) {     // 2>传入要加强的类
        super(request);
        this.request = request;
    }
     
    //3>对要加强的方法(getParaameter)重写
    @Override
    public String getParameter(String name) {
        String parameter = request.getParameter(name);//乱码
        try {
            parameter = new String(parameter.getBytes("iso8859-1"),"UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return parameter;
    }
     
}

注意事项

(1) 尽可能保持装饰类的接口与被装饰类的接口相同,这样,对于客户端而言,不管是装饰以前的对象仍是装饰以后 的对象均可以一致对待。这也就是说,在可能的状况下,咱们应该尽可能使用透明装饰模式。

(2) 尽可能保持具体构件类 ConcreteComponent 是一个“轻”类,也就是说不要把太多的行为放在具体构件类中,咱们能够经过装饰类对其进行扩展。

(3) 若是只有一个具体构件类,那么抽象装饰类能够做为该具体构件类的直接子类。

优势

(1) 对于扩展一个对象的功能,装饰模式比继承更加灵活性,不会致使类的个数急剧增长。(避免了继承->继承->继承)

(2) 能够经过一种动态的方式来扩展一个对象的功能,经过配置文件能够在运行时选择不一样的具体装饰类,从而实现不一样的行为。

(3) 能够对一个对象进行屡次装饰,经过使用不一样的具体装饰类以及这些装饰类的排列组合,能够创造出不少不一样行为的组合,获得功能更为强大的对象。

(4) 具体构件类与具体装饰类能够独立变化,用户能够根据须要增长新的具体构件类和具体装饰类,原有类库代码无须改变,符合“开闭原则”。

缺点

(1) 使用装饰模式进行系统设计时将产生不少小对象,这些对象的区别在于它们之间相互链接的方式有所不一样,而不是它们的类或者属性值有所不一样,大量小对象的产生势必会占用更多的系统资源,在必定程序上影响程序的性能。

(2) 装饰模式提供了一种比继承更加灵活机动的解决方案,但同时也意味着比继承更加易于出错,排错也很困难,对于屡次装饰的对象,调试时寻找错误可能须要逐级排查,较为繁琐。

适用场景

(1) 在不影响其余对象的状况下,以动态、透明的方式给单个对象添加职责。

(2) 当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护时可使用装饰模式。不能采用继 承的状况主要有两类:第一类是系统中存在大量独立的扩展,为支持每一种扩展或者扩展之间的组合将产生大量 的子类,使得子类数目呈爆炸性增加;第二类是由于类已定义为不能被继承(如 Java 语言中的 final 类)。

5、外观模式(其实web调用service层使用的就是外观模式)

在软件开发中,有时候为了完成一项较为复杂的功能,一个客户类须要和多个业务类交互,而这些须要交互的业务类常常会做为一个总体出现,因为涉及到的类比较多,致使使用时代码较为复杂,此时,特别须要一个相似服务员同样的角色,由它来负责和多个业务类进行交互,而客户类只需与该类交互。外观模式经过引入一个新的外 观类(Facade)来实现该功能,外观类充当了软件系统中的“服务员”,它为多个业务类的调用提供了一个统一 的入口,简化了类与类之间的交互。在外观模式中,那些须要交互的业务类被称为子系统(Subsystem)。若是 没有外观类,那么每一个客户类须要和多个子系统之间进行复杂的交互,系统的耦合度将很大,如图 2(A) 所示;而 引入外观类以后,客户类只须要直接与外观类交互,客户类与子系统之间原有的复杂引用关系由外观类来实 现,从而下降了系统的耦合度。

外观模式中,一个子系统的外部与其内部的通讯经过一个统一的外观类进行,外观类将客户类与子系统的内部复杂性分隔开,使得客户类只须要与外观角色打交道,而不须要与子系统内部的不少对象打交道。

外观模式定义以下:

外观模式(Facade Pattern):为子系统中的一组接口提供一个统一的入口。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

外观模式又称为门面模式,它是一种对象结构型模式。外观模式是迪米特法则的一种具体实现,经过引入一个新的外观角色能够下降原有系统的复杂度,同时下降客户类与子系统的耦合度

优势

 

(1) 它对客户端屏蔽了子系统组件,减小了客户端所需处理的对象数目,并使得子系统使用起来更加容易。经过引 入外观模式,客户端代码将变得很简单,与之关联的对象也不多。

(2) 它实现了子系统与客户端之间的松耦合关系,这使得子系统的变化不会影响到调用它的客户端,只须要调整外 观类便可。

(3) 一个子系统的修改对其余子系统没有任何影响,并且子系统内部变化也不会影响到外观对象。

缺点

(1) 不能很好地限制客户端直接使用子系统类,若是对客户端访问子系统类作太多的限制则减小了可变性和灵活 性。

(2) 若是设计不当,增长新的子系统可能须要修改外观类的源代码,违背了开闭原则。

适用场景

(1) 当要为访问一系列复杂的子系统提供一个简单入口时可使用外观模式。

(2) 客户端程序与多个子系统之间存在很大的依赖性。引入外观类能够将子系统与客户端解耦,从而提升子系统的独立性和可移植性。

(3) 在层次化结构中,可使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系,而经过外观类创建联系,下降层之间的耦合度。

6、享元模式(Spring)

当一个软件系统在运行时产生的对象数量太多,将致使运行代价太高,带来系统性能降低等问题。例如在一个文 本字符串中存在不少重复的字符,若是每个字符都用一个单独的对象来表示,将会占用较多的内存空间,那么 咱们如何去避免系统中出现大量相同或类似的对象,同时又不影响客户端程序经过面向对象的方式对这些对象进 行操做?享元模式正为解决这一类问题而诞生。享元模式经过共享技术实现相同或类似对象的重用,在逻辑上每 一个出现的字符都有一个对象与之对应,然而在物理上它们却共享同一个享元对象,这个对象能够出如今一个字 符串的不一样地方,相同的字符对象都指向同一个实例,在享元模式中,存储这些共享实例对象的地方称为享元池(Flyweight Pool)。咱们能够针对每个不一样的字符建立一个享元对象,将其放在享元池中,须要时再从享 元池取出。

享元模式(Flyweight Pattern):用共享技术有效地支持大量细粒度对象的复用。系统只使用少许的对象,而这些对象都很类似,状态变化很小,能够实现对象的屡次复用。因为享元模式要求可以共享的对象必须是细粒度对象,所以它又称为轻量级模式,它是一种对象结构型模式。

享元模式结构较为复杂,通常结合工厂模式一块儿使用,在它的结构图中包含了一个享元工厂类,其结构图如图所示:

  • Flyweight(抽象享元类):一般是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些 方法能够向外界提供享元对象的内部数据(内部状态),同时也能够经过这些方法来设置外部数据(外部状 态)。
  • ConcreteFlyweight(具体享元类):它实现了抽象享元类,其实例称为享元对象;在具体享元类中为内部 状态提供了存储空间。一般咱们能够结合单例模式来设计具体享元类,为每个具体享元类提供惟一的享元 对象。
  • UnsharedConcreteFlyweight(非共享具体享元类):并非全部的抽象享元类的子类都须要被共享,不 能被共享的子类可设计为非共享具体享元类;当须要一个非共享具体享元类的对象时能够直接经过实例化创 建。
  • FlyweightFactory(享元工厂类):享元工厂类用于建立并管理享元对象,它针对抽象享元类编程,将各类 类型的具体享元对象存储在一个享元池中,享元池通常设计为一个存储“键值对”的集合(也能够是其余类 型的集合),能够结合工厂模式进行设计;当用户请求一个具体享元对象时,享元工厂提供一个存储在享元 池中已建立的实例或者建立一个新的实例(若是不存在的话),返回新建立的实例并将其存储在享元池中。

在享元模式中引入了享元工厂类,享元工厂类的做用在于提供一个用于存储享元对象的享元池,当用户须要对象时,首先从享元池中获取,若是享元池中不存在,则建立一个新的享元对象返回给用户,并在享元池中保存该新增对象。典型的享元工厂类的代码以下:

class FlyweightFactory { //定义一个HashMap用于存储享元对象,实现享元池
        private HashMap flyweights = new HashMap();

        public Flyweight getFlyweight(String key) { //若是对象存在,则直接从享元池获取
            if (flyweights.containsKey(key)) {
                return (Flyweight) flyweights.get(key);
            }
            //若是对象不存在,先建立一个新的对象添加到享元池中,而后返回 
            else {
            Flyweight fw = newConcreteFlyweight();
            flyweights.put(key, fw);
            return fw;
            }
        }
    }

享元类的设计是享元模式的要点之一,在享元类中要将内部状态和外部状态分开处理,一般将内部状态做为享元类的成员变量,而外部状态经过注入的方式添加到享元类中。典型的享元类代码以下所示:

class Flyweight { //内部状态intrinsicState做为成员变量,同一个享元对象其内部状态是一致的
    private String intrinsicState;

    public Flyweight(String intrinsicState) {
        this.intrinsicState = intrinsicState;
    }

    //外部状态extrinsicState在使用时由外部设置,不保存在享元对象中,即便是同一个对象,在每一次调用时也能够传入不一样的外部
    public void operation(String extrinsicState) {
        // ......
    }
}

demo:

//围棋棋子类:抽象享元类
abstract class IgoChessman {
    public abstract String getColor();

    public void display() {
        System.out.println("棋子颜色:" + this.getColor());
    }
}

//黑色棋子类:具体享元类
class BlackIgoChessman extends IgoChessman {
    public String getColor() {
        return "黑色";
    }
}

//白色棋子类:具体享元类
class WhiteIgoChessman extends IgoChessman {
    public String getColor() {
        return "白色";
    }
}

//围棋棋子工厂类:享元工厂类,使用单例模式进行设计
class IgoChessmanFactory {
    private static IgoChessmanFactory instance = new IgoChessmanFactory();
    private static Hashtable ht; //使用Hashtable来存储享元对象,充当享元池

    private IgoChessmanFactory() {
        ht = new Hashtable();
        IgoChessman black, white;
        black = new BlackIgoChessman();
        ht.put("b", black);
        white = new WhiteIgoChessman();
        ht.put("w", white);
    }

    //返回享元工厂类的惟一实例
    public static IgoChessmanFactory getInstance() {
        return instance;
    }

    //经过key来获取存储在Hashtable中的享元对象
    public static IgoChessman getIgoChessman(String color) {
        return (IgoChessman) ht.get(color);
    }
}


class Client {
    public static void main(String args[]) {
        IgoChessman black1, black2, black3, white1, white2;
        IgoChessmanFactory factory;
        //获取享元工厂对象
        factory = IgoChessmanFactory.getInstance();
        //经过享元工厂获取三颗黑子
        black1 = factory.getIgoChessman("b");
        black2 = factory.getIgoChessman("b");
        black3 = factory.getIgoChessman("b");
        System.out.println("判断两颗黑子是否相同:" + (black1 == black2));
        //经过享元工厂获取两颗白子
        white1 = factory.getIgoChessman("w");
        white2 = factory.getIgoChessman("w");
        System.out.println("判断两颗白子是否相同:" + (white1 == white2));
        //显示棋子 
        black1.display();
        black2.display();
        black3.display();
        white1.display();
        white2.display();
    }
}

与其余模式的联用

(1)在享元模式的享元工厂类中一般提供一个静态的工厂方法用于返回享元对象,使用简单工厂模式来生成享元对象。

(2)在一个系统中,一般只有惟一一个享元工厂,所以可使用单例模式进行享元工厂类的设计。

(3)享元模式能够结合组合模式造成复合享元模式(为多个内部状态不一样的 享元对象设置相同的外部状态),统一对多个享元对象设置外部状态。

优势

(1) 能够极大减小内存中对象的数量,使得相同或类似对象在内存中只保存一份,从而能够节约系统资源,提升系统性能。

(2) 享元模式的外部状态相对独立,并且不会影响其内部状态,从而使得享元对象能够在不一样的环境中被共享。

缺点

(1) 享元模式使得系统变得复杂,须要分离出内部状态和外部状态,这使得程序的逻辑复杂化。

(2) 为了使对象能够共享,享元模式须要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变长。

适用场景

(1) 一个系统有大量相同或者类似的对象,形成内存的大量耗费。

(2) 对象的大部分状态均可之外部化,能够将这些外部状态传入对象中。

(3) 在使用享元模式时须要维护一个存储享元对象的享元池,而这须要耗费必定的系统资源,所以,应当在须要屡次重复使用享元对象时才值得使用享元模式。

7、代理模式

http://www.runoob.com/design-pattern/proxy-pattern.html

相关文章
相关标签/搜索