设计模式相关知识

七大原则

  • 单一职责原则
    一个类只负责一项职责
  • 接口隔离原则
    客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上
  • 依赖倒置原则
    高层模块不应该依赖底层模块,两者都应该依赖其抽象
    抽象不应该依赖细节,细节应该依赖抽象
    中心思想为面向接口编程(接口传递,构造方法传递,setter方式传递)
  • 里氏替换原则
    使用继承时,在子类中尽量不要重写父类的方法。适当时,可以通过聚合,组合,依赖来解决问题。
  • 开闭原则(最基础,最重要)
    对拓展开放(提供方),对修改关闭(使用方)
  • 迪米特法则(最少知道法则)
    为了降低类之间的 耦合
    一个对象应该对其他对象保持最少的了解,只与最直接的朋友通信(成员变量,方法参数,方法返回值)
  • 合成复用原则
    尽量使用合成/聚合的方式,而不是使用继承

UML

继承(泛化)

在这里插入图片描述

实现

矩形表示法
空心三角+虚线
在这里插入图片描述
棒棒糖表示法在这里插入图片描述

依赖

类中出现就具有依赖关系
在这里插入图片描述

关联

单向 或 双向的 n:m
在这里插入图片描述

聚合(has a)

弱关系,有不同的生命周期
表示的部分与整体的关系,可以分开
在这里插入图片描述

组合(contains a )

强关系 ,同一生命周期,不可分割
在这里插入图片描述

设计模式分类

  • 创建型模式:单例模式,抽象工厂模式,原型模式,建造者模式,工厂模式。
  • 结构型模式:适配器模式,桥接模式,装饰模式,组合模式,外观模式,享元模式,代理模式
  • 行为型模式:模板方法模式,命令模式,访问者模式,迭代器模式,观察者模式,中介者模式,备忘录模式,解释器模式,状态模式,策略模式,职责链模式(责任链模式)。

策略模式

设计原则:把变化的封装起来,不变的不受影响
组合比封装好
在这里插入图片描述

策略这个词应该怎么理解,打个比方说,我们出门的时候会选择不同的出行方式,比如骑自行车、坐公交、坐火车、坐飞机、坐火箭等等,这些出行方式,每一种都是一个策略。
  再比如我们去逛商场,商场现在正在搞活动,有打折的、有满减的、有返利的等等,其实不管商场如何进行促销,说到底都是一些算法,这些算法本身只是一种策略,并且这些算法是随时都可能互相替换的,比如针对同一件商品,今天打八折、明天满100减30,这些策略间是可以互换的。
  策略模式(Strategy),定义了一组算法,将每个算法都封装起来,并且使它们之间可以互换。UML结构图如下:

在这里插入图片描述
  其中,Context是上下文,用一个ConcreteStrategy来配置,维护一个对Strategy对象的引用;Strategy是策略类,用于定义所有支持算法的公共接口;ConcreteStrategy是具体策略类,封装了具体的算法或行为,继承于Strategy。
  应用:
  java中的Arrays中的comparator ,提供 compare 和equals 接口
  Treemap中的comparator

观察者模式

设计原则:松耦合
观察者一般可以看做是第三者,比如在学校上自习的时候,大家肯定都有过交头接耳、各种玩耍的经历,这时总会有一个“放风”的小伙伴,当老师即将出现时及时“通知”大家老师来了。再比如,拍卖会的时候,大家相互叫价,拍卖师会观察最高标价,然后通知给其它竞价者竞价,这就是一个观察者模式。
  对于观察者模式而言,肯定有观察者和被观察者之分。比如在一个目录下建立一个文件,这时系统会通知目录管理器增加目录,并通知磁盘减少空间,在这里,文件就是观察者,目录管理器和磁盘就是被观察者。
  观察者模式(Observer),又叫发布-订阅模式(Publish/Subscribe),定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新。UML结构图如下:
在这里插入图片描述
  其中,Subject类是主题,它把所有对观察者对象的引用文件存在了一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供了一个接口,可以增加和删除观察者对象;Observer类是抽象观察者,为所有的具体观察者定义一个接口,在得到主题的通知时更新自己;ConcreteSubject类是具体主题,将有关状态存入具体观察者对象,在具体主题内部状态改变时,给所有登记过的观察者发出通知;ConcreteObserver是具体观察者,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协同。
  当一个对象的改变需要同时改变其它对象,并且它不知道具体有多少对象有待改变的时候,应该考虑使用观察者模式。
  而使用观察者模式的动机在于:将一个系统分割成一系列相互协作的类有一个很不好的副作用,就是需要维护相关对象间的一致性,我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便,而观察者模式所做的工作就是在解除耦合。
  实例 java.util中的 Observer

装饰者模式

设计模式: 开闭原则
还记得我的一个长辈曾经买了一部手机,买的时候还好好的新新的,刚拿到家就坏了,怎么回事呢?其实就是一个假手机,把一个已经报废的旧机子改了改,外面加了个新壳子罢了,这就是一个装饰模式,在原有的基础上加了些东西。
  装饰模式(Decorator),动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更灵活。UML结构图如下:

