Java 设计模式 轻读汇总版

网上设计模式的文章较多,本篇主要是自己总结学习用,力求简单,易于掌握。文章大量参考网络资源,主要有https://javadoop.com/post/design-pattern 一文和 《设计模式之禅》一书。

一 创建类模式

1 单例模式

定义

确保某一个类只有一个实例,并且自行实例化并向整个系统提供这个实例.

各种单例写法对比
单例写法单例保障机制单例对象初始化时机优点缺点
饿汉模式类加载机制类加载简单,易理解难以保证懒加载,无法应对反射和反序列化
双重校验锁(DCL)锁机制(需volatile防止重排序)第一次调用getInstance()实现懒加载复杂,无法应对反射和反序列化
Holder模式(静态内部类)类加载机制第一次调用getInstance()实现懒加载无法应对反射和反序列化
枚举枚举语言特性第一次引用枚举对象简洁,安全(语言级别防止通过反射和反序列化破坏单例)enum的另类用法
拓展点
  1. 双重校验锁:双重的原因和volatile关键字
  2. 饿汉模式:单例 final 关键字在并发情况下的作用,static 关键字修饰在类加载机制中的时机
  3. Holder模式:类加载条件
  4. enum:enum的相关用法
参考

单例模式及其4种推荐写法和3类保护手段

2 工厂模式(含简单工厂)

定义

定义一个用于创建对象的接口,让子类决定实例化哪一个类工厂方法使一个类的实例化延迟到其子类

简单工厂(静态工厂模式)

只需一个工厂的时候使用。
一个工厂类 XxxFactory,里面有一个静态方法,根据我们不同的参数,返回不同的派生自同一个父类(或实现同一接口)的实例对象

代码
public class FoodFactory {

    public static Food makeFood(String name) {
        if (name.equals("noodle")) {
            Food noodle = new LanZhouNoodle();
            noodle.addSpicy("more");
            return noodle;
        } else if (name.equals("chicken")) {
            Food chicken = new HuangMenChicken();
            chicken.addCondiment("potato");
            return chicken;
        } else {
            return null;
        }
    }
}
工厂模式

需要多个工厂的时候使用。
将工厂类进行抽象提取。

核心在于,我们需要在第一步选好我们需要的工厂。比如,我们有 LogFactory 接口,实现类有 FileLogFactory 和 KafkaLogFactory,分别对应将日志写入文件和写入 Kafka 中,显然,我们客户端第一步就需要决定到底要实例化 FileLogFactory 还是 KafkaLogFactory,这将决定之后的所有的操作。

例图

例图

3 抽象工厂模式

定义

为创建一组相关或相互依赖的对象提供一个接口,而且无须指定它们的具体类。
一般是涉及对象族的时候使用,屏蔽一个对象族的相同约束。缺点是很难拓展。

例子
使用普通工厂模式

使用普通工厂

// 得到 Intel 的 CPU
CPUFactory cpuFactory = new IntelCPUFactory();
CPU cpu = intelCPUFactory.makeCPU();

// 得到 AMD 的主板
MainBoardFactory mainBoardFactory = new AmdMainBoardFactory();
MainBoard mainBoard = mainBoardFactory.make();

// 组装 CPU 和主板
Computer computer = new Computer(cpu, mainBoard);

单独看 CPU 工厂和主板工厂,它们分别是前面我们说的工厂模式。这种方式也容易扩展,因为要给电脑加硬盘的话,只需要加一个 HardDiskFactory 和相应的实现即可,不需要修改现有的工厂。

但是,这种方式有一个问题,那就是如果 Intel 家产的 CPU 和 AMD 产的主板不能兼容使用,那么这代码就容易出错,因为客户端并不知道它们不兼容,也就会错误地出现随意组合。

下面就是我们要说的产品族的概念,它代表了组成某个产品的一系列附件的集合:
产品族

使用抽象工厂模式

