大话【设计模式】

一、简介

  • 设计模式是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案

二、六大原则

a、单一职责原则

【基本介绍】

  • 对类来说的,即一个类应该只负责一项职责。如类A负责两个不同职责:职责1,职责2。 当职责1需求变更而改变A时,可能造成职责2执行错误,所以需要将类A的粒度分解为 A1,A2

【注意事项和细节】

  1. 降低类的复杂度,一个类只负责一项职责
  2. 提高类的可读性,可维护性 
  3. 降低变更引起的风险
  4. 通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级违 反单一职责原则;只有类中方法数量足够少,可以在方法级别保持单一职责原

b、接口隔离原则

【基本介绍 】

  • 客户端不应该依赖它不需要的接 口,即一个类对另一个类的依赖 应该建立在最小的接口上

c、依赖倒转(DIP)原则

【基本介绍】

  •  高层模块不应该依赖低层模块,二者都应该依赖其抽象 
  • 抽象不应该依赖细节,细节应该依赖抽象 
  • 依赖倒转(倒置)的中心思想是面向接口编程
  • 依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的 多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在java中,抽象 指的是接口或抽象类,细节就是具体的实现类
  • 使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的 任务交给他们的实现类去完成

【注意事项和细节】

  1. 低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好. 
  2. 变量的声明类型尽量是抽象类或接口, 这样我们的变量引用和实际对象间,就存在 一个缓冲层,利于程序扩展和优化
  3. 继承时遵循里氏替换原则

d、里氏替换原则

【基本介绍】

  • 在使用继承时,遵循里氏替换原则,在子类中【尽量不要重写】父类的方法,可以把原先(A)继承的类,提升继承更基础(Base)的类,如果A类中要使用B类的方法,可以通依赖,聚合,组合方式实现。

e、开闭原则ocp

【基本介绍】

  • 开闭原则(Open Closed Principle)是编程中最基础、最重要的设计原则 
  • 一个软件实体如类,模块和函数应该对扩展开放(对提供方),对修改关闭(对使用 方)。用抽象构建框架,用实现扩展细节。 
  • 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已 有的代码来实现变化。
  • 编程中遵循其它原则,以及使用设计模式的目的就是遵循开闭原则。

f、迪米特法则

【基本介绍】

  • 一个对象应该对其他对象保持最少的了解 
  • 类与类关系越密切,耦合度越大
  • 迪米特法则(Demeter Principle)又叫最少知道原则,即一个类对自己依赖的类知道的 越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内 部。对外除了提供的public 方法,不对外泄露任何信息 
  • 迪米特法则还有个更简单的定义:只与直接的朋友通信

【注意事项和细节】

  1. 迪米特法则的核心是降低类之间的耦合 
  2.  但是注意:由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低 类间(对象间)耦合关系, 并不是要求完全没有依赖关系

三、常见设计模式

==============================创建型模式===================================

3.1、单例模式

【基本介绍】

  • 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类 只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)

单例模式有八种方式

1、饿汉式(静态常量)

package com.atguigu.singleton.type1;

public class SingletonTest01 {

    public static void main(String[] args) {
        //测试
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance == instance2); // true
        System.out.println("instance.hashCode=" + instance.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());
    }

}

//饿汉式(静态变量)

class Singleton {
    
    //1. 构造器私有化, 外部能new
    private Singleton() {
        
    }
    
    //2.本类内部创建对象实例
    private final static Singleton instance = new Singleton();
    
    //3. 提供一个公有的静态方法,返回实例对象
    public static Singleton getInstance() {
        return instance;
    }
    
}
View Code

2、饿汉式(静态代码块)

package com.atguigu.singleton.type2;

public class SingletonTest02 {

    public static void main(String[] args) {
        //测试
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance == instance2); // true
        System.out.println("instance.hashCode=" + instance.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());
    }

}

//饿汉式(静态变量)

class Singleton {
    
    //1. 构造器私有化, 外部能new
    private Singleton() {
        
    }
    