其中,Component是抽象构件,定义一个对象接口,可以给这些对象动态地添加职责;ConreteComponent定义一个具体对象,也可以给这个对象添加一些职责;Decorator是装饰抽象类,实现接口或抽象方法;ConreteDecorator是具体装饰对象,起到给Component添加职责的功能。
  在这里插入图片描述
  java IO中大量装饰者模式
  和静态代理很像,非要分区别的话,静态代理不传参数,装饰者传参数。

工厂方法模式

设计原则:依赖倒置
工厂二字想必大家都不陌生,工厂就是用来建造东西的,我们市面上买的东西比如水杯、玩具、汽车等等都是从工厂生产的,那我们需不需要知道它们是如何生产出来的呢?当然不需要,商家从工厂中直接提货,我们就可以购买了,完全不知道它是如何生产的,这就是工厂方法模式。
工厂方法模式(Factory Method),定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法模式使一个类的实例化延迟到其子类。UML结构图如下:​
在这里插入图片描述
其中,Product定义工厂方法所创建的对象的接口;Creator声明工厂方法,并返回一个Product类型的对象;ConcreteProduct是具体的产品,实现了Product接口;ConcreteCreteCreator重定义工厂方法,返回一个ConcreteProduct实例。

简单工场和工厂方法模式区别:
在这里插入图片描述
在这里插入图片描述
如果现在需要增加其他运算,比如取余。简单工厂模式需要在添加case分支条件,修改了原有的类,违背了开闭原则;而工厂方法模式只需再新加个取余类和取余工厂,然后对客户端进行修改即可。
简单工厂模式最大的优点在于工厂类中包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关的类,对与客户端来说,去除了与具体产品的依赖。为了弥补他违背了开闭原则,于是就有了工厂方法模式,根据依赖倒转原则,把工厂类抽象出一个接口,这个接口只有一个方法,就是创建抽象产品的工厂方法。
其实工厂方法模式还存在一个问题,就是客户端需要决定实例化哪一个工厂来实现运算类,也就是说,工厂方法把简单工厂的内部逻辑判断移到了客户端代码来进行。对于这个问题,可以利用反射来解决(具体实例可参考抽象工厂模式中的反射实例)。
简单工厂应用实例:jdk中的calendar
jdbc 中的Class.for(name)
logger中的 getLogger()
工厂方法应用实例: 集合类collection 中的iterator() collection arraylist ltr
URLStreamHandler()

抽象工厂

抽象工厂(AbstractFactory)模式的定义:是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。

抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。
在这里插入图片描述
抽象工厂uml
在这里插入图片描述
在这里插入图片描述
应用实例:
java.sql 中的connection
mybatis中的sqlsessionFactory

单例模式

大家学操作系统的时候应该知道,当多个进程或线程同时操作一个文件时,只有一个能访问;java中类似的例子也有很多,比如多线程中我们最常用的锁,保证了多线程同时对一个方法或对象操作时只有一个能够访问。单例模式就是如此,我们给出它的定义。
  单例模式(Singleton),保证一个类仅有一个实例,并提供一个访问它的全局访问点。UML结构图如下:
  在这里插入图片描述

懒汉式

线程不安全
  在这里插入图片描述

public class Singleton {

    private static Singleton instance;
    
    //限制产生多个对象
    private Singleton() {
    }
    
    //通过方法获取实例对象
    public static Singleton getInstance() {
        if(instance == null) {
            instance = new Singleton();
        }
        
        return instance;
    }
    
}

饿汉式

线程安全,但是提前加载资源,消耗资源
static 变量 在类装载的时候就完成了实例化
有两种方法 :一种是下面的静态变量方式,另一种可以用静态代码块,都是为了instance提前赋值。
在这里插入图片描述

public class Singleton {
    //静态变量
    private static final Singleton instance = new Singleton();
    
    private Singleton() {
    }

    public static Singleton getInstance() {
        return instance;
    }
    
    //类中其他方法,尽量使static
    public static void dosomething() {
    }
    
}
class Sington{
//静态代码块
    static {
        instance  = new Sington();
    }
    private final static Sington instance;
    private  Sington(){

    }
    public static Sington getInstance(){
        return  instance;
    }
}

解决线程安全问题:

  • 懒汉式
    所谓懒汉式单例,就是通过在上述代码中增加synchronized关键字来实现。
//直接加同步代码块,解决线程安全问题
class Singleton{
    private  static Singleton instance;
    private Singleton(){

    }
    public synchronized   static  Singleton getInstance(){
        if(instance ==null){
            instance= new Singleton();
        }
        return instance;
    }
}

在这里插入图片描述
双重检查

public class Singleton {
	
    private volatile static Singleton instance;
    private static Object syncRoot = new Object();
    
    private Singleton() {
    }

    public static Singleton getInstance() {
        //双重锁定
        if(instance == null) {
            synchronized (syncRoot) {
                if(instance == null) {
                    instance = new Singleton();
                }
            }
        }
        
        return instance;
    }
    
}

