Java中常见的设计模式

本文详细介绍了Java中的设计模式,包括设计模式的六大原则:开闭原则、里氏代换原则、依赖倒转原则、接口隔离原则、迪米特法则和合成复用原则。接着,文章探讨了设计模式的三大类型:创建型、结构型和行为型,并逐一讲解了Java的二十三种设计模式,如单例模式、工厂模式、模板设计模式和代理模式等,每种模式都配有实例和优缺点分析,帮助开发者更好地理解和运用设计模式。
摘要由CSDN通过智能技术生成

JAVA中常见的设计模式



前言

在互联网不断内卷的今天,作为年轻的开发工程师想要去追逐高薪,那么设计模式这一块的东西是一定需要去掌握的,人家官方都使用这些设计模式,为什么你的项目中不用?不好用的话,官方会花大量时间去搞这些设计模式?所以其存在必有其道理,需要我们展开横向和纵向的思考。


一、设计模式是什么?

设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性

二、设计模式6大原则

1、开闭原则(Open Close Principle)

通俗的说:扩展开放,对修改关闭,当你的程序需要改变的时候,不用去修改原来的主要代码,只用去修改你的设计模块的内容,最终实现功能的扩展需求,比如:键盘更换轴体,只用去换轴体,而键盘本身外观,键帽是不需要去改变的。
官方说法当应用的需求改变时,在不修改软件实体的源代码或者二进制代码的前提下,可以扩展模块的功能,使其满足新的需求。
主要作用有以下几点

1. 对软件测试的影响
软件遵守开闭原则的话,软件测试时只需要对扩展的代码进行测试就可以了,因为原有的测试代码仍然能够正常运行。
2. 可以提高代码的可复用性
粒度越小,被复用的可能性就越大;在面向对象的程序设计中,根据原子和抽象编程可以提高代码的可复用性。
3. 可以提高软件的可维护性
遵守开闭原则的软件,其稳定性高和延续性强,从而易于扩展和维护。

2、里氏代换原则(Liskov Substitution Principle)

里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。

里氏替换原则的定义可以总结如下:
子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
子类中可以增加自己特有的方法
当子类的方法重载父类的方法时,方法的前置条件(即方法的输入参数)要比父类的方法更宽松
当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的的输出/返回值)要比父类的方法更严格或相等

举一个栗子
证明:几维鸟不是鸟
父类(鸟类)

//父类---鸟类
class Bird{
        double flySpeed;

        public void setSpeed(double speed){
            flySpeed=speed;
        }