    //2.本类内部创建对象实例
    private  static Singleton instance;
    
    static { // 在静态代码块中,创建单例对象
        instance = new Singleton();
    }
    
    //3. 提供一个公有的静态方法,返回实例对象
    public static Singleton getInstance() {
        return instance;
    }
    
}
View Code

3、懒汉式(线程不安全)

package com.atguigu.singleton.type3;


public class SingletonTest03 {

    public static void main(String[] args) {
        System.out.println("懒汉式1 , 线程不安全~");
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance == instance2); // true
        System.out.println("instance.hashCode=" + instance.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());
    }

}

class Singleton {
    private static Singleton instance;
    
    private Singleton() {}
    
    //提供一个静态的公有方法,当使用到该方法时,才去创建 instance
    //即懒汉式
    public static Singleton getInstance() {
        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
View Code

4、懒汉式(线程安全,同步方法)

package com.atguigu.singleton.type4;


public class SingletonTest04 {

    public static void main(String[] args) {
        System.out.println("懒汉式2 , 线程安全~");
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance == instance2); // true
        System.out.println("instance.hashCode=" + instance.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());
    }

}

// 懒汉式(线程安全,同步方法)
class Singleton {
    private static Singleton instance;
    
    private Singleton() {}
    
    //提供一个静态的公有方法,加入同步处理的代码,解决线程安全问题
    //即懒汉式
    public static synchronized Singleton getInstance() {
        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
View Code

5、懒汉式(线程安全,同步代码块)

package com.atguigu.singleton.type5;

public class SingletonTest05 {
    public static void main(String[] args) {
        System.out.println("线程安全,同步代码块");
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance == instance2); // true
        System.out.println("instance.hashCode=" + instance.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());
        
    }
}

//懒汉式(线程安全,同步方法)
class Singleton {
    private static volatile Singleton instance;
    
    private Singleton() {}
    
    public static synchronized Singleton getInstance() {
        if(instance == null) {
            synchronized (Singleton.class) {
                instance = new Singleton();
            }
            
        }
        return instance;
    }
}
View Code

6、双重检查

package com.atguigu.singleton.type6;


public class SingletonTest06 {

    public static void main(String[] args) {
        System.out.println("双重检查");
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance == instance2); // true
        System.out.println("instance.hashCode=" + instance.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());
        
    }

}

// 懒汉式(线程安全,同步方法)
class Singleton {
    private static volatile Singleton instance;
    
    private Singleton() {}
    
    //提供一个静态的公有方法,加入双重检查代码,解决线程安全问题, 同时解决懒加载问题
    //同时保证了效率, 推荐使用
    
    public static synchronized Singleton getInstance() {
        if(instance == null) {
            synchronized (Singleton.class) {
                if(instance == null) {
                    instance = new Singleton();
                }
            }
            
        }
        return instance;
    }
}
View Code

7、静态内部类

package com.atguigu.singleton.type7;


public class SingletonTest07 {

    public static void main(String[] args) {
        System.out.println("使用静态内部类完成单例模式");
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance == instance2); // true
        System.out.println("instance.hashCode=" + instance.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());
        
    }

}

// 静态内部类完成, 推荐使用
class Singleton {
    private static volatile Singleton instance;
    
    //构造器私有化
    private Singleton() {}
    
    //写一个静态内部类,该类中有一个静态属性 Singleton
    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton(); 
    }
    
    //提供一个静态的公有方法,直接返回SingletonInstance.INSTANCE
    
    public static synchronized Singleton getInstance() {
        
        return SingletonInstance.INSTANCE;
    }
}
View Code

8、枚举

package com.atguigu.singleton.type8;

public class SingletonTest08 {
    public static void main(String[] args) {
        Singleton instance = Singleton.INSTANCE;
        Singleton instance2 = Singleton.INSTANCE;
        System.out.println(instance == instance2);
        
        System.out.println(instance.hashCode());
        System.out.println(instance2.hashCode());
        
        instance.sayOK();
    }
}

