设计模式

252 阅读13分钟

代理模式

(1)基本介绍:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象。好处:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。

(2)被代理的对象可以说远程对象/创建开销大的对象或需要安全控制的对象

(3)代理模式有不同的形式,主要有静态代理/动态代理(JDK代理或接口代理)和Cglib代理(可以在内存动态的创建对象,而不需要实现接口,也属于动态代理的范畴)

(4)代理模式示意图:

静态代理

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

应用实例: 1)定义一个接口:ITeacherDao

public interface ITeacherDao {
    void teach();//授课方法
}
  1. 目标对象TeacherDao实现接口ITeacherDao
public class TeacherDao implements ITeacherDao{
    @Override
    public void teach() {
        System.out.println("老师授课中。。。");
    }
}
  1. 使用静态代理方式,就需要在代理对象TeacherDaoProxy中也实现ITeacherDao,同时定义一个ITeacherDao类型的属性和有参构造函数TeacherDaoProxy(ITeacherDao)以持有目标对象TeacherDao的引用,方便使用目标对象的方法
/**
 * 代理对象,静态代理
 */
public class TeacherDaoProxy implements ITeacherDao {
    private ITeacherDao iTeacherDao;//目标对象,通过接口来聚合
    public TeacherDaoProxy(ITeacherDao iTeacherDao) {
        this.iTeacherDao = iTeacherDao;
    }
    @Override
    public void teach() {
        System.out.println("代理开始。。。");//额外代码
        iTeacherDao.teach();//核心功能(代码)
        System.out.println("代理结束。。。");//增强功能
    }
}

4)调用的时候通过调用代理对象的方法来调用目标对象

public class Client {
    public static void main(String[] args) {
        //创建目标对象(被代理对象)
        TeacherDao teacherDao = new TeacherDao();

        //创建代理对象,同时将被代理对象传递给代理对象
        TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(teacherDao);

        //通过代理对象,调用到被代理对象的方法
        //即:执行的是代理对象的方法,代理对象再去调用目标对象的方法
        teacherDaoProxy.teach();
    }
}

5)静态代理模式示意图:

命令模式

1)命令模式使得请求发送者与请求接受者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦。

2)在命令模式中,会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求(即命令),同时命令模式也支持可撤销的操作。

3)通俗易懂的理解:将军发布命令士兵去执行。其中有几个角色:将军(命令发布者)、士兵(命令的具体执行者)、命令(连接将军和士兵)

Invoker是调用者(将军),Receiver是被调用者(士兵),MyCommand是命令,实现了Command接口,持有对象。

. Invoker 调用者角色

. Command 命令角色,需要执行的所有命令都在这里

. Receiver 接受者角色,知道如何实施和执行一个请求,可以是接口或抽象类

. ConcreteCommand 将一个接受者对象与一个动作绑定,调用接受者相应的操作实现

public interface Command {

    //执行动作(操作)
    void execute();

    //撤销动作(操作)
    void undo();
}
/**
 * 没有任何命令,即空执行:用于初始化每个按钮,当调用空命令时,对象什么都不做
 * 其实,这样式是一种设计模式,可以省掉空判断
 */
public class NoCommand implements Command {
    @Override
    public void execute() {

    }

    @Override
    public void undo() {

    }
}
public class LightOnCommand implements Command {

    //聚合接受者LightReceiver
    private LightReceiver lightReceiver;

    public LightOnCommand(LightReceiver lightReceiver) {
        this.lightReceiver = lightReceiver;
    }

    @Override
    public void execute() {
        //调用接受者的方法
        lightReceiver.on();
    }

    @Override
    public void undo() {
        //调用接受者的方法
        lightReceiver.off();
    }
}
public class LightOffCommand implements Command {

    private LightReceiver lightReceiver;

    public LightOffCommand(LightReceiver lightReceiver) {
        this.lightReceiver = lightReceiver;
    }

    @Override
    public void execute() {
        lightReceiver.off();
    }

    @Override
    public void undo() {
        lightReceiver.on();
    }
}
public class LightReceiver {

    public void on(){
        System.out.println("电灯打开了。。。");
    }

    public void off(){
        System.out.println("电灯关闭了");
    }
}
public class RemoteController {
    //开 按钮的命令数组
    Command[] onCommands;
    Command[] offCommands;