当涉及到这种产品族的问题的时候,就需要抽象工厂模式来支持了。我们不再定义 CPU 工厂、主板工厂、硬盘工厂、显示屏工厂等等,我们直接定义电脑工厂,每个电脑工厂负责生产所有的设备,这样能保证肯定不存在兼容问题。
抽象工厂
这个时候,对于客户端来说,不再需要单独挑选 CPU厂商、主板厂商、硬盘厂商等,直接选择一家品牌工厂,品牌工厂会负责生产所有的东西,而且能保证肯定是兼容可用的。

public static void main(String[] args) {
    // 第一步就要选定一个“大厂”
    ComputerFactory cf = new AmdFactory();
    // 从这个大厂造 CPU
    CPU cpu = cf.makeCPU();
    // 从这个大厂造主板
    MainBoard board = cf.makeMainBoard();
      // 从这个大厂造硬盘
      HardDisk hardDisk = cf.makeHardDisk();

    // 将同一个厂子出来的 CPU、主板、硬盘组装在一起
    Computer result = new Computer(cpu, board, hardDisk);
}

当然,抽象工厂的问题也是显而易见的,比如我们要加个显示器,就需要修改所有的工厂,给所有的工厂都加上制造显示器的方法。这有点违反了对修改关闭,对扩展开放这个设计原则。

4 建造者模式

定义

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示

常见使用形式
Food food = Food.builder().a().b().c().build();
写法

核心:使用一个具有相关属性的静态内部类Builder,先把所有的属性都设置给 Builder,然后 build() 方法的时候,将这些属性复制给实际产生的对象。并可在 build() 的时候做自定义检查。

简易版

使用Lombok

@Builder
class User {
    private String  name;
    private String password;
    private String nickName;
    private int age;
}
标准版
//--------------建造者类----------------------------------------
class User {
    // 下面是“一堆”的属性
    private String name;
    private String password;
    private String nickName;
    private int age;

    // 构造方法私有化,不然客户端就会直接调用构造方法了
    private User(String name, String password, String nickName, int age) {
        this.name = name;
        this.password = password;
        this.nickName = nickName;
        this.age = age;
    }
    // 静态方法,用于生成一个 Builder,这个不一定要有,不过写这个方法是一个很好的习惯,
    // 有些代码要求别人写 new User.UserBuilder().a()...build() 看上去就没那么好
    public static UserBuilder builder() {
        return new UserBuilder();
    }

    public static class UserBuilder {
        // 下面是和 User 一模一样的一堆属性
        private String  name;
        private String password;
        private String nickName;
        private int age;

        private UserBuilder() {
        }

        // 链式调用设置各个属性值,返回 this,即 UserBuilder
        public UserBuilder name(String name) {
            this.name = name;
            return this;
        }

        public UserBuilder password(String password) {
            this.password = password;
            return this;
        }

        public UserBuilder nickName(String nickName) {
            this.nickName = nickName;
            return this;
        }

        public UserBuilder age(int age) {
            this.age = age;
            return this;
        }

        // build() 方法负责将 UserBuilder 中设置好的属性“复制”到 User 中。
        // 当然,可以在 “复制” 之前做点检验
        public User build() {
            if (name == null || password == null) {
                throw new RuntimeException("用户名和密码必填");
            }
            if (age <= 0 || age >= 150) {
                throw new RuntimeException("年龄不合法");
            }
            // 还可以做赋予”默认值“的功能
              if (nickName == null) {
                nickName = name;
            }
            return new User(name, password, nickName, age);
        }
    }
}

//---------------------客户端调用----------------------------------//
public class APP {
    public static void main(String[] args) {
        User d = User.builder()
                .name("foo")
                .password("pAss12345")
                .age(25)
                .build();
    }
}

5 原型模式

定义

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象
实质即克隆

Java 实现

Object 类中有一个 clone() 方法,它用于生成一个新的对象,当然,如果我们要调用这个方法,java 要求我们的类必须先实现 Cloneable 接口,此接口没有定义任何方法,但是不这么做的话,在 clone() 的时候,会抛出 CloneNotSupportedException 异常。

protected native Object clone() throws CloneNotSupportedException;
注意

java 的克隆是浅克隆,碰到对象引用的时候,克隆出来的对象和原对象中的引用将指向同一个对象。通常实现深克隆的方法是将对象进行序列化,然后再进行反序列化。