//使用枚举,可以实现单例, 推荐
enum Singleton {
    INSTANCE; //属性
    public void sayOK() {
        System.out.println("ok~");
    }
}
View Code

【注意事项和细节】

  1. 单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需 要频繁创建销毁的对象,使用单例模式可以提高系统性能 
  2. 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使 用new
  3. 单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或 耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数 据库或文件的对象(比如数据源、session工厂等)

3.2、简单工厂模式(不属于GOF23

【基本介绍】

  • 简单工厂模式是属于创建型模式,是工厂模式的一种。简单工厂模式是由一 个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族 中最简单实用的模式 
  • 简单工厂模式:定义了一个创建对象的类,由这个类来封装实例化对象的行为
  • 在软件开发中,当我们会用到大量的创建某种、某类或者某批对象时,就会 使用到工厂模式.

3.3、工厂方法模式

【基本介绍】

  • 工厂方法模式设计方案:将披萨项目的实例化功能抽象成抽象方法,在不同的口味点 餐子类中具体实现。
  • 工厂方法模式:定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方 法模式将对象的实例化推迟到子类

3.4、抽象工厂模式

【基本介绍】

  • 抽象工厂模式:定义了一个interface用于创建相关或有依赖关系的对象簇,而无需 指明具体的类
  • 抽象工厂模式可以将简单工厂模式和工厂方法模式进行整合。 
  • 从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者称为进一步的抽象)。 
  • 将工厂抽象成两层,AbsFactory(抽象工厂) 和 具体实现的工厂子类。程序员可以 根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇, 更利于代码的维护和扩展。 

【工厂模式小结】

  1. 工厂模式的意义 将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的 依赖关系的解耦。从而提高项目的扩展和维护性。
  2. 三种工厂模式 (简单工厂模式、工厂方法模式、抽象工厂模式)
  3. 设计模式的依赖抽象原则
    • 创建对象实例时,不要直接 new 类, 而是把这个new 类的动作放在一个工厂的方法 中,并返回。有的书上说,变量不要直接持有具体类的引用。 
    • 不要让类继承具体类,而是继承抽象类或者是实现interface(接口) 
    • 不要覆盖基类中已经实现的方法。

3.5、原型模式

【基本介绍】

  • 原型模式(Prototype模式)是指:用原型实例指定创建对象的种类,并且通过拷 贝这些原型,创建新的对象 
  • 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象, 无需知道如何创建的细节 
  • 工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建 的对象通过请求原型对象拷贝它们自己来实施创建,即 对象.clone() 

【注意事项与细节】

  1. 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提 高效率 
  2. 不用重新初始化对象,而是动态地获得对象运行时的状态 
  3. 如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化, 无需修改代码 
  4. 在实现深克隆的时候可能需要比较复杂的代码 
  5. 缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有 的类进行改造时,需要修改其源代码,违背了ocp原则,这点请同学们注意.

3.6、建造者模式

【基本介绍】

  • 建造者模式(Builder Pattern) 又叫生成器模式,是一种对象构建模式。它可以 将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方 法可以构造出不同表现(属性)的对象。
  • 建造者模式 是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象 的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。

【建造者模式的四个角色】

  1. Product(产品角色): 一个具体的产品对象。 
  2. Builder(抽象建造者): 创建一个Product对象的各个部件指定的 接口/抽象类。
  3. ConcreteBuilder(具体建造者): 实现接口,构建和装配各个部件。 
  4. Director(指挥者): 构建一个使用Builder接口的对象。它主要是用于创建一个 复杂的对象。它主要有两个作用,一是:隔离了客户与对象的生产过程,二是: 负责控制产品对象的生产过程。