    //执行撤销的命令
    Command undoCommand;

    //构造器,完成对按钮初始化

    public RemoteController() {
        onCommands = new Command[5];
        offCommands = new Command[5];

        for (int i = 0; i < 5;i++){
            onCommands[i] = new NoCommand();
            offCommands[i] = new NoCommand();
        }
    }

    //给我们的按钮设置你需要的命令
    public void setCommand(int no, Command onCommand, Command offCommand){
        onCommands[no] = onCommand;
        offCommands[no] = offCommand;
    }

    public void onButtonWasPushed(int no){
        //找到你按下的开的按钮,并调用对应方法
        onCommands[no].execute();
        //记录这次的记录,用于撤销
        undoCommand = onCommands[no];
    }

    //按下关按钮
    public void offButtonWasPushed(int no){
        //找到你按下的开的按钮,并调用对应方法
        offCommands[no].execute();
        //记录这次的记录,用于撤销
        undoCommand = offCommands[no];
    }

    //按下撤销按钮
    public void undoButtonWasPushed(){
        undoCommand.undo();
    }
}

访问者模式

适配器模式

类适配器模式

基本介绍:Adapter类,通过继承src类,实现dst类接口,完成src->dst的适配

类适配器模式应用实例:

1)充电器本身相当于Adapter,220V交流电相当于src(即被适配者),我们的目标dst是5V直流电

2)图解:

//被适配的类
public class Voltage220V {
    //输出220电压
    public int output220V(){
        int src = 220;
        System.out.println("电压 = " + src + "伏");
        return src;
    }
}
//适配接口
public interface IVoltage5V {
    public int output5V();
}
//适配器类
public class VoltageAdapter extends Voltage220V implements IVoltage5V {
    @Override
    public int output5V() {
        //获取到220V电压
        int srcV = output220V();
        int dstV = srcV /44;//转成5V
        return dstV;
    }
}
public class Phone {
    //充电
    public void charing(IVoltage5V iVoltage5V){
        if (iVoltage5V.output5V() == 5){
            System.out.println("电压5V,可以充电。。。");
        }else if (iVoltage5V.output5V() > 5){
            System.out.println("电压大于5V,不能充电");
        }
    }
}
public class Client {
    public static void main(String[] args) {
        System.out.println("==类适配器模式==");
        Phone phone = new Phone();
        phone.charing(new VoltageAdapter());
    }
}

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

1)Java是单继承机制,所以类适配器需要继承src类,这一点算是一个缺点,因为这要求dst必须是接口,有一点的局限性;

2)src类的方法在Adapter中都会暴露出来,也增加了使用的成本

3)由于其继承了src类,所以它可以根据需求重写src类的方法,使得Adapter的灵活性增强了。

对象适配器模式

1)基本思路和类的适配器模式相同,只是将Adapter类作修改,不是继承src类,而是持有src类的实例,以解决兼容性的问题。即:持有src类,实现dst类接口,完成src-dst的适配

2)根据“合成复用原则”,在系统中尽量使用关联关系替代继承关系。

3)对象适配器模式是适配器模式常用的一种

图解:

对上面的应用实例改造,只需改动适配器类Adapter和client客户端调用

//适配器类
public class VoltageAdapter implements IVoltage5V {

    private Voltage220V voltage220V;//关联关系-聚合

    //通过构造器,传入一个Voltage220V 实例
    public VoltageAdapter(Voltage220V voltage220V){
        this.voltage220V = voltage220V;
    }
    @Override
    public int output5V() {
        int dst = 0;
        if (voltage220V != null){
            int src = voltage220V.output220V();//获取220V 电压
            dst = src / 44;
            System.out.println("输出电压 = " + dst);
        }
        return dst;
    }
}
public class Client {
    public static void main(String[] args) {
        System.out.println("==对象适配器模式==");
        Phone phone = new Phone();
        phone.charing(new VoltageAdapter(new Voltage220V()));
    }
}

接口适配器模式

1)当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择性的覆盖父类的某些方法来实现需求。

2)适用于一个接口不想使用其所有的方法的情况。