二 结构类模式

1 代理模式(委托模式)

定义

为其他对象提供一种代理以控制对这个对象的访问

例图

例图

拓展点

动态代理与AOP(Spring),JDK动态代理(需接口),CGLIB(需可继承)

2 适配器模式

定义

将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。

默认适配器模式

对于有多个方法的接口,可以提供一个默认实现(空实现)的默认适配器实现类,这样用户只需继承这个适配器类然后重写个别需要用到的方法即可。

对象适配器模式

对象适配器模式

类适配器模式

通过继承的方法,适配器自动获得了所需要的大部分方法。这个时候,客户端使用更加简单,直接 Target t = new SomeAdapter(); 就可以了。
类适配器模式

注意点
  1. 类适配和对象适配的异同
    一个采用继承,一个采用组合;
    类适配属于静态实现,对象适配属于组合的动态实现,对象适配需要多实例化一个对象。
    总体来说,对象适配用得比较多。
  2. 适配器模式和代理模式的异同
    在代码结构上,它们很相似,都需要一个具体的实现类的实例。但是它们的目的不一样,代理模式做的是增强原方法的活;适配器做的是适配的活,为的是提供“把鸡包装成鸭,然后当做鸭来使用”,而鸡和鸭它们之间原本没有继承关系。
    代理模式和适配器模式

3 装饰模式

定义

动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更加灵活。

结构图

所有的具体装饰者们 ConcreteDecorator 都可以作为 Component 来使用,因为它们都实现了 Component 中的所有接口。它们和 Component 实现类 ConcreteComponent 的区别是,它们只是装饰者,起装饰作用,也就是即使它们看上去牛逼轰轰,但是它们都只是在具体的实现中加了层皮来装饰而已。(通常在构造方法中传入被包装的基类Component)
结构图

例图

例图

Java IO 中的装饰模式

Java IO

4 门面模式(外观模式)

定义

要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。门面模式提供一个高层次的接口,使得子系统更加易于使用。
提供“统一的对象”给外部访问,不允许有任何直接访问子系统的行为发生,力求“金玉其表”
需要注意门面模式不符合开闭原则,难以拓展(因为无法直接访问内部)。

例图(slf4j)

例图

通用代码
//--------------------------------子系统-----------------------------------
public class ClassA{
	public void dosomethingA(){}
}
public class ClassB{
	public void dosomethingB(){}
}
public class ClassC{
	public void dosomethingC(){}
}
//----------------------------门面模式-------------------------------------
public class Facade{
	//被委托的对象
	private ClassA a=new ClassA();
	private ClassB a=new ClassB();
	private ClassC a=new ClassC();
	//提供给外部访问的方法
	public void methodA(){
		this.a.doSomethingA();
	}
	public void methodB(){
		this.b.doSomethingB();
	}
	public void methodC(){
		this.c.doSomethingC();
	}
}

5 桥梁模式(桥接模式)

定义

将抽象和实现解耦,使得两者可以独立地变化

即把会变化的实现定义成一个接口Implementor(桥梁)

结构图

结构图

例图

例图

6 组合模式(合成模式 / 部分-整体模式)

定义

将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。

当你发现需求中是体现部分与整体层次的结构时,以及你希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,就应该考虑使用组合模式了。

例子

每个员工都有姓名、部门、薪水这些属性,同时还有下属员工集合(虽然可能集合为空),而下属员工和自己的结构是一样的,也有姓名、部门这些属性,同时也有他们的下属员工集合。

public class Employee {
   private String name;
   private String dept;
   private int salary;
   private List<Employee> subordinates; // 下属

   public Employee(String name,String dept, int sal) {
      this.name = name;
      this.dept = dept;
      this.salary = sal;
      subordinates = new ArrayList<Employee>();
   }

   public void add(Employee e) {
      subordinates.add(e);
   }

   public void remove(Employee e) {
      subordinates.remove(e);
   }

   public List<Employee> getSubordinates(){
     return subordinates;
   }