【注意事项和细节】

  1. 客户端(使用程序)不必知道产品内部组成的细节,将产品本身与产品的创建过程解 耦,使得相同的创建过程可以创建不同的产品对象
  2. 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替 换具体建造者或增加新的具体建造者, 用户使用不同的具体建造者即可得到不同 的产品对象 
  3. 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法 中,使得创建过程更加清晰,也更方便使用程序来控制创建过程 
  4. 增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程, 系统扩展方便,符合 “开闭原则”
  5. 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间 的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。 
  6. 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化, 导致系统变得很庞大,因此在这种情况下,要考虑是否选择建造者模式.
  7. 抽象工厂模式VS建造者模式 
    • 抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不 同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心什么产品 由什么工厂生产即可。而建造者模式则是要求按照指定的蓝图建造产品,它的主要 目的是通过组装零配件而产生一个新产品

==============================结构型模式===================================

3.7、适配器模式

【基本介绍】

  • 适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表 示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同 工作。其别名为包装器(Wrapper) 
  • 适配器模式属于结构型模式 
  • 主要分为三类:类适配器模式、对象适配器模式、接口适配器模式

 【工作原理 】

  • 适配器模式:将一个类的接口转换成另一种接口.让原本接口不兼容的类可以兼容
  • 从用户的角度看不到被适配者,是解耦的
  • 用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口 方法 
  • 用户收到反馈结果,感觉只是和目标接口交互

【类适配器模式注意事项和细节】

  1. Java是单继承机制,所以类适配器需要继承src类这一点算是一个缺点, 因为这要 求dst必须是接口,有一定局限性; 
  2. src类的方法在Adapter中都会暴露出来,也增加了使用的成本。 
  3. 由于其继承了src类,所以它可以根据需求重写src类的方法,使得Adapter的灵 活性增强了。

【对象适配器模式介绍】

  1. 基本思路和类的适配器模式相同,只是将Adapter类作修改,不是继承src类,而 是持有src类的实例,以解决兼容性的问题。 即:持有 src类,实现 dst 类接口, 完成src->dst的适配
  2. 根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系。 
  3. 对象适配器模式是适配器模式常用的一种

【对象适配器模式注意事项和细节】

  1. 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。 根据合成复用原则,使用组合替代继承, 所以它解决了类适配器必须继承src的 局限性问题,也不再要求dst必须是接口
  2. 使用成本更低,更灵活。

【适配器模式的注意事项和细节】

  1. 三种命名方式,是根据 src是以怎样的形式给到Adapter(在Adapter里的形式)来 命名的。 
    1. 类适配器:以类给到,在Adapter里,就是将src当做类,继承
    2. 对象适配器:以对象给到,在Adapter里,将src作为一个对象,持有
    3. 接口适配器:以接口给到,在Adapter里,将src作为一个接口,实现
  2. Adapter模式最大的作用还是将原本不兼容的接口融合在一起工作。 
  3. 实际开发中,实现起来不拘泥于我们讲解的三种经典形式

3.8、桥接模式

