常用的设计模式

设计模式

1 什么是设计模式

软件设计模式(Software Design Pattern),又称设计模式,是一套被反复使用、多数人知晓的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。

2 设计模式的种类23种

  • 创建型模式

    用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”。GoF(四人组)书中提供了单例、原型、工厂方法、抽象工厂、建造者等5种创建型模式。

  • 结构型模式

    用于描述如何将类或对象按某种布局组成更大的结构,GoF(四人组)书中提供了代理适配器、桥接、装饰、外观、享元、组合等 7 种结构型模式。

  • 行为型模式

    用于描述类或对象之间怎样相互协作共同完成单个对象无法单独完成的任务,以及怎样分配职责。GoF(四人组)书中提供了模板方法策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器等 11 种行为型模式。

单例模式

  1. 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

    这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

  2. 单例模式的创建方式

第一种:饿汉式--->不管是否使用该对象,该类会直接创建。

第二种: 懒汉式--->当使用到该对象在创建

饿汉式-方式1(静态变量方式)

package com.lqh;


public class 饿汉式 {
    public static void main(String[] args) {
        Singleton s1=Singleton.getInstance();
        Singleton s2=Singleton.getInstance();
        Singleton s3=Singleton.getInstance();
        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s3);
    }
}
//创建一个单例类
class Singleton{
     //构造方法私有化---
     private Singleton(){}

     //创建一个本类的对象---static:随着类的加在而被加在到内存而且只会加载一次
     private static Singleton singleton=new Singleton();

     //静态方法 返回本类创建的类对象
     public static Singleton getInstance(){
         return singleton;
     }
}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

说明:

该方式在成员位置声明Singleton类型的静态变量,并创建Singleton类的对象instance。instance对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。

2.懒汉式-方式1(线程不安全)

package com.lqh;


public class 懒汉式01 {
    public static void main(String[] args) {
        Singleton01 s1 = Singleton01.getInstance();
        Singleton01 s2 = Singleton01.getInstance();
        System.out.println(s1);
        System.out.println(s2);
    }
}
//创建一个单例类
class Singleton01{
    //构造方法私有化---
    private Singleton01(){}

    //创建一个本类的对象---static:随着类的加在而被加在到内存而且只会加载一次
    private static Singleton01 singleton;

    //静态方法 返回本类创建的类对象
    public static Singleton01 getInstance(){
         if(singleton==null){
             singleton=new Singleton01();
         }
         return singleton;
    }
}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

说明:

从上面代码我们可以看出该方式在成员位置声明Singleton类型的静态变量,并没有进行对象的赋值操作,那么什么时候赋值的呢?当调用getInstance()方法获取Singleton类的对象的时候才创建Singleton类的对象,这样就实现了懒加载的效果。但是,如果是多线程环境,会出现线程安全问题。

3.懒汉式-方式2(线程安全)

package com.lqh;


public class 懒汉式02 {
    public static void main(String[] args) {
        Singleton01 s1 = Singleton01.getInstance();
        Singleton01 s2 = Singleton01.getInstance();
        System.out.println(s1);
        System.out.println(s2);
    }
}

//创建一个单例类
class Singleton02{
    //构造方法私有化---
    private Singleton02(){}

    //创建一个本类的对象---static:随着类的加在而被加在到内存而且只会加载一次
    private static Singleton02 singleton;

    //静态方法 返回本类创建的类对象
    public synchronized static Singleton02 getInstance(){
         if(singleton==null){
             singleton=new Singleton02();
         }
         return singleton;
    }
}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

说明:

该方式也实现了懒加载效果,同时又解决了线程安全问题。但是在getInstance()方法上添加了synchronized关键字,导致该方法的执行效果特别低。从上面代码我们可以看出,其实就是在初始化instance的时候才会出现线程安全问题,一旦初始化完成就不存在了。

4.懒汉式-方式3(双重检查锁)