   public String toString(){
      return ("Employee :[ Name : " + name + ", dept : " + dept + ", salary :" + salary+" ]");
   }   
}

7 享元模式

定义

使用共享对象可有效地支持大量的细粒度的对象

理解

每个事物都是不同的,但是又有一定的共性,如果只有完全相同的事物才能共享,那么享元模式可以说就是不可行的;
因此我们应该尽量将事物的共性共享,而又保留它的个性。为了做到这点,享元模式中区分了内部状态/内蕴状态(Internal State)和外部状态/外蕴状态(External State)。内部状态就是共性,外部状态就是个性了。

内部状态存储在享元内部,不会随环境的改变而有所不同,是可以共享的;
外部状态是不可以共享的,它随环境的改变而改变的,因此外部状态是由客户端来保持(因为环境的变化是由客户端引起的)。
在每个具体的环境下,客户端将外部状态传递给享元,从而创建不同的对象出来。
(外部状态一般用基本类型或String,如果外部状态也用类来表示则往往得不偿失)

因为把外部状态的管理交由客户端,故享元模式主要适用于数量多的、性质相近(外部状态少)的对象。

结构图

结构图

例子
//----------------------------抽象享元单元(Flyweight)----------------------

public interface Flyweight
{
    public void operation(String state);
}

//----------------------------具体享元单元(ConcreteFlyweight )----------------------

public class ConcreteFlyweight implements Flyweight
{
    private String str;

    public ConcreteFlyweight(String str)
    {
        this.str = str;
    }

    @Override
    public void operation(String state)
    {
        System.out.println("内蕴状态:"+str);
        System.out.println("外蕴状态:"+state);
    }
}

//----------------------------享元工厂(FlyWeightFactory)----------------------

public class FlyWeightFactory
{
    private Map<String,ConcreteFlyweight> flyWeights = new HashMap<String, ConcreteFlyweight>();

    public ConcreteFlyweight factory(String str)
    {
        ConcreteFlyweight flyweight = flyWeights.get(str);
        if(null == flyweight)
        {
            flyweight = new ConcreteFlyweight(str);
            flyWeights.put(str, flyweight);
        }
        return flyweight;
    }

    public int getFlyWeightSize()
    {
        return flyWeights.size();
    }
}


        

测试如下,可以看到 “a fly weight” 是作为外部状态传递给 f1的operation 方法的。对于这种仅需临时使用的对象,并不需要自己维持其外部状态,就比较适合使用享元模式。


//----------------------------测试代码----------------------

        FlyWeightFactory factory = new FlyWeightFactory();
        Flyweight f1 = factory.factory("a");
        Flyweight f2 = factory.factory("b");
        Flyweight f3 = factory.factory("a");

        f1.operation("a fly weight");
        f2.operation("b fly weight");
        f3.operation("c fly weight");

        System.out.println(f1 == f3);
        System.out.println(factory.getFlyWeightSize());
//----------------------------测试结果----------------------

内蕴状态:a
外蕴状态:a fly weight
内蕴状态:b
外蕴状态:b fly weight
内蕴状态:a
外蕴状态:c fly weight
true
2

三 行为类模式

1 策略模式

定义

定义一组算法,把每个算法都封装起来,并且使它们之间可以互换。

例图

例图

策略模式和桥梁模式的区别

策略模式更加简单,桥梁模式则是在Strategy使用类多加了一层抽象。

2 观察者模式

定义

定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。

被观察者(Subject)内部维护了一个观察者(Observer)列表,当被观察者执行操作的时候触发 notify 遍历 观察者列表逐个执行 update()操作,相对于观察者观察到了主题的变化而执行一定的操作。

注意

JDK提供了 Observable 和 Observer 来代表被观察者和观察者。
生产中往往使用消息中间件来实现,这时候往往变成 发布-订阅模型,有一些文章强调这两个模式是不同的。发布-订阅模型往往需要一个中间件(如分布式使用消息队列、单机可以使用Guava的EventBus事件总线),观察者向中间件订阅主题,发布者向中间件发布主题,可以进一步解耦。

结构图

结构图

3 责任链模式