public interface Interface4 {
    public void m1();
    public void m2();
    public void m3();
    public void m4();
}
//在AbsAdapter 我们将Interface4 的方法进行默认实现
public abstract class AbsAdapter implements Interface4{
    @Override
    public void m1() {

    }

    @Override
    public void m2() {

    }

    @Override
    public void m3() {

    }

    @Override
    public void m4() {

    }
}
public class Client {
    public static void main(String[] args) {
        AbsAdapter absAdapter = new AbsAdapter(){
            //只需要去覆盖我们需要使用的接口方法(匿名内部类)
            @Override
            public void m1() {
                System.out.println("使用了m1的方法");
            }
        };
        absAdapter.m1();
    }
}

适配器模式在SpringMVC框架应用的源码分析

1)SpringMVC中的HandlerAdapter,就使用了适配器模式 2)SpringMVC处理请求的流程回顾:

3)使用HandlerAdapter的原因分析:可以看到处理器的类型不同,有多重实现方,那么调用方式就不是确定的,如果需要直接调用Controller方法,需要调用的时候就得不断使用if else 来进行判断是哪一种子类然后执行,那么如果后面要扩展Controller,就得修改原来的代码,这就违背了OCP原则。

原型模式

1)原型模式(Prototype模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象。

2)原型模式是一种创建型设计模式,允许一个对象再创建另一个可定制的对象,无需知道如何创建的细节。

3)对象.clone()

4)形象的理解:孙大圣拔出猴毛,变出其他孙大圣。

克隆羊问题:

现在有一只羊tom,姓名:tom,年龄为:1,颜色:白色,请编写程序创建和tom羊属性完全相同的10只羊。

传统的方式的缺点:

(1)在创建新的对象时,总时需要重新获取原始对象的属性,如果创建的对象比较复杂,效率比较低

(2)总是需要重新初始化对象,而不是动态地获得对象运行的时的状态,不够灵活

(3)改进的思路分析:Java中的Object类是所有类的根类,Object类提供了一个clone()方法,该方法可以将一个Java对象复制一份,但是需要实现clone的Java类必须要实现一个接口Cloneable,该接口表示该类能够复制且具有复制的能力 =》 原型模式

(4)图解:

public class Sheep implements Cloneable{

    private String name;

    private int age;

    private String color;

    public Sheep(String name, int age, String color) {
        this.name = name;
        this.age = age;
        this.color = color;
    }
    
    // get() and set()
    // toString()
    
    //克隆该实例,使用默认的clone方法来完成
    @Override
    protected Object clone(){
        Sheep sheep = null;
        try {
            sheep = (Sheep) super.clone();
        }catch (Exception e){
            System.out.println(e.getMessage());
        }
        return sheep;
    }
}
public class client {
    public static void main(String[] args) {
        System.out.println("原型模式完成对象的克隆");
        Sheep sheep = new Sheep("tom",1,"白色");
        Sheep sheep2 = (Sheep)sheep.clone();//克隆
        Sheep sheep3 = (Sheep)sheep.clone();//克隆
        System.out.println("sheep2 = " + sheep2);
        System.out.println("sheep3 = " + sheep3);
    }
}

如果需要添加其他属性address等,直接修改原型Sheep即可。

原型模式再Spring框架中源码分析:

1)Spring中原型bean的创建,就是原型模式的应用(扩展)

深入讨论-浅拷贝和深拷贝

浅拷贝的介绍:

1)对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。

2)对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。

3)前面我们的克隆羊就是浅拷贝。

4)浅拷贝是使用默认的clone()方法来实现sheep=(Sheep)super.clone()

深拷贝的介绍:

1)复制对象的所有基本数据类型的成员变量值

2)为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象。

3)深拷贝实现方式1:重写clone方法来实现深拷贝

4)深拷贝实现方式2:通过对象序列话实现深拷贝

建造者模式

基本介绍:

1)建造者模式(Builder Pattern)又叫生成器模式,属于创建型模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方式可以构造出不同表现(属性)的对象。

2)建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。

建造者模式的四个角色

1)Product(产品角色):一个具体的产品对象。

2)Builder(抽象建造者):创建一个Product对象的各个部件指定的接口/抽象类。

3)ConcreteBuilder(具体建造者):实现接口,构建和装配各个部件。