这里使用了双重锁定(Double-Check Locking),这样可以不用让线程每次都加锁,而只是在实例未被创建的时候再枷锁处理,同时也能保证多线程的安全。
而这里判断了两次instance实例是否存在的原因是,当instance为null时,并且同时有两个线程调用getInstance()方法时,它们都可以通过第一重instance==null的判断,然后由于lock机制,这两个线程则只有一个进入,另一个在外排队等候,必须要其中一个进入并出来后,另一个才能进入,而此时如果没有了第二重排序,则第一个线程创建了实例,而第二个线程还是可以继续再创建新的实例,就没有达到单例的目的。
这里还需要注意一个问题,第三行中加入了volatile关键字,这里如果不加volatile可能会出现一个错误,即当代码读取到第11行的判断语句时,如果instance不为null时,instance引用的对象有可能还没有完成初始化,线程将访问到一个还未初始化的对象。究其原因是因为代码第14行,创建了一个对象,此代码可分解为三行伪代码,即分配对象的内存空间、初始化对象、设置instance指向刚分配的内存地址,分别记为1、2、3,在2和3之间,可能会被重排序,重排序后初始化就变为了最后一步。因此,线程A的intra-thread semantics(所有线程在执行Java程序时必须遵守intra-thread semantics,它保证重排序不会改变单线程内的程序执行结果)没有改变,但A2和A3的重排序将导致线程B判断出instance不为空,线程B接下来将访问instance引用的对象,此时,线程B将会访问到一个还未初始化的对象。而使用volatile就可以实现线程安全的延迟初始化,本质时通过禁止2和3之间的重排序,来保证线程安全的延迟初始化。

  • 静态内部类
    这种方式与饿汉式一样,同样利用了类加载来保证只创建一个instance实例,因此不存在线程安全的问题,不一样的是,它是在内部类里面去创建对象实例。这样只要应用中不使用内部类,JVM就不会去加载这个单例类,也就不会创建单例对象,从而实现懒汉式延迟加载。

在这里插入图片描述

public class Singleton {

    //静态内部类
    private static class SingletonHolder {
        public static Singleton instance = new Singleton();
    }
    
    private Singleton() {
    }
    
    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
    
}
  • 枚举
    上面实现单例的方式都需要额外的工作来实现序列化,只要在Singleton类中定义readResolve就可以解决该问题,这样序列化底层就不会使用反射来创建新对象。
    而且可以使用反射强行调用私有构造器。
    而枚举很好的解决了这两个问题,使用枚举除了线程安全和防止反射调用构造器外,还提供了自动序列化机制,防止反序列化的时候创建新的对象。
    在这里插入图片描述
public enum Singleton {

    instance;
    
    public static void dosomething() {
    }
    
}

枚举客户端:

public class Client {
    
    public static void main(String[] args) {    
        //枚举
        Singleton instance1 = Singleton.instance;
        Singleton instance2 = Singleton.instance;
        
        if(instance1 == instance2) {
            System.out.println("两个对象是相同的实例");
        }
    }
    
}

public class User {
    //私有化构造函数
    private User(){ }
 
    //定义一个静态枚举类
    static enum SingletonEnum{
        //创建一个枚举对象,该对象天生为单例
        INSTANCE;
        private User user;
        //私有化枚举的构造函数
        private SingletonEnum(){
            user=new User();
        }
        public User getInstnce(){
            return user;
        }
    }
 
    //对外暴露一个获取User对象的静态方法
    public static User getInstance(){
        return SingletonEnum.INSTANCE.getInstnce();
    }
}

public class Test {
    public static void main(String [] args){
        System.out.println(User.getInstance());
        System.out.println(User.getInstance());
        System.out.println(User.getInstance()==User.getInstance());
    }
}
结果为true

原型模式

是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时

创建对象有四种方式: new 。clone,反射的newInstance,反序列化(深拷贝)。

浅拷贝

当一个对象创建过程复杂,我们是否可以根据已有的对象直接来克隆一份,而不必关系创建的细节呢(原型模式)(浅拷贝)。
1.Java Object根类默认提供了clone方法:

protected native Object clone() throws CloneNotSupportedException;

一个本地方法,protected权限: 这样做是为避免我们创建每一个类都默认具有克隆能力

2.实现Cloneable接口

我们要使用一个对象的clone方法,必须Cloneable接口,这个接口没有任何实现,跟 Serializable一样是一种标志性接口.如果不实现Cloneable接口,会抛出CloneNotSupportedException异常.
重写clone方法,使用public修饰(否则外部调用不到),调用父类的clone方法
应用: spring中的prototype

深拷贝

两种方法 :重写clone或者序列化
1.重写clone 对引用类型的属性,再次clone
2.序列化,推荐,可以output再input

建造者模式