定义

使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。

模式本质:分离职责,动态组合。分离职责是前提,动态组合是精华所在。

拓展

SpringSecurity的拦截链,Netty的处理链等

结构图

结构图

例子

例图
代码

@Data
@AllArgsConstructor
public class LeaveNote {
    private String name;
    private String leaveReason;
    private int leaverDayNum;
}

public interface Handler {
    void handLeave(LeaveNote leaveNote);

    void setNextHandler(Handler h);
}

public class DirectorHandler implements Handler {
    private Handler nextHandler;

    public void handLeave(LeaveNote leaveNote) {
        if (leaveNote.getLeaverDayNum() <= 3) {
            System.out.println("主管同意" + leaveNote.getName() + "申请请假" + leaveNote.getLeaverDayNum() + "天,原因:" + leaveNote.getLeaveReason());
        } else {
            nextHandler.handLeave(leaveNote);
        }
    }

    public void setNextHandler(Handler h) {
        nextHandler = h;
    }
}

public class ManagerHandler implements Handler {
    private Handler nextHandler;

    public void handLeave(LeaveNote leaveNote) {
        System.out.println("总经理同意" + leaveNote.getName() + "申请请假" + leaveNote.getLeaverDayNum() + "天,原因:" + leaveNote.getLeaveReason());
    }

    public void setNextHandler(Handler h) {
        nextHandler = h;
    }
}

public class ManagerHandler implements Handler {
    private Handler nextHandler;

    public void handLeave(LeaveNote leaveNote) {
        System.out.println("总经理同意" + leaveNote.getName() + "申请请假" + leaveNote.getLeaverDayNum() + "天,原因:" + leaveNote.getLeaveReason());
    }

    public void setNextHandler(Handler h) {
        nextHandler = h;
    }
}

public class Client {

    public static void main(String[] args) {
        LeaveNote leaveNote = new LeaveNote("小米","肚子疼",8);
        DirectorHandler directorHandler = new DirectorHandler();
        ViceManagerHandler viceManagerHandler = new ViceManagerHandler();
        ManagerHandler managerHandler = new ManagerHandler();

        directorHandler.setNextHandler(viceManagerHandler);
        viceManagerHandler.setNextHandler(managerHandler);
        directorHandler.handLeave(leaveNote);
    }
}

4 模板方法模式

定义

定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

常用于含有继承结构的代码中,将可能变化的步骤抽取出来,交由子类去实现而无须改变整体结构。

结构图

结构图

5 状态模式

定义

当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类。

优点:避免过多switch…case或者if…else,可以隐藏状态变换

Context 持有各个State,不同的State执行的操作不同,客户端通过Context来执行操作,将State的管理交由Context。适用于各个State有逻辑转化顺序的情景。

结构图

结构图

例子

抽象环境角色

public abstract class State {
     //定义一个环境角色,提供子类访问
     protected Context context;
     //设置环境角色
     public void setContext(Context _context){
             this.context = _context;
     }
     //行为1
     public abstract void handle1();
     //行为2
     public abstract void handle2();
}

具体环境角色
具体环境角色有两个职责:(1)处理本状态必须完成的任务,(2)决定是否可以过渡到其他状态。

public class ConcreteState1 extends State {
     @Override
     public void handle1() {
             //本状态下必须处理的逻辑
     }
     @Override
     public void handle2() {
             //设置当前状态为stat2
             super.context.setCurrentState(Context.STATE2);
             //过渡到state2状态,由Context实现
             super.context.handle2();
     }
}
public class ConcreteState2 extends State {
     @Override
     public void handle1() {          
             //设置当前状态为state1
             super.context.setCurrentState(Context.STATE1);
             //过渡到state1状态,由Context实现
             super.context.handle1();
     }
     @Override
     public void handle2() {
             //本状态下必须处理的逻辑
     }
}

上下文角色,提供给客户使用