再来讨论一下懒汉模式中加锁的问题,对于 getInstance() 方法来说,绝大部分的操作都是读操作,读操作是线程安全的,所以我们没必让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机。由此也产生了一种新的实现模式:双重检查锁模式

/**
 * 双重检查方式
 */
public class Singleton { 

    //私有构造方法
    private Singleton() {}

    private static Singleton instance;

   //对外提供静态方法获取该对象
    public static Singleton getInstance() {
		//第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
        if(instance == null) {
            synchronized (Singleton.class) {
                //抢到锁之后再次判断是否为null
                if(instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

总结: 单例模式: 饿汉式【资源的浪费】 懒汉式【线程安全问题--双重校验锁】

应用: spring中的bean默认就是单例。

双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测锁模式看上去完美无缺,其实是存在问题,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。

要解决双重检查锁模式带来空指针异常的问题,只需要使用 volatile 关键字, volatile 关键字可以保证可见性和有序性。

/**
 * 双重检查方式
 */
public class Singleton {

    //私有构造方法
    private Singleton() {}

    private static volatile Singleton instance;

   //对外提供静态方法获取该对象
    public static Singleton getInstance() {
		//第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实际
        if(instance == null) {
            synchronized (Singleton.class) {
                //抢到锁之后再次判断是否为空
                if(instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==
  1. 小结:

    添加 volatile 关键字之后的双重检查锁模式是一种比较好的单例实现模式,能够保证在多线程的情况下线程安全也不会有性能问题。

还有,这里的private static volatile Singleton singleton = null;中的volatile也必不可少,volatile关键字可以防止jvm指令重排优化,

在java内存模型中,volatile 关键字作用可以是保证可见性或者禁止指令重排。这里是因为 singleton = new Singleton() ,它并非是一个原子操作,事实上,在 JVM 中上述语句至少做了以下这 3 件事:

  • 第一步是给 singleton 分配内存空间;

  • 第二步开始调用 Singleton 的构造函数等,来初始化 singleton;

  • 第三步,将 singleton 对象指向分配的内存空间(执行完这步 singleton 就不是 null 了)。

这里需要留意一下 1-2-3 的顺序,因为存在指令重排序的优化,也就是说第 2 步和第 3 步的顺序是不能保证的,最终的执行顺序,可能是 1-2-3,也有可能是 1-3-2。

如果是 1-3-2,那么在第 3 步执行完以后,singleton 就不是 null 了,可是这时第 2 步并没有执行,singleton 对象未完成初始化,它的属性的值可能不是我们所预期的值。假设此时线程 2 进入 getInstance 方法,由于 singleton 已经不是 null 了,所以会通过第一重检查并直接返回,但其实这时的 singleton 并没有完成初始化,所以使用这个实例的时候会报错,详细流程如下图所示:

这里还说一下volatile关键字的第二个作用,保证变量在多线程运行时的可见性:

package com.lqh;


public class Test01 {
    public static void main(String[] args) throws Exception{
        T t=new T();
        t.start();

        Thread.sleep(2000);
        System.out.println("主线程设置t线程的参数来止损失");
        t.setFlag(false);
    }
}
class T extends Thread{
    private volatile boolean flag=true;

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        System.out.println("进入run方法");
        while(flag){

        }
    }
}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

在 JDK1.2 之前,Java的内存模型实现总是从主存(即共享内存)读取变量,是不需要进行特别的注意的。而在当前 的 Java 内存模型下,线程可以把变量保存本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就 可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数 据的不一致。 要解决这个问题,就需要把变量声明为 volatile,这就指示 JVM,这个变量是不稳定的,每次使用它都到主存中进行 读取。

单例模式优点和缺点

优点:单例类只有一个实例,节省了内存资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能;单例模式可以在系统设置全局的访问点,优化和共享数据,例如前面说的Web应用的页面计数器就可以用单例模式实现计数值的保存。

缺点:单例模式一般没有接口,扩展的话除了修改代码基本上没有其他途径。

工厂模式

背景

在面向对象编程中,创建对象实例最常用的方式就是通过 new 操作符构造一个对象实例,但在某些情况下,new 操作符直接生成对象会存在一些问题。举例来说,对象的创建需要一系列的步骤:可能需要计算或取得对象的初始位置、选择生成哪个子对象实例、或在生成之前必须先生成一些辅助对象。 在这些情况,新对象的建立就是一个 “过程”,而不仅仅是一个操作,就像一部大机器中的一个齿轮传动。

针对上面这种情况,我们如何轻松方便地构造对象实例,而不必关心构造对象示例的细节和复杂过程?解决方案就是使用一个工厂类来创建对象。

什么是工厂模式

工厂模式将目的将创建对象的具体过程屏蔽隔离起来,从而达到更高的灵活性,工厂模式可以分为三类:

  • 简单工厂模式(Simple Factory)

  • 工厂方法模式(Factory Method)

  • 抽象工厂模式(Abstract Factory)

我们在了解工厂模式时,先从现实生活说起:

(1)在没有工厂的时代,如果客户需要一款宝马车,那么就需要客户去创建一款宝马车,然后拿来用。

(2)简单工厂模式:后来出现了工厂,用户不再需要去创建宝马车,由工厂进行创建,想要什么车,直接通过工厂创建就可以了。比如想要320i系列车,工厂就创建这个系列的车。

(3)工厂方法模式:为了满足客户,宝马车系列越来越多,如320i、523i等等系列,一个工厂无法创建所有的宝马系列,于是又单独分出来多个具体的工厂,每个具体工厂创建一种系列,即具体工厂类只能创建一个具体产品。但是宝马工厂还是个抽象,你需要指定某个具体的工厂才能生产车出来。

(4)抽象工厂模式:随着客户要求越来越高,宝马车必须配置空调,于是这个工厂开始生产宝马车和需要的空调。最终是客户只要对宝马的销售员说:我要523i空调车,销售员就直接给他523i空调车了。而不用自己去创建523i空调车宝马车。

下面我们就针对几种不同的工厂模式进行详细的说明:

3.简单工厂模式:

简单工厂模式的核心是定义一个创建对象的接口,将对象的创建和本身的业务逻辑分离,降低系统的耦合度,使得两个修改起来相对容易些,当以后实现改变时,只需要修改工厂类即可。

如果不使用工厂,用户将自己创建宝马车,具体UML图和代码如下:

package com.lqh.factory.demo01;


public class Test01 {
    public static void main(String[] args) {
        BMW instance = SimpleFactory.getInstance("523i");
        instance.move();
    }

}
class SimpleFactory{
    public static BMW getInstance(String type){
         if("320i".equals(type)){
             return new BMW320();
         }else if("523i".equals(type)){
             return new BMW523();
         } else{
             throw new RuntimeException("该型号不存在");
         }
    }
}
// 开闭原则---对扩充开放 对修改关闭。
abstract  class BMW{
     public abstract void move();
}
class BMW320 extends BMW{

    public void move() {
        System.out.println("BMW320正在路上奔跑");
    }
}
class BMW523 extends  BMW{

    public void move() {
        System.out.println("BMW523正在路上奔跑");
    }
}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

简单工厂模式的优缺点:**

简单工厂模式提供专门的工厂类用于创建对象,实现了对象创建和使用的职责分离,客户端不需知道所创建的具体产品类的类名以及创建过程,只需知道具体产品类所对应的参数即可,通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。

但缺点在于不符合“开闭原则”,每次添加新产品就需要修改工厂类。在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展维护,并且工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响。

为了解决简单工厂模式的问题,出现了工厂方法模式。

工厂方法模式:

工厂方法模式将工厂抽象化,并定义一个创建对象的接口。每增加新产品,只需增加该产品以及对应的具体实现工厂类,由具体工厂类决定要实例化的产品是哪个,将对象的创建与实例化延迟到子类,这样工厂的设计就符合“开闭原则”了,扩展时不必去修改原来的代码。在使用时,用于只需知道产品对应的具体工厂,关注具体的创建过程,甚至不需要知道具体产品类的类名,当我们选择哪个具体工厂时,就已经决定了实际创建的产品是哪个了。

但缺点在于,每增加一个产品都需要增加一个具体产品类和实现工厂类,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。

工厂方法的 UML 结构图如下:

  • 抽象工厂 AbstractFactory: 工厂方法模式的核心,是具体工厂角色必须实现的接口或者必须继承的父类,在 Java 中它由抽象类或者接口来实现。

  • 具体工厂 Factory:被应用程序调用以创建具体产品的对象,含有和具体业务逻辑有关的代码 抽象产品 AbstractProduct:是具体产品继承的父类或实现的接口,在 Java 中一般有抽象类或者接口来实现。

  • 具体产品 Product:具体工厂角色所创建的对象就是此角色的实例。  

package com.lqh.factory.demo02;


public class Test02 {
    public static void main(String[] args) {
        BMW bmw = BMW320Factory.createBMW();
        bmw.move();
    }
}
//产品的抽象
abstract class BMW{
     public abstract void move();
}
//具体产品
class BMW320i extends BMW{

    public void move() {
        System.out.println("320i移动");
    }
}
class BMW523i extends BMW{

    public void move() {
        System.out.println("523i移动");
    }
}
class BMW330i extends BMW{

    public void move() {
        System.out.println("330i移动");
    }
}
//抽象工厂类
abstract  class Factory{

    public  static  BMW createBMW(){
        return null;
    }
}
//具体工厂类
class  BMW320Factory extends Factory{

    public static BMW createBMW() {
        return new BMW320i();
    }
}
//具体工厂类
class  BMW523Factory extends Factory{

    public static BMW createBMW() {
        return new BMW523i();
    }
}

class  BMW330Factory extends Factory{

    public static BMW createBMW() {
        return new BMW330i();
    }
}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

但缺点在于,每增加一个产品都需要增加一个具体产品类和实现工厂类,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。

作用:

  1. 把创建对象和使用对象进行分离。降低的耦合性。

  2. 工厂模式的实现方式: [1]简单工厂模式 [2]工厂方法模式。

抽象工厂模式:了解

在工厂方法模式中,我们使用一个工厂创建一个产品,一个具体工厂对应一个具体产品,但有时候我们需要一个工厂能够提供多个产品对象,而不是单一的对象,这个时候我们就需要使用抽象工厂模式。

在介绍抽象工厂模式前,我们先理清两个概念:

  • 产品等级结构:产品等级结构指的是产品的继承结构,例如一个空调抽象类,它有海尔空调、格力空调、美的空调等一系列的子类,那么这个空调抽象类和他的子类就构成了一个产品等级结构。

  • 产品族:产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品。比如,海尔工厂生产海尔空调、海尔冰箱,那么海尔空调则位于海尔族中。

产品等级结构和产品族结构示意图如下:

1、什么是抽象工厂模式

 抽象工厂模式主要用于创建相关对象的家族。当一个产品族中需要被设计在一起工作时,通过抽象工厂模式,能够保证客户端始终只使用同一个产品族中的对象;并且通过隔离具体类的生成,使得客户端不需要明确指定具体生成类;所有的具体工厂都实现了抽象工厂中定义的公共接口,因此只需要改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。

    但该模式的缺点在于添加新的行为时比较麻烦,如果需要添加一个新产品族对象时,需要更改接口及其下所有子类,这必然会带来很大的麻烦。

  2、UML结构图:

  • 抽象工厂 AbstractFactory:定义了一个接口,这个接口包含了一组方法用来生产产品,所有的具体工厂都必须实现此接口。

  • 具体工厂 ConcreteFactory:用于生产不同产品族,要创建一个产品,用户只需使用其中一个工厂进行获取,完全不需要实例化任何产品对象。

  • 抽象产品 AbstractProduct:这是一个产品家族,每一个具体工厂都能够生产一整组产品。

  • 具体产品 Product

3、代码实现:

通过抽象工厂模式,我们可以实现以下的效果:比如宝马320系列使用空调型号A和发动机型号A,而宝马230系列使用空调型号B和发动机型号B,在为320系列生产相关配件时,就无需制定配件的型号,它会自动根据车型生产对应的配件型号A。

也就是说,当每个抽象产品都有多于一个的具体子类的时候(空调有型号A和B两种,发动机也有型号A和B两种),工厂角色怎么知道实例化哪一个子类呢?抽象工厂模式提供两个具体工厂角色(宝马320系列工厂和宝马230系列工厂),分别对应于这两个具体产品角色,每一个具体工厂角色只负责某一个产品角色的实例化,每一个具体工厂类只负责创建抽象产品的某一个具体子类的实例。

产品类:

//发动机以及型号  
public interface Engine {}  
 
public class EngineA implements Engine{  
    public EngineA(){  
        System.out.println("制造-->EngineA");  
    }  
}  
public class EngineB implements Engine{  
    public EngineB(){  
        System.out.println("制造-->EngineB");  
    }  
}  
 
 
//空调以及型号  
public interface Aircondition {} 
 
public class AirconditionA implements Aircondition{  
    public AirconditionA(){  
        System.out.println("制造-->AirconditionA");  
    }  
}  
public class AirconditionB implements Aircondition{  
    public AirconditionB(){  
        System.out.println("制造-->AirconditionB");  
    }  
} 
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

创建工厂类:

//创建工厂的接口  
public interface AbstractFactory {  
    //制造发动机
    public Engine createEngine();
    //制造空调 
    public Aircondition createAircondition(); 
}  
 
//为宝马320系列生产配件  
public class FactoryBMW320 implements AbstractFactory{     
    @Override  
    public Engine createEngine() {    
        return new EngineA();  
    }  
    @Override  
    public Aircondition createAircondition() {  
        return new AirconditionA();  
    }  
}  
//宝马523系列
public class FactoryBMW523 implements AbstractFactory {  
     @Override  
    public Engine createEngine() {    
        return new EngineB();  
    }  
    @Override  
    public Aircondition createAircondition() {  
        return new AirconditionB();  
    }  
} 
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

客户:

public class Customer {  
    public static void main(String[] args){  
        //生产宝马320系列配件
        FactoryBMW320 factoryBMW320 = new FactoryBMW320();  
        factoryBMW320.createEngine();
        factoryBMW320.createAircondition();
          
        //生产宝马523系列配件  
        FactoryBMW523 factoryBMW523 = new FactoryBMW523();  
        factoryBMW523.createEngine();
        factoryBMW523.createAircondition();
    }  
}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

工厂模式小结:

1、工厂方法模式与抽象工厂模式的区别在于:

(1)工厂方法只有一个抽象产品类和一个抽象工厂类,但可以派生出多个具体产品类和具体工厂类,每个具体工厂类只能创建一个具体产品类的实例。

(2)抽象工厂模式拥有多个抽象产品类(产品族)和一个抽象工厂类,每个抽象产品类可以派生出多个具体产品类;抽象工厂类也可以派生出多个具体工厂类,同时每个具体工厂类可以创建多个具体产品类的实例

适配器模式

  • 定义:适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。

三种适配器:类的适配器模式、对象的适配器模式

适配器模式之类适配器模式

实现方式:让Adapter继承Adaptee类,然后再实现Target接口,来实现适配器功能。

- Adaptee:适配者类,它是需要被访问的、需要被适配的组件. AC220V
- Target:目标接口,当前系统业务所使用的接口,可以是抽象类或接口. DC5V
- Adapter:适配器类,通过继承和实现目标接口,让客户端按照目标接口的方法访问适配者
- Client:客户端,适配器的使用者 

实例:手机充电需要将220V的交流电转化为手机锂电池需要的5V直流电。使用电源适配器,将 AC220v ——> DC5V。

类图

代码:

/**
 * 源角色(Adaptee):现在需要适配的接口。
 */
public class AC220 {
    /**
     * 输出220V交流电
     *
     * @return
     */
    public int output220V() {
        int output = 220;
        return output;
    }
}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

/**
 * 目标角色(Target):这就是所期待得到的接口。
 * 注意:由于这里讨论的是类适配器模式,因此目标不可以是类。
 */
public interface DC5 {
    /**
     * 输出5V直流电(期待得到的接口)
     *
     * @return
     */
    int output5V();
}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==
/**
 * 类适配器
 * 适配器类是本模式的核心。适配器把源接口转换成目标接口。显然,这一角色不可以是接口,而必须是具体类。
 */
public class PowerAdapter extends AC220 implements DC5 {
    /**
     * 输出5V直流电
     *
     * @return
     */
    @Override
    public int output5V() {
        int output = output220V();
        return (output / 44);
    }
}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==
/**
 * 测试类适配器
 */
public class TestClassAdapter {
    public static void main(String[] args) {
        DC5 dc5 = new PowerAdapter();
        System.out.println("输出电流:" + dc5.output5V() + "V");
    }
}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

优点:由于Adapter继承了Adaptee类,所以它可以根据需求重写Adaptee类的方法,使得Adapter的灵活性增强了。

缺点:因为java单继承的缘故,Target类必须是接口,以便于Adapter去继承Adaptee并实现Target,完成适配的功能,但这样就导致了Adapter里暴露了Adaptee类的方法,使用起来的成本就增加了。

 优点:根据合成复用原则,使用组合替代继承, 所以它解决了类适配器必须继承Adaptee的局限性问题,也不再要求Target必须是接口。使用成本更低,更灵活。

代理模式

代理模式是什么

概述 为其它对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

代理模式是一种设计模式,简单说即是在不改变源码的情况下,实现对目标对象功能扩展

抽象角色(Subject):通过接口或抽象类声明真实角色实现的业务方法。

角色具有的行为放入该接口或抽象类总

代理角色(Proxy):实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。

真实角色(RealSubject):实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。

例子:

比如有个歌手对象叫Singer,这个对象有一个唱歌方法叫sing()。

public class Singer{
    public void sing(){
        System.out.println("唱一首歌");
    }  
}

假如你希望,通过你的某种方式生产出来的歌手对象,在唱歌前后还要想观众问好和答谢,也即对目标对象Singer的sing方法进行功能扩展

public void sing(){
     System.out.println("向观众问好");
     System.out.println("唱一首歌");
     System.out.println("谢谢大家");
 }  

但是往往你又不能直接对源代码进行修改,可能是你希望原来的对象还保持原来的样子,又或许你提供的只是一个可插拔的插件,甚至你有可能都不知道你要对哪个目标对象进行扩展。这时就需要用到java的代理模式了。网上好多用生活中的经理人的例子来解释“代理”,看似通俗易懂,但我觉得不适合程序员去理解。程序员应该从代码的本质入手。

优点:代理模式可以屏蔽用户真正请求的对象,是用户程序和正在的对象解耦。使用代理来担当那些创建耗时的对象的替身。 一个用户不想或者不能够直接引用一个对象(或者设计者不希望用户直接访问该画图对象),而代理对象可以在客户端和目标对象之间起到中介的作用。而且这个代理对象中,我们可以做更多的操作。

代理分类:我们有多种不同的方式来实现代理。如果按照代理创建的时期来进行分类的话, 可以分为两种:静态代理、动态代理。静态代理是由程序员创建或特定工具自动生成源代码,在对其编译。动态代理是在程序运行时通过反射机制动态创建的。

静态代理

概述:静态代理就是写死了在代理对象中执行这个方法前后执行添加功能的形式,每次要在接口中添加一个新方法,则需要在目标对象中实现这个方法,并且在代理对象中实现相应的代理方法。

public interface ISinger {
      void sing();
  } 
  /**
   *  目标对象实现了某一接口
   */
  public class Singer implements ISinger{
      public void sing(){
         System.out.println("唱一首歌");
     }  
 }
 
 /**
  *  代理对象和目标对象实现相同的接口
  */
 public class SingerProxy implements ISinger{
     // 接收目标对象,以便调用sing方法
     private ISinger target;
     public UserDaoProxy(ISinger target){
         this.target=target;
     }
     // 对目标对象的sing方法进行功能扩展
     public void sing() {
         System.out.println("向观众问好");
         target.sing();
         System.out.println("谢谢大家");
     }
 }

 测试

  /**
   * 测试类
   */
  public class Test {
      public static void main(String[] args) {
          //目标对象
          ISinger target = new Singer();
          //代理对象
          ISinger proxy = new SingerProxy(target);
         //执行的是代理的方法
         proxy.sing();
     }
 }

总结:其实这里做的事情无非就是,创建一个代理类SingerProxy,继承了ISinger接口并实现了其中的方法。只不过这种实现特意包含了目标对象的方法,正是这种特征使得看起来像是“扩展”了目标对象的方法。假使代理对象中只是简单地对sing方法做了另一种实现而没有包含目标对象的方法,也就不能算作代理模式了。所以这里的包含是关键。

  缺点:这种实现方式很直观也很简单,但其缺点是代理对象必须提前写出,如果==接口层发生了变化==,代理对象的代码也要进行维护。如果能在运行时动态地写出代理对象,不但减少了一大批代理类的代码,也少了不断维护的烦恼,不过运行时的效率必定受到影响。这种方式就是接下来的动态代理。

动态代理(也叫JDK代理)

概述:动态代理是在程序运行时通过==反射机制动态创建==的

Proxy类:创建代理角色的方法。

InvocationHandler接口:提供的执行被代理角色方法的方法。

 public interface ISinger {
      void sing();
  }
  
  /**
   *  目标对象实现了某一接口
   */
  public class Singer implements ISinger{
      public void sing(){
         System.out.println("唱一首歌");
     }  
 }

这回直接上测试,由于java底层封装了实现细节,所以代码非常简单,格式也基本上固定。

调用Proxy类的静态方法newProxyInstance即可,该方法会返回代理类对象

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h ) 接收的三个参数依次为:

  • ClassLoader loader:指定当前目标对象使用类加载器,写法固定

  • Class<?>[] interfaces:目标对象实现的接口的类型,写法固定

  • InvocationHandler h:提供的执行被代理角色方法的方法。

 public class Test{
     public static void main(String[] args) {
   Singer target = new Singer();
         ISinger proxy  = (ISinger) Proxy.newProxyInstance(
                 target.getClass().getClassLoader(),
                 target.getClass().getInterfaces(),
                 new InvocationHandler() {
                     @Override
                     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                         System.out.println("向观众问好");
                         //执行目标对象方法
                         Object returnValue = method.invoke(target, args);
                         System.out.println("谢谢大家");
                         return returnValue;
                     }
                 });
         proxy.sing();
     }
 }

缺点:可以看出静态代理和JDK代理有一个共同的缺点,就是目标对象必须实现一个或多个接口,加入没有,则可以使用Cglib代理。

Cglib代理

前提条件:

  • 需要引入cglib的jar文件,由于Spring的核心包中已经包括了Cglib功能,所以也可以直接引入spring-core-3.2.5.jar

  • 目标类不能为==final==

  • 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法

/**
 * 目标对象,没有实现任何接口
 */
public class Singer{

    public void sing() {
        System.out.println("唱一首歌");
    }
}
/**
 * Cglib子类代理工厂
 */
public class ProxyFactory implements MethodInterceptor{
    // 维护目标对象
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    // 给目标对象创建一个代理对象
    public Object getProxyInstance(){,
        //1.工具类
        Enhancer en = new Enhancer();
        //2.设置父类
        en.setSuperclass(target.getClass());
        //3.设置回调函数
        en.setCallback(this);
        //4.创建子类(代理对象)
        return en.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("向观众问好");
        //执行目标对象的方法
        Object returnValue = method.invoke(target, args);
        System.out.println("谢谢大家");
        return returnValue;
    }
}

这里的代码也非常固定,只有部分是需要自己写出

测试

/**
 * 测试类
 */
public class Test{
    public static void main(String[] args){
        //目标对象
        Singer target = new Singer();
        //代理对象
        Singer proxy = (Singer)new ProxyFactory(target).getProxyInstance();
        //执行代理对象的方法
        proxy.sing();
    }
}

总结:三种代理模式各有优缺点和相应的适用范围,主要看目标对象是否实现了接口。以Spring框架所选择的代理模式举例

在Spring的AOP编程中: 如果加入容器的目标对象有实现接口,用JDK代理 如果目标对象没有实现接口,用Cglib代理

观察者模式

概念

观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。

结构

观察者模式的主要角色如下。

  • 抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。

  • 具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。

  • 抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。

  • 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。

实现

观察者模式首先有一个被观察类,这个类中有一个 ArrayList 存放观察者们。除此以外还应有类状态和设置和获取状态的方法,状态改变时通知所有观察者,观察者类可以有个抽象类,所有的观察者类继承这个抽象类,观察者类有它要观察的对象。

例子

需求:做一个案例,公众号推送消息,关注这个公众号的客户可以接收到消息。

分析:

观察者模式分为四个角色:
    1.抽象被观察者角色
        抽象出一个公众号接口/抽象类
        公共的方法:
            关注方法
            取关方法
            设置推送消息方法
            群发方法

    2.被观察者角色
        有一个具体的公众号,实现/继承 抽象被观察者

    3.抽象观察者角色
        抽象出一个客户 接口/抽象类
        公共方法:
            接收公众号推送的消息
    4.观察者角色
        具体用户  实现/继承 抽象观察者

抽象观察者角色:

package demo02;


public interface UserInterface {
    /**
     * 接受公众号发生的消息
     * @param msg
     */
    public void getMsg(String msg);
}

观察者角色:

package demo02;


public class User implements UserInterface {
    private String name;

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

    public void getMsg(String msg) {
        System.out.println(name+"接受到公众号发送的消息:"+msg);
    }
}

抽象公众号:

package demo02;

public interface PublicHaoInterface {

    public void add(UserInterface userInterface);

    public void remove(UserInterface userInterface);

    public void setMsg(String msg);

    public void qunfa();

}

具体公共号:

package demo02;

import java.util.ArrayList;
import java.util.List;


public class AAAPublicHao implements PublicHaoInterce {
    List<UserInterface> list=new ArrayList<UserInterface>();
    //设置消息
    private String msg;
    public void add(UserInterface userInterface) {
        list.add(userInterface);
    }

    public void remove(UserInterface userInterface) {
        list.remove(userInterface);
    }

    public void setMsg(String msg) {
        this.msg=msg;
    }

    public void qunfa() {
            for(UserInterface userInterface:list){
                userInterface.getMsg(msg);
            }
    }
}

优缺点

观察者模式是一种对象行为型模式,其主要优点如下。

降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。 目标与观察者之间建立了一套触发机制。

它的主要缺点如下。

目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值