我们先说一个生活中的小例子,当我们在外面饭店吃饭时,比如点个水煮肉片,这家店可能会辣一点、那家店可能会咸一点、对面那家可能放青菜、隔壁那家可能放菠菜,每家店做出来的都不一样,明明都是水煮肉片却有不同的做法,如果都一样就不会说这家难吃那家好吃了。那再看快餐店,比如KFC,我们点个至尊虾堡,所有人不管在哪个城市哪家店,做法、味道都是一样的,为什么呢,因为它用料、时间、温度等等都是严格规定的,我们只需要下订单就行了,这就是一个建造者模式。
建造者模式(Builder),将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。UML结构图如下:
在这里插入图片描述
其中,Director为指挥者/导演类,负责安排已有模块的顺序,然后告诉Builder开始建造;Builder是抽象建造者,规范产品的组建,一般由子类实现;ConcreteBuilder是具体建造者,实现抽象类定义的所有方法,并且返回一个组建好的对象;Product是产品类,通常实现了模板方法模式。

  • 与工厂模式的区别
    建造者模式更关注于零件装配的顺序
  • 应用实例
    KFC的食品制作流程,原料多少克、加热几分钟等都有严格的规定,我们只需点餐即可,无论在哪里点的都是一样的。
    去KFC吃汉堡、薯条、炸鸡等,这些单品是不变的,其组合是经常改变的,也就是所谓的“套餐”。
    Java中的StringBuilder/StringBuffer,都继承自abstractStringBuilder bufer方法加了synchronized 。
    Guava 中的 immutable 类
    spring中的BeanDefinitionBuilder ,所有方法都是返回本身。

升级版建造者模式

链式调用,静态内部类,每个方法return this。

命令模式

在说命令模式前我们先来说一个小例子。很多人都有吃夜市的经历,对于那些推小车的摊位,通常只有老板一个人,既负责制作也负责收钱,我要两串烤串多放辣,旁边的人要了三串烤面筋不要辣,过了一会儿又来人要烤蔬菜……,当人多的时候记忆力不好的老板肯定就不知道谁要的啥、交没交钱了;而去有店铺的烤肉摊,点单的时候会有服务员来记录我们的菜单,然后再去通知烧烤师傅进行烧烤,这样就不会出现混乱了,当然我们也可以随时对菜单进行修改,此时只需服务员记录后去通知烤肉师傅即可,由于有了记录,最终算账还是不会出错的。
  从这里讲,前者其实就是“行为请求者”和“行为实现者”的紧耦合,对于请求排队或记录请求日志,以及支持可撤销的操作来说,紧耦合是不太合适的,而命令模式恰恰解决了这点问题。
  命令模式(Command),将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。UML结构图如下:
在这里插入图片描述
  其中,Invoker是调用者角色,要求该命令执行这个请求;Command是命令角色,需要执行的所有命令都在这里声明,可以是接口或抽象类;Receiver是接收者角色,知道如何实施与执行一个请求相关的操作,任何类都可能作为一个接收者;ConcreteCommand将一个接收者对象绑定与一个动作,调用接收者相应的操作,以实现Execute。
  命令模式其实是把一个操作的对象与知道怎么执行一个操作的对象分隔开。至于命令模式使用时机,敏捷开发原则告诉我们,不要为代码添加基于猜测的、实际不需要的功能。如果不清楚一个系统是否需要命令模式,一般就不要着急去实现它,事实上,在需要的时候通过重构实现这个模式并不困难,只有在真正需要如撤销/恢复操作等功能时,把原来的代码重构为命令模式才有意义。
  将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。把行为请求者与行为执行者解耦。
  应用:runnable接口,runnable是抽象命令
  一个Runnable就是一个Command;Thread扮演Invoder的角色,负责发起执行Runnable;Receiver可以是系统中的任何类,通常也可以作为Runnable的成员变量或者以参数形式传入Runnable。

适配器模式

适配器这个词我们应该很熟悉,天天都在使用,手机充电时,电源线头头就叫电源适配器,干什么用的呢?把220V电压转换为手机充电时使用的电压,那适配器模式是不是很好理解了,下面看一下定义。
适配器模式(Adapter),将一个类的接口转换成客户希望的另外一个接口。使原本由于接口不兼容而不能一起工作的那些类可以一起工作。
适配器模式有“类适配器”和“对象适配器”两种不同的形式。还有接口适配器

  • 对象适配器
    在这里插入图片描述

    通过对象层次的关联关系进行委托(对象的合成关系/关联关系)。UML结构图如下:
    在这里插入图片描述

  • 类适配器
    在这里插入图片描述

    是类间继承,对象适配器是对象的合成关系,也可以说是类的关联关系,这是两者的根本区别。
    由于对象适配器是通过类间的关联关系进行耦合的,因此在设计时就可以做到比较灵活,而类适配器就只能通过覆写源角色的方法进行扩展。
    在实际项目中,对象适配器使用到的场景较多

  • 接口适配器
    在这里插入图片描述

  • 在这里插入图片描述

  • 装饰者模式一般有新的功能和行为加入,适配器模式一般是接口的转换。

  • springmvc中的dispatcher
    在这里插入图片描述

外观模式