public class Context {
     //定义状态
     public final static State STATE1 = new ConcreteState1();
     public final static State STATE2 = new ConcreteState2();
     //当前状态
     private State CurrentState;
     //获得当前状态
     public State getCurrentState() {
             return CurrentState;
     }
     //设置当前状态
     public void setCurrentState(State currentState) {
             this.CurrentState = currentState;
             //切换状态
             this.CurrentState.setContext(this);
     }
     //行为委托
     public void handle1(){
             this.CurrentState.handle1();
     }
     public void handle2(){
             this.CurrentState.handle2();
     }
}

客户

public class Client {
     public static void main(String[] args) {
             //定义环境角色
             Context context = new Context();
             //初始化状态
             context.setCurrentState(new ConcreteState1());
             //行为执行
             context.handle1();
             context.handle2();
     }
}

6 迭代器模式

定义

提供一种方法访问一个容器对象中各个元素,而又不需要暴露该对象的内部细节。

理解即可,一般不需要自己写

结构图

结构图

7 命令模式

定义

将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能

命令模式把发出命令的责任和执行命令的责任分隔开,委派给不同的对象。
每一个命令都是一个操作:请求的一方发出请求要求执行一个操作;接收的一方接收到请求,并执行操作。命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收、以及操作是否被执行、何时被执行、怎么被执行的。

命令允许请求的一方和接收请求的一方能够独立演化,从而具有如下的优点:

命令模式使新的命令很容易被加入到系统里。
允许接受请求的一方决定是否要否决请求。
能较容易的设计一个命令队列。
可以容易的实现对请求的撤销和恢复。
在需要的情况下,可以较容易的将命令记入日志。

对于命令Command来说,它必须直到由谁来执行,所以Command中持有Receiver的引用。对于Invoker来说,它只关心命令本身,虽然在构造命令的时候需要传入接收者,但它不用去关系具体如何调用实现。

结构图

结构图

例子

接收者角色类