【基本介绍】

  • 桥接模式(Bridge模式)是指:将实现与抽象放在两个不同的类层次中,使两个层 次可以独立改变。 
  • Bridge模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同 的类承担不同的职责。它的主要特点是把抽象(Abstraction)与行为实现 (Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能 扩展

【注意事项和细节】

  1. 实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实 现部分独立开来,这有助于系统进行分层设计,从而产生更好的结构化系统。 
  2. 对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部 分由具体业务来完成。 
  3. 桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本。
  4. 桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层, 要求开发者针对抽象进行设计和编程 
  5. 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围有一定的局 限性,即需要有这样的应用场景。

3.9、装饰者设计模式

【基本介绍】

  • 装饰者模式:动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更 有弹性,装饰者模式也体现了开闭原则(ocp)
  • 这里提到的动态的将新功能附加到对象ocp原则,在后面的应用实例上会以代 码的形式体现,请同学们注意体会。

3.10、组合模式

【基本介绍】

  • 组合模式(Composite Pattern),又叫部分整体模式,它创建了对象组的树形结 构,将对象组合成树状结构以表示“整体-部分”的层次关系。
  • 组合模式依据树形结构来组合对象,用来表示部分以及整体层次。 
  • 这种类型的设计模式属于结构型模式。 
  • 组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客 户以一致的方式处理个别对象以及组合对象

【注意事项和细节】

  1. 简化客户端操作。客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子 的问题。 
  2. 具有较强的扩展性。当我们要更改组合对象时,我们只需要调整内部的层次关系, 客户端不用做出任何改动. 
  3. 方便创建出复杂的层次结构。客户端不用理会组合里面的组成细节,容易添加节点 或者叶子从而创建出复杂的树形结构
  4. 需要遍历组织机构,或者处理的对象具有树形结构时, 非常适合使用组合模式
  5. 要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性 都不一样,不适合使用组合模式

2.11、外观模式

【基本介绍】

  • 外观模式(Facade),也叫“过程模式:外观模式为子系统中的一组接口提供 一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加 容易使用 
  • 外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端 只需跟这个接口发生调用,而无需关心这个子系统的内部细节

【注意事项和细节】

  1. 外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复 杂性 
  2. 外观模式对客户端与子系统的耦合关系,让子系统内部的模块更易维护和扩展 
  3. 通过合理的使用外观模式,可以帮我们更好的划分访问的层次
  4. 当系统需要进行分层设计时,可以考虑使用Facade模式
  5. 在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时 可以考虑为新系统开发一个Facade类,来提供遗留系统的比较清晰简单的接口, 让新系统与Facade类交互,提高复用性 
  6. 不能过多的或者不合理的使用外观模式,使用外观模式好,还是直接调用模块好。 要以让系统有层次,利于维护为目的。

2.12、享元模式

【基本介绍】

  • 享元模式(Flyweight Pattern) 也叫 蝇量模式: 运用共享技术有效地支持大量细粒度的对象
  • 常用于系统底层开发,解决系统的性能问题。像 数据库连接池,里面都是创建好的连接对象,在 这些连接对象中有我们需要的则直接拿来用,避 免重新创建,如果没有我们需要的,则创建一个 
  • 享元模式能够解决重复对象的内存浪费的问题, 当系统中有大量相似对象,需要缓冲池时。不需 总是创建新对象,可以从缓冲池里拿。这样可以 降低系统内存,同时提高效率 
  • 享元模式经典的应用场景就是池技术了,String常 量池、数据库连接池、缓冲池等等都是享元模式 的应用,享元模式是池技术的重要实现方式

【内部状态和外部状态】

  • 享元模式提出了两个要求:细粒度和共享对象。这里就涉及到内部状态和外部状态 了,即将对象的信息分为两个部分:内部状态外部状态
  • 内部状态指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变
  • 外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态。

【注意事项与细节】

  1. 在享元模式这样理解,“享”就表示共享,“元”表示对象 
  2. 系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时, 我们就可以考虑选用享元模式
  3. 用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用 HashMap/HashTable存储 
  4. 享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率 
  5. 享元模式提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有 固化特性,不应该随着内部状态的改变而改变,这是我们使用享元模式需要注意的 地方. 
  6. 使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制。 
  7. 享元模式经典的应用场景是需要缓冲池的场景,比如 String常量池、数据库连接池

2.13、代理模式

【基本介绍】

  • 代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理 对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的 功能操作,即扩展目标对象的功能。 
  • 被代理的对象可以是远程对象创建开销大的对象需要安全控制的对象 
  • 代理模式有不同的形式, 主要有三种:静态代理动态代理 (JDK代理、接口代 理)和 Cglib代理 (可以在内存动态的创建对象,而不需要实现接口, 他是属于 动态代理的范畴) 。 

【静态代理】

  • 静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一 起实现相同的接口或者是继承相同父类

【静态代理优缺点】

  • 优点:在不修改目标对象的功能前提下, 能通过代理对象对目标功能扩展 
  • 缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类 ;一旦接口增加方法,目标对象与代理对象都要维护

【动态代理】

  • 代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理 
  • 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象 
  • 动态代理也叫做:JDK代理、接口代理;php的话可以借助__call()实现

==============================行为型模式===================================

2.14、模板方法模式

【基本介绍】

  • 模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern),在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法 实现,但调用将以抽象类中定义的方式进行。
  • 简单说,模板方法模式 定义一个操作中的算法的骨架,而将一些步骤延迟到子 类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定 步骤