有些人可能炒过股票,但其实大部分人都不太懂,这种没有足够了解证券知识的情况下做股票是很容易亏钱的,刚开始炒股肯定都会想,如果有个懂行的帮帮手就好,其实基金就是个好帮手,支付宝里就有许多的基金,它将投资者分散的资金集中起来,交由专业的经理人进行管理,投资于股票、债券、外汇等领域,而基金投资的收益归持有者所有,管理机构收取一定比例的托管管理费用。
  其实本篇要说的这个设计模式就和这很有关系,由于当投资者自己买股票时,由于众多投资者对众多股票的联系太多,反而不利于操作,这在软件中就成为耦合性太高,而有了基金后,就变成众多用户只和基金打交道,关心基金的上涨和下跌,而实际上的操作确是基金经理人与股票和其它投资产品打交道,这就是外观模式。
  外观模式(Facade),为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。UML结构图如下:
在这里插入图片描述
  其中Facade是外观角色,也叫门面角色,客户端可以调用这个角色的方法,此角色知晓子系统的所有功能和责任,将客户端的请求代理给适当的子系统对象;Subsystem是子系统角色,可以同时拥有一个或多个子系统,每一个子系统都不是一个单独的类,而是一个类的集合,子系统并不知道门面的存在。
  外观模式是为了隐藏系统的复杂性,而提供一个统一的外观接口而设计的,这个接口使得这一子系统更加容易使用。简而言之,外观模式是为了降低访问复杂系统的内部子系统时的复杂度,简化客户端与之的接口

模板方法

所谓模板方法模式,其实很简单,可以从模板的角度考虑,就是一个对模板的应用,就好比老师出试卷,每个人的试卷都是一样的,即都是从老师的原版试卷复印来的,这个原版试卷就是一个模板,可每个人写在试卷上的答案都是不一样的,这就是模板方法模式,是不是很好理解。它的主要用途在于将不变的行为从子类搬到超类,去除了子类中的重复代码。
  模板方法模式(TemplateMethod),定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。UML结构图如下:
在这里插入图片描述
  其中,AbstractClass实现类一个模板方法,定义了算法的骨架,具体子类将重定义PrimitiveOperation以实现一个算法的步骤;而ConcreteClass实现了PrimitiveOperation以完成算法中与特定子类相关的步骤。

public abstract class AbstractClass {

    public abstract void PrimitiveOperation1();
    public abstract void PrimitiveOperation2();
    
    public void TemplateMethod() {
        PrimitiveOperation1();
        PrimitiveOperation2();
    }
    
}

在这里插入图片描述

应用:spring ioc 初始化中的 refresh()等
AQS!!

迭代器模式

遍历集合
内部一个下标 。
hasnext()
next()

和访问者模式有一点关系。

组合模式

在这里插入图片描述
优点:
在这里插入图片描述
应用:
jdk中的awt类
map和list中的addall(),参数是父类,也是一个应用

状态模式

状态这个词汇我们并不陌生,在日常生活中,不同时间就有不同的状态,早上起来精神饱满,中文想睡觉,下午又渐渐恢复,晚上可能精神更旺也可能耗费体力只想睡觉,这一天中就对应着不同的状态。或者对软件开发人员更形象的描述可能是UML的状态图(即用于描述一个实体基于事件反应的动态行为,显示了该实体如何根据当前所处的状态对不同的事件做出反应)。
  其实相对来说,就是一种状态的变化,而状态模式主要解决的问题就是当控制一个对象状态转换的条件表达式过于复杂时的情况。即把状态的判断逻辑转移到标识不同状态的一系列类当中。
  状态模式(State),当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。UML结构图如下:
  在这里插入图片描述
  其中,Context类为环境角色,用于维护一个ConcreteState子类的实例,这个实例定义当前的状态;State为抽象状态角色,定义一个接口以封装与Context的一个特定接口状态相关的行为;ConcreteState是具体状态角色,每一个子类实现一个与Context的一个状态相关的行为。
  可以和享元模式配合使用。状态 没有内部变量,可以 复用

代理模式

关于代理模式,我们听到的见到的最多的可能就是静态代理、动态代理之类的,当然还有大家都知道的Spring Aop,这里我们先不谈这些个代理,先说个简单的例子。游戏代练应该都听说过,许多人肯定也找过代练,曾经DNF、LOL、COC等等游戏的代练很多,当然现在各类游戏层出不穷,也都有各种代练,那这里所谓的代练是什么?就是Proxy,也即代理类,那游戏代练这件事就是一个代理模式。
  如果觉得不好理解可以这么想,代练的流程是,你把自己的账号交给代练人员,让他们帮你打怪升级,而你只需要提供账号即可。那代练人员那边,他所要做的就是登陆你的账号,然后替你打游戏,从第三者的角度来看,你这个角色在打怪升级,但这个第三者并不知道是不是你本人在打游戏,他只能看到你这个账号正在打怪升级,但并不需要知道后面打游戏的是谁。这就是代理模式,由他人代理玩游戏。
  如果觉得这个还不好理解,那再说一个例子。假设我现在要邀请明星来上节目,我是直接给这个明星打电话吗?当然不是,是给他的经纪人打电话,然后再由经纪人通知到该明星,这里经纪人充当的就是代理的角色。
  更常见的例子就是Windows的快捷方式,通过快捷方式,我们可以访问某个文件夹下的exe文件,这就是一个典型的代理模式,它将接口,按上面游戏的说法说就是代练的账号,提供了出来,我们只需点击快捷方式,它会帮我们运行指定目录下的指定程序。说了这么多,现在来看一下代理模式的定义。