        public double getFlyTime(double distance){
            return (distance/flySpeed);
        }

子类(燕子)

//燕子类
class Swallow extends Bird{

}
``
<font face="微软雅黑" size=3>子类(几维鸟)</font>
```java
//几维鸟
class  BrownKiwi extends Bird{
    public void setSpeed(double speed) {
        flySpeed=0;
    }
}

测试

    @Test
    public void doTest() {
        Bird bird1 = new Swallow();
        Bird bird2 = new BrownKiwi();
        bird1.setSpeed(120);
        bird2.setSpeed(120);
        System.out.println("如果飞行300公里:");
        try {
            System.out.println("燕子将飞行" + bird1.getFlyTime(300) + "小时.");
            System.out.println("几维鸟将飞行" + bird2.getFlyTime(300) + "小时。");
        } catch (Exception err) {
            System.out.println("发生错误了!");
        }
    }

控制台输出结果

如果飞行300公里:
燕子将飞行2.5小时.
几维鸟将飞行Infinity小时。

程序运行错误的原因是:几维鸟类重写了鸟类的 setSpeed(double speed) 方法,这违背了里氏替换原则。正确的做法是:取消几维鸟原来的继承关系,定义鸟和几维鸟的更一般的父类,如动物类,它们都有奔跑的能力。几维鸟的飞行速度虽然为 0,但奔跑速度不为 0,可以计算出其奔跑 300 千米所要花费的时间。
实际修改之后的关系应该如下图
来自http://c.biancheng.net/

3、依赖倒转原则(Dependence Inversion Principle)

依赖倒置原则是实现开闭原则的重要途径之一,它降低了客户与实现模块之间的耦合。
通俗的理解:要面向接口编程,不要面向实现编程 由于在软件设计中,细节具有多变性,而抽象层则相对稳定,因此以抽象为基础搭建起来的架构要比以细节为基础搭建起来的架构要稳定得多。这里的抽象指的是接口或者抽象类,而细节是指具体的实现类。

使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给它们的实现类去完成。
举一个栗子” 场景:顾客购物程序
本程序反映了 “顾客类”与“商店类”的关系。商店类中有 sell() 方法,顾客类通过该方法购物以下代码定义了顾客类通过韶关网店 ShaoguanShop 购物:

class Customer {
    public void shopping(ShaoguanShop shop) {
        //购物
        System.out.println(shop.sell());
    }
}

当顾客换一家商店的时候,就需要修改代码,显然这样的写法是不可靠的,同时也违背了开闭原则

class Customer {
    public void shopping(WuyuanShop shop) {
        //购物
        System.out.println(shop.sell());
    }
}

现在对其进行修改

public class DIPtest {
    public static void main(String[] args) {
        Customer wang = new Customer();
        System.out.println("顾客购买以下商品:");
        wang.shopping(new ShaoguanShop());
        wang.shopping(new WuyuanShop());
    }
}

//商店
interface Shop {
    public String sell(); //卖
}

//韶关网店
class ShaoguanShop implements Shop {
    public String sell() {
        return "韶关土特产:香菇、木耳……";
    }
}

//婺源网店
class WuyuanShop implements Shop {
    public String sell() {
        return "婺源土特产:绿茶、酒糟鱼……";
    }
}

//顾客
class Customer {
    public void shopping(Shop shop) {
        //购物
        System.out.println(shop.sell());
    }
}

其关系图如下:
在这里插入图片描述

4、接口隔离原则(Interface Segregation Principle)

这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。

5、迪米特法则(最少知道原则)(Demeter Principle)

为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。

6、合成复用原则(Composite Reuse Principle)

原则是尽量使用合成/聚合的方式,而不是使用继承。

三、设计模式的三大类型

总体来说设计模式分为三大类:
创建型模式(5种):工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

结构型模式(7种):适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

行为型模式(11种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
在这里插入图片描述

四、Java的二十三种设计模式

从这一块开始,我详细介绍Java中23种设计模式在开发中常用的几个模式进行案例分析。
创建型模式(5种):用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”。

1、单例模式(Singleton)

什么是单例?一个类只有一个实例,比如:工具类,Spring中对象默认都是单例。如何让类的实例只有一个?关键就在于不能让外界随意new 对象 ,这一点可以通过私有化构造器来实现。私有了构造器那该如何创建对象?可以在类中提供一个静态方法来创建该类的实例,要注意的是这个方法多次被调用也只会返回同一个实例。 而单利模式又分为饿汉式懒汉式

A.饿汉模式

       见名知意,饿汉是很饥饿的,意味着迫切,即一开始就需要创建好类的实例(类加载时就创建一个实例),那么这种模式我们可以在类中定义一个私有的成员变量类型就是该类本身,然后直接new一个实例作为该变量的初始值,具体步骤如下:
        ①构造方法私有化
        ②定义成员变量new一个实例作为初始值(饿)
        ③提供获取实例的静态方法

案例

//单利模式 饿汉 :工具类
class JDBCUtil{

    //2.定义成员变量,new实例作为初始值 ,饿汉:一来就创建实例,赋初始值
    private static JDBCUtil instance = new JDBCUtil();

    //1.私有构造器
    private JDBCUtil(){}

    //3.提供获取实例方法
    public static JDBCUtil getInstance(){
        return instance;
    }
}

B.懒汉模式

          它和饿汉式相反,懒汉是很懒惰的,一开始不会创建类的实例,而是在使用到类的实例的时候才会去创建,我们也可以为该类定义一个私有的成员变量,只不过不需要为它赋初始值,而是在需要用到该类的实例的时候再创建实例赋值,它的具体步骤如下:
        ①构造方法私有化
        ②定义成员变量不赋初始值(懒)
        ③提供获取实例的静态方法(保证单实例)

案例:

//单利模式 懒汉 :工具类
class JDBCUtil{

    //1.定义成员变量,不赋初始值
    private static JDBCUtil instance  = null;

    //1.私有构造器
    private JDBCUtil(){}

    //2.提供获取实例方法
    public static  JDBCUtil getInstance(){

            //第一次调用时创建对象
            if(instance == null){
                //同步代码块,保证创建实例的代码的原子性,只会创建一个实例
                synchronized (JDBCUtil.class){
                    instance = new JDBCUtil();
                }
            }
        return instance;
    }

上面的getInstance有一个小问题就是高并发时多个线程同时调用该方法可能会出现安全问题创建多个实例,可以通过同步锁来解决

//单利模式 懒汉 :工具类
class JDBCUtil{
    //1.定义成员变量,不赋初始值
    private static JDBCUtil instance  = null;
    //1.私有构造器
    private JDBCUtil(){}
    //2.提供获取实例方法
    public static  JDBCUtil getInstance(){
            //第一次调用时创建对象
            if(instance == null){
                //同步代码块,保证创建实例的代码的原子性,只会创建一个实例
                synchronized (JDBCUtil.class){
                    if(instance == null){
                        instance = new JDBCUtil();
                    }
                }
            }
        return instance;
    }
}

注意:为了效率,同步锁不应该加载方法上,且是insance == null 的情况下才会尝试获取锁,然后创建对象的实例,但是上面代码还是有一个小问题,就是如果多个线程都执行到了synchronized 代码块,但是只有一个线程能获取锁,并创建实例然后释放锁,这个时候第二个线程获取到锁依然会创建一次实例,就导致创建了多次实例。所以需要在同步代码块中再加一个判断,即:双重判断加同步代码块
 

C.枚举

还有一种常用的单利实现方式 - 枚举 ,枚举的构造器默认私有化的,为枚举类定义一个实例就是单利,代码如下:

//单利:枚举
enum JDBCUtil{
    INSTANCE;
}

 

2、工厂模式(Factory Method)

        厂模式抽象于生活中的工厂,工厂的作用就是生产某种多种产品,工厂隐藏了(封装了)产品的复杂创建过程,以及可以实现生产功能复用的效果,让产品的生产更加方便和高效,而在Java语言中的工厂模式就是用来生成对象的。工厂分为三种:简单工厂,工厂方法,抽象工厂。该模式用于封装和管理对象的创建,是一种创建型模式。本文从一个具体的例子逐步深入分析,来体会三种工厂模式的应用场景和利弊。
 

A.简单工厂

 
该模式对对象创建管理方式最为简单,因为其仅仅简单的对不同类对象的创建进行了一层薄薄的封装。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。其UML类图如下: 在这里插入图片描述
代码举例:有2款手机,小米手机(“mi”) , 华为手机(“huawei”) ,两款手机都有打电话功能,所以定义如下代码

//小米手机
class MiPhone{
    public void call(){
        System.out.println("MiPhone 正在通话中...");
    }
}

//华为手机
class HuaweiPhone{
    public void call(){
        System.out.println(" HuaweiPhone 正在通话中...");
    }
}

两款手机都有打电话 call 的功能,所以我们可以自定一个标准,定义一个接口

  • 制定规范
//小米手机
class MiPhone extends AbstractPhone{
    public void call(){
       System.out.println("MiPhone 正在通话中...");
    }
}

//华为手机
class HuaweiPhone extends AbstractPhone{
    public void call(){
        System.out.println(" HuaweiPhone 正在通话中...");
    }
}

public abstract class AbstractPhone {
    //定义标准,所有的手机都要有call功能
    abstract void call();
}
  • 创建对象实例 , 调用call功能
public static void main(String[] args) {
    AbstractPhone miPhone = new MiPhone();
    miPhone.call();
}

上面的代码的缺点就是需要手动创建具体的对象实例,如果创建过程比较复杂,那么创建对象的工作就是一个麻烦的事情,如果有一天我想要把MiPhone换成HuaweiPhone,我又需要重新改代码。

所以我们对于上面这种场景我们可以使用工厂模式,我们来定义一个工厂如下:

public class PhoneSimpleFactory {
    public static void main(String[] args) {
        //Phone miPhone = new MiPhone();
        AbstractPhone phone = new PhoneSimpleFactory().makePhone("mi");
        phone.call();
    }
    //根据类型创建不同的手机
    public AbstractPhone makePhone(String type){
        if("mi".equalsIgnoreCase(type)){
            return new MiPhone();
        }else if ("huawei".equalsIgnoreCase(type)){
            return new HuaweiPhone();
        }
        return null;
    }
}

代码结构:
在这里插入图片描述
上面的代码就是一个简单工厂,其实就是把对象的创建过程交给工厂进行封装,在Spring中ApplicationContext就是一个简单工厂。

简单工厂的优缺点
  • 优点:简单工厂模式能够根据外界给定的信息,决定究竟应该创建哪个具体类的对象。明确区分了各自的职责和权力,有利于整个软件体系结构的优化。
  • 缺点:很明显工厂类集中了所有实例的创建逻辑,容易违反GRASPR的高内聚的责任分配原则,所有的类型都用了同一个工厂去创建,试想一下如果我要增加一个手机类型,那么我的创建实例的工厂需要重新修改。

B.工厂方法

     和简单工厂模式中工厂负责生产所有产品相比,工厂方法模式将生成具体产品的任务分发给具体的产品工厂,即:每个类型都搞一个工厂,然后为每个工厂在抽象一个工厂。 举例:一个工厂又要创建小米,又要创建华为,本身比较混乱了,如果又新加了一个产品苹果,如果我在已有的工厂继续增加生成苹果的的业务,那么会把我的生成线搞得更乱,那我打算把产品分类,为每一种产品都建一个工厂,每个工厂只需要创建特定的某个产品即可,生产线变得有条理性。其UML类图如下:

类图

  • 抽象一个工厂
//创建工厂接口
public interface FactoryInterface {
    //创建对象方法
    ProductInterface makePhone();
}
  • 创建一个所有手机的公共模块
//被创建者接口
public interface ProductInterface {
    //公共的方法--通话功能
     void call();
}
  • 华为手机
//华为手机
public class HuaweiPhone implements ProductInterface {

    @Override
    public void call() {
        System.out.println("华为手机通话。。。。。");
    }
}
  • OPPO手机
//OPPO手机
public class OPPOPhone implements ProductInterface {
    @Override
    public void call() {
        System.out.println("OPPO手机通话。。。。。");
    }
}
  • 两个手机的工厂
//华为手机工厂
class HuaweiPhoneFactory implements FactoryInterface {

    @Override
    public ProductInterface makePhone() {
        HuaweiPhone huaweiPhone = new HuaweiPhone();
        huaweiPhone.call();
        return huaweiPhone;
    }
}

//OPPO手机工厂
class OPPOPhoneFactory implements FactoryInterface {

    @Override
    public ProductInterface makePhone() {
        OPPOPhone oppoPhone = new OPPOPhone();
        oppoPhone.call();
        return oppoPhone;
    }
}
  • 测试代码
 @Test
    public void doTest() {
        HuaweiPhoneFactory huaweiPhoneFactory = new HuaweiPhoneFactory();
        OPPOPhoneFactory oppoPhoneFactory = new OPPOPhoneFactory();
        System.out.println(huaweiPhoneFactory.makePhone());
        System.out.println(oppoPhoneFactory.makePhone());
    }
工厂方法的优缺点
  • 优点:通过对应的工厂类来生成对应的产品类,在这里我们就可以实现“开发-封闭”原则,无论加多少产品类,我们都不用修改原来类中的代码,而是通过增加工厂类来实现
  • 缺点:如果产品类过多,我们就要生成很多的工厂类

 

C.抽象工厂

上面两种模式不管工厂怎么拆分抽象,都只是针对一类产品Phone(AbstractProduct),如果要生成另一种产品PC,应该怎么表示呢?
最简单的方式是把2中介绍的工厂方法模式完全复制一份,不过这次生产的是PC。但同时也就意味着我们要完全复制和修改Phone生产管理的所有代码,显然这是一个笨办法,并不利于扩展和维护。
抽象工厂模式通过在AbstarctFactory中增加创建产品的接口,并在具体子工厂中实现新加产品的创建,当然前提是子工厂支持生产该产品。否则继承的这个接口可以什么也不干。

适用场景

抽象工厂适用于多个品牌,多种产品类型的情况
在这里插入图片描述

优缺点

优点:管理多个品牌、多个类型时,很方便。
缺点: 增加新的产品类型时比较麻烦。

举个栗子

需求:如上边“适用场景”所示,提供小米和华为两个品牌,手机和路由器两种类型。
文件架构图:
在这里插入图片描述

产品
手机

手机产品(接口)

//手机产品接口
public interface IPhoneProduct {
    //抽取公共的方法

     //开机
     void start();
     //关机
     void shutdown();
     //打电话
     void call();
     //发短信
     void sendSMS();
}

小米手机工厂(实现类)

public class XiaomiPhone implements IPhoneProduct {
    @Override
    public void start() {
        System.out.println("小米手机:开机");
    }

    @Override
    public void shutdown() {
        System.out.println("小米手机:关机");
    }

    @Override
    public void call() {
        System.out.println("小米手机:打电话");
    }

    @Override
    public void sendSMS() {
        System.out.println("小米手机:发短信");
    }
}

华为手机工厂(实现类)

//华为手机
public class HuaweiPhone implements IPhoneProduct {
    @Override
    public void start() {
        System.out.println("华为手机:开机");
    }

    @Override
    public void shutdown() {
        System.out.println("华为手机:关机");
    }

    @Override
    public void call() {
        System.out.println("华为手机:打电话");
    }

    @Override
    public void sendSMS() {
        System.out.println("华为手机:发短信");
    }


}
路由器

路由器产品(接口)

有这些方法:开机、关机、打开wifi、设置

//路由产品接口
public interface IRouterProduct {
    //开机
    void start();
    //关机
    void shutdown();
    //打开wifi
    void openWifi();
    //打开设置
    void setting();
}

小米路由器产品(实现类)

public class XiaomiRouter implements IRouterProduct {
    @Override
    public void start() {
        System.out.println("小米路由器:开机");
    }

    @Override
    public void shutdown() {
        System.out.println("小米路由器:关机");
    }

    @Override
    public void openWifi() {
        System.out.println("小米路由器:打开wifi");
    }

    @Override
    public void setting() {
        System.out.println("小米路由器:设置");
    }
}

华为路由器产品(实现类)

public class HuaweiRouter implements IRouterProduct {
    @Override
    public void start() {
        System.out.println("华为路由器:开机");
    }

    @Override
    public void shutdown() {
        System.out.println("华为路由器:关机");
    }

    @Override
    public void openWifi() {
        System.out.println("华为路由器:打开wifi");
    }

    @Override
    public void setting() {
        System.out.println("华为路由器:设置");
    }
}
工厂

工厂(接口)

//工厂生产接口
public interface IProductFactory {
    //生产手机
    IPhoneProduct phoneProduct();
    //生产路由
    IRouterProduct routerProduct();
}

小米工厂(实现类)

public class XiaomiFactory implements IProductFactory {
    @Override
    public IPhoneProduct phoneProduct() {
        return new XiaomiPhone();
    }

    @Override
    public IRouterProduct routerProduct() {
        return new XiaomiRouter();
    }
}

华为工厂(实现类)

public class HuaweiFactory implements IProductFactory {
    @Override
    public IPhoneProduct phoneProduct() {
        return new HuaweiPhone();
    }

    @Override
    public IRouterProduct routerProduct() {
        return new HuaweiRouter();
    }
}
测试
@SpringBootTest
class MyspringbootApplicationTests {

    @Test
    public void doTest() {
        IProductFactory xiaomiFactory = new XiaomiFactory();
        IPhoneProduct xiaomiPhoneProduct = xiaomiFactory.phoneProduct();
        xiaomiPhoneProduct.start();

        IProductFactory huaweiFactory = new HuaweiFactory();
        IRouterProduct iRouterProduct = huaweiFactory.routerProduct();
        iRouterProduct.openWifi();
    }

}
执行结果
小米手机:开机
华为路由器:打开wifi

3.模板设计模式(Template Method Pattern)

个人理解:先定义一个操作中的算法骨架,而将算法的某一个或者某些步骤的具体实现延迟到了子类中来实现,使得子类可以在不修改当前算法的结构情况下,重新定义当前算法的某些特定步骤。

模板案例

//抽象基类,定义流程 - 喝水的流程
public abstract class AbstractDrinkWaterTemplate {

    //喝水的流程
    void drinkWaterProcess(){
        //烧
        System.out.println("烧水...");
        //倒
        System.out.println("倒水...");
        //泡
        pourWater();
        //喝
        drinkWater();
    }
    abstract void pourWater();
    abstract void drinkWater();
}

子类实现Coffee

public class Coffee extends AbstractDrinkWaterTemplate{
    @Override
    void pourWater() {
        System.out.println("泡咖啡...");
    }

    @Override
    void drinkWater() {
        System.out.println("喝咖啡...");
    }
}

子类实现Team

public class Team extends AbstractDrinkWaterTemplate {
    @Override
    void pourWater() {
        System.out.println("泡茶...");
    }

    @Override
    void drinkWater() {
        System.out.println("喝茶...");
    }
}

测试代码

public class DrinkWaterTemplateTest {
    public static void main(String[] args) {
        new Coffee().drinkWaterProcess();
        System.out.println("-----------------");
        new Team().drinkWaterProcess();
    }
}

4.代理模式(Proxy)

所谓代理模式是指客户端并不直接调用实际的对象,而是通过调用代理对象,来间接的调用实际的对象。这样我们可以在代理调用被代理对象之前,之后,报错加入自己的逻辑。
举例:经纪人和明星
明星会唱歌,但是明星开办演唱会这个事情是交给了经纪人去办,经纪人就相当于明星的代理类,经纪人同合作方谈价钱,谈开办时间,活动,明星只需能够唱歌就行。经纪人(代理类)无异于增强了明星(原生类)。
UML图如下:
在这里插入图片描述

A.静态代理

代理分为动态代理与静态代理。我们先从简单的静态代理开始研究。

  • 实体接口
public interface ITeacherDao {
	void teach(); // 授课的方法
}
  • 具体实现类
public class TeacherDao implements ITeacherDao {
	@Override
	public void teach() {
		System.out.println(" 老师授课中  。。。。。");
	}
}
  • 代理类
//代理对象,静态代理
public class TeacherDaoProxy implements ITeacherDao{
	private ITeacherDao target; // 目标对象,通过接口来聚合
	//构造器
	public TeacherDaoProxy(ITeacherDao target) {
		this.target = target;
	}
	@Override
	public void teach() {
		System.out.println("开始代理  完成某些操作。。。。。 ");//方法
		target.teach();
		System.out.println("提交。。。。。");//方法
	}
}
  • 创建代理对象
public class Client {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//创建目标对象(被代理对象)
		TeacherDao teacherDao = new TeacherDao();
		//创建代理对象, 同时将被代理对象传递给代理对象
		TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(teacherDao);
		//通过代理对象,调用到被代理对象的方法	//即:执行的是代理对象的方法,代理对象再去调用目标对象的方法 
		teacherDaoProxy.teach();
	}
}
静态代理的优缺点

每个类都要创建一个代理类,对于项目开发而言需要为很多类都创建代理类,使用静态代理无疑是一个很麻烦的工作,我们需要在代码运行的过程中动态生成代理类 - 动态代理。

B.JDK动态代理

动态代理有别于静态代理, 是根据代理的对象,动态创建代理类。这样,就可以避免静态代理中代理类接口过多的问题。Java1.3就提供了动态代理,让咱们可以在代码运行期去实现一个接口的代理实例。这个功能在刚出来时,几乎没有太大实际用途,但是后来发现,它简直就是为实现AOP量身打造。

但是大家注意了, jdk的动态代理只允许完成有接口的代理,但是在我们开发的很多时候,可能还是会遇到去代理没有接口的类(创建的代理对象就是这个类的子类),比如咱们学习的Hibernate中的延时加载就是使用的这种方式。那么Spring是怎么解决这个问题的呢?Spring使用两种方式来完成动态代理:

  • 如果代理的类有接口,使用JDK的动态代理模式,
  • 如果代理的类没有接口,使用CGLIB的动态代理模式。

所以,现在咱们要开始来学习一下怎么使用这两种模式来完成动态代理。
案例的类图如下:
在这里插入图片描述
代码实现上面的类图
接口类

public interface ITeacherDao {
	void teach(); // 授课方法
	void sayHello(String name);
}

接口实现类

public class TeacherDao implements ITeacherDao {
	@Override
	public void teach() {
		System.out.println(" 老师授课中.... ");
	}
	@Override
	public void sayHello(String name) {
		System.out.println("hello " + name);
	}
}

代理工厂( 很重要!!!)

public class ProxyFactory {
	//维护一个目标对象 , Object
	private Object target;
	//构造器 , 对target 进行初始化
	public ProxyFactory(Object target) {
		this.target = target;
	} 
	//给目标对象 生成一个代理对象
	public Object getProxyInstance() {
		//说明
		/*
		 *  public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
                                          
            //1. ClassLoader loader : 指定当前目标对象使用的类加载器, 获取加载器的方法固定
            //2. Class<?>[] interfaces: 目标对象实现的接口类型,使用泛型方法确认类型
            //3. InvocationHandler h : 事情处理,执行目标对象的方法时,会触发事情处理器方法, 会把当前执行的目标对象方法作为参数传入
		 */
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), 
				target.getClass().getInterfaces(), 
				new InvocationHandler() {		//重写了invocationHandler
					@Override
					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
						// TODO Auto-generated method stub
						System.out.println("JDK代理开始~~");
						//反射机制调用目标对象的方法
						Object returnVal = method.invoke(target, args);
						System.out.println("JDK代理提交");
						return returnVal;
					}
				}); 
	}
}

创建代理

public class Client {
	public static void main(String[] args) {
		//创建目标对象
		ITeacherDao target = new TeacherDao();
		
		//给目标对象,创建代理对象, 可以转成 ITeacherDao
		ITeacherDao proxyInstance = (ITeacherDao)new ProxyFactory(target).getProxyInstance();
	
		// proxyInstance=class com.sun.proxy.$Proxy0 内存中动态生成了代理对象
		System.out.println("proxyInstance=" + proxyInstance.getClass());
		
		//通过代理对象,调用目标对象的方法
		//proxyInstance.teach();
		proxyInstance.sayHello(" tom ");
	}
}

C.CGLIB动态代理(子类代理 )

CGLIB是针对类来实现代理的,它的原理是对指定目标类生成一个子类,并覆盖其中的方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。

举例:
1、定义被代理类:

public class CglibOrigin {public  CglibOrigin(){
        System.out.println("CglibOrigin对象创建");
    }
    public final void  finalMethod(){
        System.out.println("final方法执行");
    }public void  regularMethod(){
        System.out.println("regularMethod方法执行");
    }
}

2、定义拦截器,用以拦截被代理对象的方法(需要实现MenthodInterceptor接口)

public class MyMethodInterceptor implements MethodInterceptor {//Object o——生成的代理对象
    //Method——被代理对象的方法
    //Object[] objects——方法的参数
    //methodProxy——代理方法
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("======插入前置通知======");
        ((CglibOrigin)o).regularMethod();
        Object object = methodProxy.invokeSuper(o, objects);
        System.out.println("======插入后者通知======");
        return object;
    }
}

其中intercept()就是代理对象调用到方法时执行到的代码,即实现增强的代码部分;

而methodProxy.invokeSuper(o, objects)是执行被代理对象的方法;

3、测试

public class MyTest {@Test
    public void test(){
        Enhancer enhancer = new Enhancer();
        //设置被代理类class
        enhancer.setSuperclass(CglibOrigin.class);
        //设置回调对象即拦截器
        enhancer.setCallback(new MyMethodInterceptor());
        //生成代理对象
        CglibOrigin origin = (CglibOrigin)enhancer.create();
        //调用方法
        origin.regularMethod();
    }
}

这里的origin.regularMethod()方法,其实就是执行MyMethodInterceptor的intercept()。

生成的代理对象内部是这样的:

public final void sayHello() {
 
//enhancer中赋值给callback的拦截器对象
 
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;if (var10000 == null) { ​ CGLIB$BIND_CALLBACKS(this); ​ var10000 = this.CGLIB$CALLBACK_0;}
 
    if (var10000 != null) {
    //调用拦截器MethodInterceptor的interceptor方法,完成增强
        var10000.intercept(this, CGLIB$sayHello$0$Method, CGLIB$emptyArgs, CGLIB$sayHello$0$Proxy);
    } else {
        super.sayHello();
    }
}

5.适配器模式

概述

将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作。

解决的问题

即Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作。
下面是一个非常形象的例子
在这里插入图片描述

代码实现(举例)

适配器模式包括3种形式:类适配器模式、对象适配器模式、接口适配器模式(或又称作缺省适配器模式)
以下举例,最新智能手机用VGA接口适配实现充电
源:手机typeC充电

package com.zhouzy.sjms.adaptee;
 
public class Adaptee {
	
	public void typeC(){
		System.out.println("手机正在充电");
	}
}

目标:vga充电

package com.zhouzy.sjms.adaptee;
 
public interface Target {
	public void vga();	//目标,vga充电
}
1.类适配器模式
package com.zhouzy.sjms.adaptee;
 
public class Adapter extends Adaptee implements Target {
 
	@Override
	public void vga() {
		typeC();	//vga实现typec充电
	}
 
}
2、对象适配器
package com.zhouzy.sjms.adaptee;
 
public class AdapterObj implements Target {
 
	Adaptee adaptee;
	public AdapterObj(Adaptee adaptee){
		this.adaptee = adaptee;
	}
	
	@Override
	public void vga() {
		adaptee.typeC();	//vga实现typec充电
	}
 
}
3、接口适配器
package com.zhouzy.sjms.adaptee;
 
public interface ITarget {
	public void typeC();
	public void typeC2vga();
	
}

定义一个抽象实现类

package com.zhouzy.sjms.adaptee;
 
public abstract class AbsAdapter implements ITarget {
	 	public void typec() { 
	 		
	 	}
	    
	    public void typec2vga() { 
	    	
	    }
}

具体实现类

package com.zhouzy.sjms.adaptee;
 
public class VgaAdapter extends AbsAdapter{	//其原理就是这个适配器同时实现了vga充电功能和typeC的充电功能
 
	@Override
	public void typeC() {
		System.out.println("手机正在充电");
	}
 
	@Override
	public void typeC2vga() {
		System.out.println("手机正在充电");
	}
 
}

总结

Java的设计模式到这里就告一段落了,最近公司业务繁忙,项目赶进度,这都是超梦在业余的时间去整理出来的东西,可能会有一部分内容不是很正确,欢迎在评论区留言指出问题所在,我会第一时间回复并且修改。最后点赞还是要求一下的,希望兄弟们能够一键三连(虽然我知道是下次一定)。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值