【注意事项和细节】

  1. 基本思想是:算法只存在于一个地方,也就是在父类中,容易修改。需要修改算 法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改 
  2. 实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接 使用。
  3. 既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不 变,同时由子类提供部分步骤的实现。 
  4. 该模式的不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加, 使得系统更加庞大 
  5. 一般模板方法都加上final关键字, 防止子类重写模板方法.
  6. 模板方法模式使用场景:当要完成在某个过程,该过程要执行一系列步骤这一 系列的步骤基本相同,但其个别步骤在实现时 可能不同,通常考虑用模板方法模式来处理

2.15、命令模式

【基本介绍】

  • 命令模式(Command Pattern):在软件设计中,我们经常需要 向某些对象发送请求,但是并不知道请求的接收者是谁,也不知 道被请求的操作是哪个, 我们只需在程序运行时指定具体的请求接收者即可,此时,可以 使用命令模式来进行设计 
  • 命名模式使得请求发送者与请求接收者消除彼此之间的耦合,让 对象之间的调用关系更加灵活,实现解耦。
  • 在命名模式中,会将一个请求封装为一个对象,以便使用不同参 数来表示不同的请求(即命名),同时命令模式也支持可撤销的操作。 
  • 通俗易懂的理解:将军发布命令,士兵去执行。其中有几个角色: 将军(命令发布者)士兵(命令的具体执行者)命令(连接将 军和士兵)。 
  • Invoker是调用者(将军),Receiver是被调用者(士兵), MyCommand是命令,实现了Command接口,持有接收对象

【注意事项和细节】

  1. 将发起请求的对象与执行请求的对象解耦。发起请求的对象是调用者,调用者只要 调用命令对象的execute()方法就可以让接收者工作,而不必知道具体的接收者对 象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说:” 请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到 了纽带桥梁的作用。 
  2. 容易设计一个命令队列。只要把命令对象放到列队,就可以多线程的执行命令 
  3. 容易实现对请求的撤销和重做 
  4. 命令模式不足:可能导致某些系统有过多的具体命令类,增加了系统的复杂度,这 点在在使用的时候要注意 
  5. 空命令也是一种设计模式,它为我们省去了判空的操作。在上面的实例中,如果没 有用空命令,我们每按下一个按键都要判空,这给我们编码带来一定的麻烦。 
  6. 命令模式经典的应用场景:界面的一个按钮都是一条命令、模拟CMD(DOS命令) 订单的撤销/恢复、触发-反馈机制

2.16、访问者模式

【基本介绍】

  • 访问者模式(Visitor Pattern),封装一些作用于某种数据结构的各元素的操作, 它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。 
  • 主要将数据结构与数据操作分离,解决 数据结构操作耦合性问题
  • 访问者模式的基本工作原理是:在被访问的类里面加一个对外提供接待访问者 的接口
  • 访问者模式主要应用场景是:需要对一个对象结构中的对象进行很多不同操作 (这些操作彼此没有关联),同时需要避免让这些操作"污染"这些对象的类,可以 选用访问者模式解决

【注意事项和细节】

优点 :

  • 访问者模式符合单一职责原则、让程序具有优秀的扩展性、灵活性非常高 
  • 访问者模式可以对功能进行统一,可以做报表、UI、拦截器与过滤器,适用于数据 结构相对稳定的系统

缺点:

  • 具体元素对访问者公布细节,也就是说访问者关注了其他类的内部细节,这是迪米 特法则所不建议的, 这样造成了具体元素变更比较困难 
  • 违背了依赖倒转原则。访问者依赖的是具体元素,而不是抽象元素
  • 因此,如果一个系统有比较稳定的数据结构,又有经常变化的功能需求,那么访问 者模式就是比较合适的.

2.17、迭代器模式