静态代理

代理模式(Proxy),为其他对象提供一种代理以控制对这个对象的访问。UML结构图如下:
在这里插入图片描述
其中,Subject是主题角色,定义了RealSubject和Proxy的共同接口;RealSubject是具体主题角色,定义了Proxy所代表的真实实体;Proxy为代理主题角色,保存一个引用使代理可以访问实体,并提供一个与Subject的接口相同的接口。

  • Subject抽象类
      定义了RealSubject和Proxy的共同接口,这样就在任何使用RealSubject的地方都可以使用Proxy。
public abstract class Subject {
 
     public abstract void request();
     
 }
  • RealSubject类
      定义了Proxy所代表的真实实体。
public class RealSubject extends Subject {

    @Override
    public void request() {
        System.out.println("真实的请求RealSubject");
    }

}
  • Proxy类
      代理类。一个代理类可以代理多个被委托者或被代理者,因此一个代理类具体代理哪个真实主题角色,是由场景类决定的。
public class Proxy extends Subject {

    private RealSubject realSubject = null;
    
    public Proxy() {
        this.realSubject = new RealSubject();
    }
    
    @Override
    public void request() {
        this.before();
        this.realSubject.request();
        this.after();
    }

    //预处理
    private void before() {
        System.out.println("-------before------");
    }
    
    //善后处理
    private void after() {
        System.out.println("-------after-------");
    }
}
  • Client客户端
public class Client {

    public static void main(String[] args) {
        Proxy proxy = new Proxy();
        proxy.request();
    }
    
}

总结一下就是,在代理类中注入依赖,即引入需要代理的实体类,通过代理类来调用实体类中的方法来实现静态代理。
  静态代理由我们自己去生成固定的代码进行编译。需要定义接口或抽象的父类作为抽象目标类,具体目标类和代理类一起实现相同的接口或继承相同的类,然后通过调用相同的方法来调用目标对象的方法。
  静态代理需要目标对象和代理对象实现相同的接口。可以在不修改目标对象功能的前提下,对目标功能进行扩展。
  虽然静态代理可以很好的对目标对象进行功能扩展,但对每一个服务都需要建立代理类,工作量较大且不易管理,而且如果接口发生改变的话,代理类也得进行相应的修改,这时动态代理的作用就显现出来了。

动态代理

JDK

动态代理与静态代理的区别在于:在程序运行时,动态代理类是运用反射机制创建而成的。在抽象工厂模式的最后有提到用反射来代理switch语句进行选择,这里就运用到了类似的思想。
  通过动态代理,我们不再需要手动创建代理类,只需编写一个动态处理器即可,而真正的代理对象由JDK在运行时帮我们创建。所以我们也将之称为JDK动态代理。
  方法步骤如下:
1.写一个代理类实现InvocationHandler接口,通过构造函数把代理对象(具体目标类)传入到此处理器中,在invoke()方法中增加method.invoke(realSubject, args)
2.在调用方法时,通过java.lang.reflect.Proxy和newProxyInstance()来获取代理实现类,生成代理对象时,直接调用方法即可。

public class Proxy {
   private Object target;
   Proxy(Object target){
        this.target = target;
   }
   public Object getProxyInstance(){
        return java.lang.reflect.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("before!!");
                Object value = method.invoke(target,args);
                return value;
            }
        });
   }

}

下面看一个例子,UML结构图如下:
  在这里插入图片描述
  1. IBusiness接口
  被代理的接口。

 public interface IBusiness {
 
     public void doWork();
     
 }
 

2. Business类
  具体实现类/被代理的对象。

public class Business implements IBusiness {
 
     @Override
     public void doWork() {
         System.out.println("进行业务逻辑处理");
     }
 
 }

3. BusinessHandler类
  BusinessHandler类实现类Invocation接口,它是方法调用接口,声明了负责调用任意一个方法的invoke()方法,参数proxy指定动态代理类实例,参数method指定被调用的方法,参数args指定向被调用方法传递的参数,而invoke()方法的返回值表示被调用方法的返回值。其中 method.invoke(iBusiness, args) 相当于 iBusiness.method(args) 。

  public class BusinessHandler implements InvocationHandler {
      
      private IBusiness iBusiness;
      
      public BusinessHandler(IBusiness iBusiness) {
          this.iBusiness = iBusiness;
      }
  
      @Override
     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
         System.out.println("before");
         method.invoke(iBusiness, args);
         System.out.println("after");
         return null;
     }
 
 }

4. Client客户端

  public class Client {
      
      public static void main(String[] args) {
          Business business = new Business();
          
          //生成代理类对象
          IBusiness proxy = (IBusiness) Proxy.newProxyInstance(
                  business.getClass().getClassLoader(), 
                  business.getClass().getInterfaces(), 
                 new BusinessHandler(business));
         
         proxy.doWork();
     }
 
 }