4)Director(指挥者):构建一个使用Builder接口的对象。。它主要是用于创建一个复杂的对象。它主要有两个作用,一是:隔离了客户与对象的生成过程,二是负责控制产品对象的生产过程。

建造者模式解决盖房需求应用实例:

1)需要建房子:这一过程为打桩、砌墙、封顶。不管是普通房子也好,别墅也好都需要经历这些过程。

2)思路分析图解:

/**
* 产品角色 -> Product
* 定义产品的基本属性
*/
public class House {

    private String baise;

    private String wall;

    private String roofed;

    //geter and seter
}
// 抽象的建造者
public abstract class HouseBuilder {

    protected House house = new House();

    //将建造的流程写好,抽象的方法
    public abstract void buildBasic();
    public abstract void buildWalls();
    public abstract void roofed();

    //建造房子好,将产品(房子)返回
    public House buildHouse(){
        return house;
    }
}
/**
* 具体建造者角色 -> ConcreteBuilder
* 负责实现抽象的建造过程,按指定的规格建造特定的产品
*/
public class CommonHouse extends HouseBuilder {
    @Override
    public void buildBasic() {
        System.out.println("普通房子打地基5米");
    }

    @Override
    public void buildWalls() {
        System.out.println("普通房子砌墙10cm");
    }

    @Override
    public void roofed() {
        System.out.println("普通房子屋顶");
    }
}
/**
* 具体建造者角色 -> ConcreteBuilder
* 负责实现抽象的建造过程,按指定的规格建造特定的产品
*/
public class HighHouse extends HouseBuilder {
    @Override
    public void buildBasic() {
        System.out.println("高楼的打地基100米");
    }

    @Override
    public void buildWalls() {
        System.out.println("高楼的砌墙20cm");
    }

    @Override
    public void roofed() {
        System.out.println("高楼的透明屋顶");
    }
}
/**
* 指挥者,指定制作流程,并返回产品
*/
public class HouseDirector {
    HouseBuilder houseBuilder = null;

    //构造器传入 houseBuilder
    public HouseDirector(HouseBuilder houseBuilder){
        this.houseBuilder =houseBuilder;
    }

    //通过setter 传入 houseBuilder,不必再重新创建新的指挥者
    public void setHouseBuilder(HouseBuilder houseBuilder){
        this.houseBuilder = houseBuilder;
    }

    //如何处理建造房子的流程,交给指挥者(组装各个部分零件)
    public House constructHouse(){
        houseBuilder.buildBasic();
        houseBuilder.buildWalls();
        houseBuilder.roofed();
        return houseBuilder.buildHouse();
    }
}
public class client {
    public static void main(String[] args) {
        //盖普通房子
        CommonHouse commonHouse = new CommonHouse();
        //准备创建房子的指挥者
        HouseDirector houseDirector = new HouseDirector(commonHouse);
        House house = houseDirector.constructHouse();

        //盖高楼
        HighHouse highBuilder = new HighHouse();
        //重置建造者
        houseDirector.setHouseBuilder(highBuilder);
        //完成盖房子,返回产品(高楼)
        houseDirector.constructHouse();
    }
}

建造者模式在JDK的应用和源码分析

1)Appendable接口定义了许多append抽象方法,即Appendable为抽象建造者,定义了抽象方法。

2)AbstractStringBuilder抽象类实现了Appendable接口方法,这里的AbstractStringBuilder已经是具体建造者,只是不能实例化。

3)StringBuilder即是指挥者角色,又是具体建造者。建造方法的实现是由AbstractStringBuilder完成的,StringBuilder只是继承了AbstractStringBuilder,使用了父类方法而已。

public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence{

    /*
    * 使用了父类的方法
    */
    public StringBuilder(String str) {
        super(str.length() + 16);
        append(str);
    }
    ...
}
/*
*实现了Appendable接口中的抽象方法,但此类仍然是抽象类,不能实例化
*/
abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /*
    * 实现了Appendable接口中的方法
    */
    public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }
    ...
}
public interface Appendable {
    /*
    *建造过程的抽象方法
    */
    Appendable append(CharSequence csq, int start, int end) throws IOException;

    Appendable append(CharSequence csq) throws IOException;
    
    Appendable append(char c) throws IOException;
}