【基本介绍】

  • 迭代器模式(Iterator Pattern)是常用的设计模式,属于行为型模式 
  • 如果我们的集合元素是用不同的方式实现的,有数组,还有java的集合类, 或者还有其他方式,当客户端要遍历这些集合元素的时候就要使用多种遍历 方式,而且还会暴露元素的内部结构,可以考虑使用迭代器模式解决。
  • 迭代器模式,提供一种遍历集合元素的统一接口,用一致的方法遍历集合元素, 不需要知道集合对象的底层表示,即:不暴露其内部的结构。

【注意事项和细节】

优点:

  • 提供一个统一的方法遍历对象,客户不用再考虑聚合的类型,使用一种方法就可以 遍历对象了。 
  • 隐藏了聚合的内部结构,客户端要遍历聚合的时候只能取到迭代器,而不会知道聚 合的具体组成。 
  • 提供了一种设计思想,就是一个类应该只有一个引起变化的原因(叫做单一责任 原则)。在聚合类中,我们把迭代器分开,就是要把管理对象集合和遍历对象集 合的责任分开,这样一来集合改变的话,只影响到聚合对象。而如果遍历方式改变 的话,只影响到了迭代器。 
  • 当要展示一组相似对象,或者遍历一组相同对象时使用, 适合使用迭代器模式

缺点:

  • 每个聚合对象都要一个迭代器,会生成多个迭代器不好管理类

2.18、观察者模式

【基本介绍】

  • 观察者模式:对象之间多对一依赖的一种设计方案,被依赖的对象为Subject, 依赖的对象为Observer,Subject通知Observer变化,比如这里的奶站是 Subject,是1的一方。用户时Observer,是多的一方。

【观察者模式的好处】

  • 观察者模式设计后,会以集合的方式来管理用户(Observer),包括注册,移除 和通知。 
  • 这样,我们增加观察者(这里可以理解成一个新的公告板),就不需要去修改核 心类WeatherData不会修改代码,遵守了ocp原则。

2.19、中介者模式

【基本介绍】

  • 中介者模式(Mediator Pattern),用一个中介对象来封装一系列的对象交互。 中介者使各个对象不需要显式地相互引用,从而使其耦合松散,而且可以独立 地改变它们之间的交互 
  • 中介者模式属于行为型模式,使代码易于维护 
  • 比如MVC模式,C(Controller控制器)是M(Model模型)和V(View视图)的中 介者,在前后端交互时起到了中间人的作用

【注意事项和细节】

  • 多个类相互耦合,会形成网状结构, 使用中介者模式将网状结构分离为星型结构, 进行解耦
  • 减少类间依赖,降低了耦合,符合迪米特原则 
  • 中介者承担了较多的责任,一旦中介者出现了问题,整个系统就会受到影响 
  • 如果设计不当,中介者对象本身变得过于复杂,这点在实际使用时,要特别注意

2.20、备忘录模式

【基本介绍】

  • 备忘录模式(Memento Pattern)在不破坏封装性的前提下,捕获一个对象的内 部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保 存的状态
  • 可以这里理解备忘录模式:现实生活中的备忘录是用来记录某些要去做的事情, 或者是记录已经达成的共同意见的事情,以防忘记了。而在软件层面,备忘录 模式有着相同的含义,备忘录对象主要用来记录一个对象的某种状态,或者某 些数据,当要做回退时,可以从备忘录对象里获取原来的数据进行恢复操作 

【注意事项和细节】

  • 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史 的状态
  • 实现了信息的封装,使得用户不需要关心状态的保存细节
  • 如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定 的内存, 这个需要注意 
  • 适用的应用场景:1、后悔药。 2、打游戏时的存档。 3、Windows 里的 ctri + z。 4、IE 中的后退。 4、数据库的事务管理 
  • 为了节约内存,备忘录模式可以和原型模式配合使用

2.21、解释器模式

【基本介绍】

  • 在编译原理中,一个算术表达式通过词法分析器形成词法单元,而后这些词法 单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树。这 里的词法分析器和语法分析器都可以看做是解释器 
  • 解释器模式(Interpreter Pattern):是指给定一个语言(表达式),定义它的文法 的一种表示,并定义一个解释器,使用该解释器来解释语言中的句子(表达式) 
  •  应用场景