此处通过java.lang.reflect.Proxy类的newProxyInstance()方法来生成代理类对象,它的完整定义如下:

 public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler) throws IllegalArgumentException

参数loader指定动态代理类的类加载器,参数interfaces指定动态代理类需要实现的所有接口,参数handler指定与动态代理类相关联的InvocationHandler对象。所以我们只需调用newProxyInstance()方法就可以某一个对象的代理对象了。(有关ClassLoader类加载器的内容这里就不再赘述了,它的作用是将class文件加载到jvm虚拟机中去)。
   相比于静态代理,动态代理的优势还是很明显的,不仅减少了对业务接口的依赖,还降低了耦合度,但它还是无法摆脱对接口的依赖。那么对于没有接口的类应该如何实现动态代理呢?

cglib

cglib是一个强大的高性能代码生成包,底层是通过使用一个小而快的字节码处理框架ASM来转换并生成新的类,所以我们一般也称之为cglib字节码生成。
  与JDK动态代理不同,cglib是针对类来实现代理的,所以对于没有接口的类我们可以通过cglib字节码生成来实现代理。原理是对指定的业务类生成一个子类,并覆盖其中的业务方法实现代理。但因为采用的是继承,所以不能对final修饰的类进行代理。
  下面看一个使用cglib进行代理的实例,需先导入相应的jar包(asm及cglib包)。
  方法步骤如下:
1.创建被代理类,创建拦截器(实现MethodInterceptor)
2.实现Enhancer工具类,允许为非接口类型创建一个Java代理
3.使用setSuperclass()方法设置父类
4.使用setCallback()方法设置回调函数(默认执行intercept方法)
5.使用create()方法创建子类

public class ProxyFactory implements MethodInterceptor {
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }
    public Object getProxyInstance(){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("before!!!");
        Object value = method.invoke(target,objects);
        System.out.println("after!!");
        return value;
    }
}

1. 被代理类
  首先定义业务类,无需实现接口。

 public class Hello {
     
     public void sayHello() {
         System.out.println("Hello World!");
     }
 
 }

2. 拦截器
  定义一个拦截器,通过实现MethodInterceptor接口的intercept()方法来实现回调方法,通过invokeSuper()执行目标对象的方法。

public class HelloMethodInterceptor implements MethodInterceptor {
  
      @Override
      public Object intercept(Object object, Method method , Object[] objects , MethodProxy methodProxy ) throws Throwable {
          System.out.println("before " + method.getName());
          methodProxy.invokeSuper(object, objects);
          System.out.println("after " + method.getName());
          return null;
      }
 
 }

3. Client客户端
  通过Enhancer加强类来创建动态代理类,通过它的setSuperclass()方法来指定要代理的业务类(即为下方生成的代理类指定父类),然后通过create()方法生成代理类对象。
  在enhance.create()创建完代理对象后,在代理类调用方法中,会被我们实现的方法拦截器HelloMethodInterceptor拦截。如果被代理类被final修饰,则该类不能被继承,即不能被代理;同样,如果被代理类存在final修饰的方法,则该方法不能被代理。

  public class Client {
      
      public static void main(String[] args) {
          Enhancer enhancer = new Enhancer();        //工具类
          enhancer.setSuperclass(Hello.class);    //继承被代理类
          enhancer.setCallback(new HelloMethodInterceptor());        //设置回调
          
          Hello hello = (Hello) enhancer.create();    //生成代理类对象
          hello.sayHello();
     }
 
 }

综上所述,cglib采用的是动态创建子类的方法,所以对final修饰的类不能进行代理。以Spring AOP编程为例,JDK动态代理及cglib代理的区别在于,有接口的目标对象采用JDK代理,无接口的目标对象采用cglib代理。
  使用cglib的前提条件为:

  • 需要引入cglib的jar文件
  • 目标类不能为final
  • 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法

享元模式

说到享元模式,第一个想到的应该就是池技术了,String常量池、数据库连接池、缓冲池等等都是享元模式的应用,所以说享元模式是池技术的重要实现方式。
  比如我们每次创建字符串对象时,都需要创建一个新的字符串对象的话,内存开销会很大,所以如果第一次创建了字符串对象“adam“,下次再创建相同的字符串”adam“时,只是把它的引用指向”adam“,这样就实现了”adam“字符串再内存中的共享。
  举个最简单的例子,网络联机下棋的时候,一台服务器连接了多个客户端(玩家),如果我们每个棋子都要创建对象,那一盘棋可能就有上百个对象产生,玩家多点的话,因为内存空间有限,一台服务器就难以支持了,所以这里要使用享元模式,将棋子对象减少到几个实例。下面给出享元模式的定义。
  在这里插入图片描述
  其中,Flyweight是抽象享元角色。它是产品的抽象类,同时定义出对象的外部状态和内部状态(外部状态及内部状态相关内容见后方)的接口或实现;ConcreteFlyweight是具体享元角色,是具体的产品类,实现抽象角色定义的业务;UnsharedConcreteFlyweight是不可共享的享元角色,一般不会出现在享元工厂中;FlyweightFactory是享元工厂,它用于构造一个池容器,同时提供从池中获得对象的方法。关键代码:用 HashMap 存储这些对象.
  应用实例:各种池技术
   Integer。valueOf() ,先判断是否在IntegerCache中,(-128-127),不在的话则new
   string