public class Receiver {
    /**
     * 真正执行命令相应的操作
     */
    public void action() {
        System.out.println("执行操作");
    }
}
抽象命令角色类
```java
public interface Command {
    /**
     * 执行方法
     */
    void execute();
}

具体命令角色类

public class ConcreteCommand implements Command {
    /**
     * 持有相应的接收者对象
     */
    private Receiver receiver = null;
    /**
     * 构造方法
     * @param receiver
     */
    public ConcreteCommand(Receiver receiver) {
        this.receiver = receiver;
    }
    @Override
    public void execute() {
        //通常会转调接收者的形影方法,让接收者来真正执行功能
        receiver.action();
    }
}

请求者角色类

public class Invoker {
    /**
     * 持有命令对象
     */
    private Command command = null;
    /**
     * 构造方法
     * @param command
     */
    public Invoker(Command command) {
        this.command = command;
    }
    /**
     * 行动方法
     */
    public void action() {
        command.execute();
    }
}

客户端角色类

public class Client {
    public static void main(String[] args) {
        //创建接收者
        Receiver receiver = new Receiver();
        //创建命令对象,设定其接收者
        Command command = new ConcreteCommand(receiver);
        //创建请求者,把命令对象设置进去
        Invoker invoker = new Invoker(command);
        //执行方法
        invoker.action();
    }
}

8 备忘录模式

定义

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

由于备忘录模式有太多的变形和处理方式,每种方式都有它自己的优点和缺点,标准的备忘录模式很难在项目中遇到,基本上都有一些变换处理方式。

结构图

结构图

例子
/**
 * 备忘录
 */
@Data
@AllArgsConstructor
public class Memento {
	private String state;
}

/**
 * 发起人
 */
@AllArgsConstructor 
public class Originator {

	private String state;

	public void setMemento(Memento memento) {
		state = memento.getState();
	}

	public Memento createMemento() {
		return new Memento(state);
	}
	public void show(){
		System.out.println(state);
	}
}

/**
 * 管理者
 */
@AllArgsConstructor
public class Caretaker {
	private Memento memento;
}

/**
 * 客户端
 */
public class Client {
	public static void main(String[] args) {
		
		Originator originator = new Originator();
		originator.setState("start");
		originator.show();
		Memento memento = originator.createMemento();
		Caretaker caretaker = new Caretaker();
		caretaker.setMemento(memento);

		originator.setState("over");
		originator.show();	
		originator.setMemento(caretaker.getMemento());
		originator.show();
	}
}

9 访问者模式

定义

封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作

换言之,如果系统的数据结构是比较稳定的,但其操作(算法)是易于变化的,那么使用访问者模式是个不错的选择;如果数据结构是易于变化的,则不适合使用访问者模式。

处理现已稳定的数据结构易变的操作耦合问题,把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化。

访问者模式是一种集中规整模式,特别适用于大规模重构的项目,在这一个阶段需求已经非常清晰,原系统的功能点也已经明确,通过访问者模式可以很容易把一些功能进行梳理,达到最终目的——功能集中化,如一个统一的报表运算、UI展现等,我们还可以与其他模式混编建立一套自己的过滤器或者拦截器

较复杂。

结构图

访问者模式一共有五种角色:

  1. Vistor(抽象访问者):为该对象结构中具体元素角色声明一个访问操作接口。
  2. ConcreteVisitor(具体访问者):每个具体访问者都实现了Vistor中定义的操作。
  3. Element(抽象元素):定义了一个accept操作,以Visitor作为参数。
  4. ConcreteElement(具体元素):实现了Element中的accept()方法,调用Vistor的访问方法以便完成对一个元素的操作。
  5. ObjectStructure(对象结构):可以是组合模式,也可以是集合;能够枚举它包含的元素;提供一个接口,允许Vistor访问它的元素。
    结构图
例子

例图
代码:
完整例子见:https://segmentfault.com/a/1190000012495957https://www.jianshu.com/p/cd17bae4e949

class Home {
    private List<Animal> nodeList = new ArrayList<>();

    void action(Person person) {
        for (Animal node : nodeList) {
            node.accept(person);
        }
    }

    void add(Animal animal) {
        nodeList.add(animal);
    }
}
//-------------------------测试--------------------------------------
public class Client {

    public static void main(String[] args) {
        Home home = new Home();
        home.add(new Dog());
        home.add(new Cat());

        Owner owner = new Owner();
        home.action(owner);

        Someone someone = new Someone();
        home.action(someone);
    }

}

10 中介者模式

定义

用一个中介对象封装一系列的对象交互,中介者使各对象不需要显示地相互作用,从而使其耦合松散,而且可以独立地改变它们之间的交互

作用:

  1. 适当地使用中介者模式可以避免同事类之间的过度耦合,使得各同事类之间可以相对独立地使用。
  2. 使用中介者模式可以将对象间一对多的关联转变为一对一的关联,使对象间的关系易于理解和维护。
  3. 使用中介者模式可以将对象的行为和协作进行抽象,能够比较灵活的处理对象间的相互作用。
结构图
  1. Mediator(中介者接口)
    在里面定义各个同事之间交互需要的方法,可以是公共的通讯方法,比如changed方法,大家都用,也可以是小范围的交互方法。
  2. ConcreteMediator(具体中介者类)
    它需要了解并维护各个同事对象,并负责具体的协调各同事对象的交互关系。
  3. Colleague(同事类)
    通常实现成为抽象类,主要负责约束同事对象的类型,并实现一些具体同事类之间的公共功能,比如:每个具体同事类都应该知道中介者对象,也就是具体同事类都会持有中介者对象,就可以定义到这个类里面。
  4. ConcreteColleague(具体同事类)
    具体的同事类,实现自己的业务。每一个同事类都知道它的中介者对象。每一个同事对象在需与其他的同事通信的时候,与它的中介者通信。

结构图

例子

有两个类A和B,类中各有一个数字,并且要保证类B中的数字永远是类A中数字的100倍。也就是说,当修改类A的数时,将这个数字乘以100赋给类B,而修改类B时,要将数除以100赋给类A。类A类B互相影响,就称为同事类。代码如下:

abstract class AbstractColleague {
	protected int number;
 
	public int getNumber() {
		return number;
	}
 
	public void setNumber(int number){
		this.number = number;
	}
	//注意这里的参数不再是同事类,而是一个中介者
	public abstract void setNumber(int number, AbstractMediator am);
}
 
class ColleagueA extends AbstractColleague{
 
	public void setNumber(int number, AbstractMediator am) {
		this.number = number;
		am.AaffectB();
	}
}
 
class ColleagueB extends AbstractColleague{
 
	@Override
	public void setNumber(int number, AbstractMediator am) {
		this.number = number;
		am.BaffectA();
	}
}
 
abstract class AbstractMediator {
	protected AbstractColleague A;
	protected AbstractColleague B;
	
	public AbstractMediator(AbstractColleague a, AbstractColleague b) {
		A = a;
		B = b;
	}
 
	public abstract void AaffectB();
	
	public abstract void BaffectA();
 
}
class Mediator extends AbstractMediator {
 
	public Mediator(AbstractColleague a, AbstractColleague b) {
		super(a, b);
	}
 
	//处理A对B的影响
	public void AaffectB() {
		int number = A.getNumber();
		B.setNumber(number*100);
	}
 
	//处理B对A的影响
	public void BaffectA() {
		int number = B.getNumber();
		A.setNumber(number/100);
	}
}
 
public class Client {
	public static void main(String[] args){
		AbstractColleague collA = new ColleagueA();
		AbstractColleague collB = new ColleagueB();
		
		AbstractMediator am = new Mediator(collA, collB);
		
		System.out.println("==========通过设置A影响B==========");
		collA.setNumber(1000, am);
		System.out.println("collA的number值为:"+collA.getNumber());
		System.out.println("collB的number值为A的10倍:"+collB.getNumber());
 
		System.out.println("==========通过设置B影响A==========");
		collB.setNumber(1000, am);
		System.out.println("collB的number值为:"+collB.getNumber());
		System.out.println("collA的number值为B的0.1倍:"+collA.getNumber());
		
	}
}

11 解释器模式

定义

给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子

解释器是一个简单语法分析工具,它最显著的优点就是扩展性,修改语法规则只要修改相应的非终结符表达式就可以了,若扩展语法,则只要增加非终结符类就可以了。

尽量不要在重要的模块中使用解释器模式,否则维护会是一个很大的问题。在项目中可以使用shell、JRuby、Groovy等脚本语言来代替解释器模式,弥补Java编译型语言的不足。

解释器模式在实际的系统开发中使用得非常少,因为它会引起效率、性能以及维护等问题,一般在大中型的框架型项目能够找到它的身影,如一些数据分析工具、报表设计工具、科学计算工具等,若你确实遇到“一种特定类型的问题发生的频率足够高”的情况,准备使用解释器模式时,可以考虑一下Expression4J、MESP(Math Expression String Parser)、Jep等开源的解析工具包(这三个开源产品都可以通过百度、Google搜索到,请读者自行查询),功能都异常强大,而且非常容易使用,效率也还不错,实现大多数的数学运算完全没有问题,自己没有必要从头开始编写解释器。有人已经建立了一条康庄大道,何必再走自己的泥泞小路呢?

结构图

结构图

例子

例图

  1. 抽象表达式角色(Expression):声明一个所有的具体表达式角色都需要实现的抽象接口。这个接口主要是一个interpret()方法,称作解释操作。

  2. 终结符表达式角色(Terminal Expression):实现了抽象表达式角色所要求的接口,主要是一个interpret()方法;文法中的每一个终结符都有一个具体终结表达式与之相对应。比如有一个简单的公式R=R1+R2,在里面的R1和R2就是终结符,对应的解析R1和R2的解释器就是终结符表达式。

  3. 非终结表达式角色(Nonterminal Expression):文法中的每一条规则都需要一个具体的非终结符表达式,非终结符表达式一般是文法中的运算符或者其他关键字,比如公式R=R1+R2中,+就是非终结符,解析+的解释器就是一个非终结符表达式。

  4. 环境角色(Context):这个角色的任务一般是用来存放文法中各个终结符所对应的具体值,比如R=R1+R2,我们给R1赋值100,给R2赋值200。这些信息都需要存放到环境角色中,很多情况下我们使用Map来充当环境角色就够了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值