• 应用可以将一个需要解释执行的语言中的句子表示为一个抽象语法树

• 一些重复出现的问题可以用一种简单的语言来表达

• 一个简单语法需要解释的场景 

【注意事项和细节】

  • 当有一个语言需要解释执行,可将该语言中的句子表示为一个抽象语法树,就可以 考虑使用解释器模式,让程序具有良好的扩展性
  • 应用场景:编译器、运算表达式计算、正则表达式、机器人等
  • 使用解释器可能带来的问题:解释器模式会引起类膨胀、解释器模式采用递归调用 方法,将会导致调试非常复杂、效率可能降低.

2.22、状态模式

【基本介绍】

  • 状态模式(State Pattern):它主要用来解决对象在多种状态转换时,需要对外 输出不同的行为的问题。状态和行为是一一对应的,状态之间可以相互转换 
  • 当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了 其类

【注意事项和细节】

  • 代码有很强的可读性。状态模式将每个状态的行为封装到对应的一个类中
  • 方便维护。将容易产生问题的if-else语句删除了,如果把每个状态的行为都放到一 个类中,每次调用方法时都要判断当前是什么状态,不但会产出很多if-else语句, 而且容易出错 
  • 符合“开闭原则”。容易增删状态 
  • 会产生很多类。每个状态都要一个对应的类,当状态过多时会产生很多类,加大维 护难度 
  • 应用场景:当一个事件或者对象有很多种状态,状态之间会相互转换,对不同的状 态要求有不同的行为的时候,可以考虑使用状态模式

2.23、策略模式

【基本介绍】

  • 策略模式(Strategy Pattern)中,定义算法族,分别封装起来,让他们之间可以 互相替换,此模式让算法的变化独立于使用算法的客户 
  • 这算法体现了几个设计原则,第一、把变化的代码从不变的代码中分离出来; 第二、针对接口编程而不是具体类(定义了策略接口);第三、多用组合/聚合, 少用继承(客户通过组合方式使用策略)。

【注意事项和细节】

  • 策略模式的关键是:分析项目中变化部分与不变部分 
  • 策略模式的核心思想是:多用组合/聚合 少用继承;用行为类组合,而不是行为的 继承。更有弹性 
  • 体现了“对修改关闭,对扩展开放”原则,客户端增加行为不用修改原有代码,只 要添加一种策略(或者行为)即可,避免了使用多重转移语句(if..else if..else) 
  • 提供了可以替换继承关系的办法: 策略模式将算法封装在独立的Strategy类中使得 你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展
  • 需要注意的是:每添加一个策略就要增加一个类,当策略过多是会导致类数目庞大

2.24、责任链模式

【基本介绍】

  • 职责链模式(Chain of Responsibility Pattern), 又叫 责任链模式,为请求创建了一个接收者 对象的链(简单示意图)。这种模式对请求的 发送者和接收者进行解耦。 
  • 职责链模式通常每个接收者都包含对另一个接 收者的引用。如果一个对象不能处理该请求, 那么它会把相同的请求传给下一个接收者,依 此类推。

【注意事项和细节】

  • 将请求和处理分开,实现解耦,提高系统的灵活性 
  • 简化了对象,使对象不需要知道链的结构 
  • 性能会受到影响,特别是在链比较长的时候,因此需控制链中最大节点数量,一般 通过在Handler中设置一个最大节点数量,在setNext()方法中判断是否已经超过阀值, 超过则不允许该链建立,避免出现超长链无意识地破坏系统性能
  • 调试不方便。采用了类似递归的方式,调试时逻辑可能比较复杂 
  •  最佳应用场景:有多个对象可以处理同一个请求时,比如:多级请求、请假/加薪 等审批流程、Java Web中Tomcat对Encoding的处理、拦截器
posted @ 2019-12-23 11:27  大数据老司机  阅读(367)  评论(0编辑  收藏  举报