桥接模式

桥接模式,又叫桥梁模式,顾名思义,就是有座“桥”,那这座桥是什么呢?就是一条聚合线(下方UML图),比如我们下面会举的例子,手机有手机品牌和手机游戏等等,每个手机品牌都有多款游戏,那是不是二者之间就是聚合关系了,这是合成/聚合复用原则的体现,当我们发现类有多层继承时就可以考虑使用桥接模式,用聚合代替继承。
桥接模式(Bridge),将抽象部分与它的实现部分分离,使它们都可以独立地变化。UML结构图如下:
在这里插入图片描述
其中,Abstraction为抽象化角色,定义出该角色的行为,同时保存一个对实现化角色的引用;Implementor是实现化角色,它是接口或者抽象类,定义角色必需的行为和属性;RefinedAbstraction为修正抽象化角色,引用实现化角色对抽象化角色进行修正;ConcreteImplementor为具体实现化角色,实现接口或抽象类定义的方法或属性。
在这里插入图片描述
这里用到的合成/聚合复用原则是一个很有用处的原则,即优先使用对象的合成或聚合,而不是继承。究其原因是因为继承是一种强耦合的结构,父类变,子类就必须变。
用于解决多继承。防止类爆炸。

职责链模式

从文字角度出发,我们可以先将关注点放在“链”字上,很容易联想到链式结构,举个生活中常见的例子,击鼓传花游戏就是一个很典型的链式结构,所有人形成一条链,相互传递。而从另一个角度说,职责链就是所谓的多级结构,比如去医院开具病假条,普通医生只能开一天的证明,如果需要更多时常,则需将开具职责转交到上级去,上级医师只能开三天证明,如需更多时常,则需将职责转交到他的上级,以此类推,这就是一个职责链模式的典型应用。再比如公司请假,根据请假时常的不同,需要递交到的级别也不同,这种层级递进的关系就是一种多级结构。
  职责链模式(Chain Of Responsibility),使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。UML结构图如下:
在这里插入图片描述
  其中,Handler是抽象处理者,定义了一个处理请求的接口;ConcreteHandler是具体处理者,处理它所负责的请求,可访问它的后继者,如果可处理该请求就处理,否则就将该请求转发给它的后继者。
  抽象处理者实现了三个职责:
定义一个请求的处理方法handlerMessage(),是唯一对外开放的方法
定义一个链的编排方式setNext(),用于设置下一个处理者
定义了具体的请求者必须实现的两个方法,即定义自己能够处理的级别的getHandlerLevel()方法及具体的处理任务echo()方法

public abstract class Handler {
    
    private Handler nextHandler;    //下一个处理者
    
    public final Response handlerMessage(Request request) {
        Response response = null;
        
        if(this.getHandlerLevel().equals(request.getRequestLevel())) {    //判断是否是自己的处理级别
            response = this.echo(request);
        } else {
            if(this.nextHandler != null) {    //下一处理者不为空
                response = this.nextHandler.handlerMessage(request);
            } else {
                //没有适当的处理者,业务自行处理
            }
        }
        
        return response;
    }
    
    //设定下一个处理者
    public void setNext(Handler handler) {
        this.nextHandler = handler;
    }
    
    //每个处理者的处理等级
    protected abstract Level getHandlerLevel();
    
    //每个处理者都必须实现的处理任务
    protected abstract Response echo(Request request);

}

应用:
netty
springmvc
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

解释器模式

在这里插入图片描述
解释器和适配器区别:解释器需要提前知道规则,适配器不用
应用:正则表达式。

中介者模式

在这里插入图片描述
在这里插入图片描述

mvc中的c(controller)就是“中介者”。

访问者模式

在这里插入图片描述
元素加一个accept方法,访问者加visit方法。
相似模式:迭代器模式,迭代器注重遍历,访问者注重操作。两者都是以集合类(list,set,map等)为基础。
应用: nio 下的Filevisitor
spring中的BeanDefinitionVisitor

备忘录模式

在这里插入图片描述
应用场景:后悔药,游戏存档,ctrl+z ,数据库事务。
为了节约内存,可以和原型模式一起使用。

相似比较

Proxy、Facade以及Adapter可能都是对对象的一层封装,侧重点不同。

Proxy基于一致的接口进行封装,接口必须一致,只是对访问进行控制。

Facade针对封装子系统,转化为高层接口,

Adapter的封装是处于适配接口的目的。

1.外观模式是结构型模式,中介者模式是行为型模式。
2.外观模式是对子系统提供统一的接口,中介者模式是用一个中介对象来封装一系列同事对象的交互行为。
3.外观模式协议是单向,中介者模式协议是双向。
4.外观模式所有的请求处理都委托给子系统完成,而中介者模式则由中心协调同事类和中心本身共同完成业务。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值