设计模式学习笔记

设计模式

  • 设计模式(Design Pattern)是前辈们们对代码开发经验得总结,是解决特定问题得一系列套路。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性得解决方案。
  • 1995年,GoF(Gang of Four,四人组/四人帮)合作出版了《设计模式:可复用面向对象软件得基础》一书,共收录了23种设计模式,从此树立了软件设计模式领域得里程碑,人称“GoF设计模式
  • GoF 23:一种思维,一种态度,一种进步
  • 设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。
  • 正确使用设计模式具有以下优点:
    • 可以提高程序员的思维能力、编程能力和设计能力
    • 是程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期
    • 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性

设计模式的基本要素:模式名称、问题、解决方案、效果

面向对象设计模式可分为以下三类:

  • 创建型模式:单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式
    • 目标:将一个系统与其对象的创建、组合、表示分离开来
    • 目的:在哪个对象被创建、谁负责创建对象、怎样创建对象、何时创建对象方面增强灵活性
    • 主要任务:为客户程序创建对象,而不是由客户程序直接初始化对象
    • 好处:大量减少客户程序中对象创建的代码量
  • 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
    • 作用:从程序的结构上实现松耦合,从而可以扩大整体的类结构,用来解决更大的问题。
  • 行为型模式:模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式
序号模式 & 描述包括
1创建型模式 这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。工厂模式(Factory Pattern) 抽象工厂模式(Abstract Factory Pattern) 单例模式(Singleton Pattern) 建造者模式(Builder Pattern) 原型模式(Prototype Pattern)
2结构型模式 这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。适配器模式(Adapter Pattern) 桥接模式(Bridge Pattern) 过滤器模式(Filter、Criteria Pattern) 组合模式(Composite Pattern) 装饰器模式(Decorator Pattern) 外观模式(Facade Pattern) 享元模式(Flyweight Pattern) 代理模式(Proxy Pattern)
3行为型模式 这些设计模式特别关注对象之间的通信。责任链模式(Chain of Responsibility Pattern) 命令模式(Command Pattern) 解释器模式(Interpreter Pattern) 迭代器模式(Iterator Pattern) 中介者模式(Mediator Pattern) 备忘录模式(Memento Pattern) 观察者模式(Observer Pattern) 状态模式(State Pattern) 空对象模式(Null Object Pattern) 策略模式(Strategy Pattern) 模板模式(Template Pattern) 访问者模式(Visitor Pattern)
4J2EE 模式 这些设计模式特别关注表示层。这些模式是由 Sun Java Center 鉴定的。MVC 模式(MVC Pattern) 业务代表模式(Business Delegate Pattern) 组合实体模式(Composite Entity Pattern) 数据访问对象模式(Data Access Object Pattern) 前端控制器模式(Front Controller Pattern) 拦截过滤器模式(Intercepting Filter Pattern) 服务定位器模式(Service Locator Pattern) 传输对象模式(Transfer Object Pattern)

image-20200706160347954

OOP七大原则

  • 开闭原则(Open Close Principle):对扩展开放,对修改关闭

    ​ 在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类。

  • 里氏替换原则(Liskov Substitution Principle:继承必须确保超类所拥有的性质在子类中仍然成立

    ​ 任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

  • 依赖倒置原则(Dependence Inversion Principle):要面向接口编程,不要面向实现编程

    针对接口编程,依赖于抽象而不依赖于具体。

  • 单一职责原则:控制类的粒度大小、将对象解耦、提高其内聚性

  • 接口隔离原则(Interface Segregation Principle):要为各个类建立它们需要的专用接口

    ​ 使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。

  • 迪米特法则(Demeter Principle):只与你的直接朋友交谈,不跟”陌生人“说话

    ​ 一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。

  • 合成复用原则(Composite Reuse Principle):尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。

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

创建型模式

单例模式

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

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

注意:

  • 单例类只能有一个实例
  • 单例类必须自己创建自己唯一的实例
  • 单例类必须给所有其他对象提供这一实例

介绍

意图:保证一个类仅有一个实例,并向外提供一个访问它的全局访问点。

何时使用:需要控制实例数目,节省系统资源的时候

如何解决:判断系统释放已经有这个单例,如果有则返回,如果没有则创建。

关键代码:构造函数必须私有。

应用实例

  • 一个班级只有一个班主任
  • Windows是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过一个唯一的实例来进行。
  • 一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。

使用场景

  • 要求生产唯一序列号
  • Web中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来
  • 创建的一个对象需要消耗的资源过多,比如I/O与数据库的连接等

优点

  • 在内存中只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例
  • 避免对资源的多重占用(比如写文件操作)

缺点

没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

注意事项

  • 多线程情况下getInstance()方法需要使用同步锁synchronized(Singleton.class)防止多线程同时进入造成instance被多次实例化

实现

​ 创建SingleObject类。SingleObject 类(单例类)有它的私有构造函数和本身的一个静态实例。SingleObject类提供了一个静态方法,供外界获取它的静态实例。演示类SingletonPatternDemo使用SingleObject类来获取 SingleObject对象。

image-20200625165847438

步骤一

创建一个Singleton类:SingleObject.java

public class SingleObject {
	//创建 SingleObject 的一个对象
	private static SingleObject instance = new SingleObject();

	//让构造函数为 private,这样该类就不会被实例化
	private SingleObject() {}

	//获取唯一可用的对象
	public static SingleObject getInstance() {
    	return instance;
	}

	public void showMessage() {
    	System.out.println("Hello Singleton.");
	}
}

步骤二

从singleton类获取唯一的对象:SingletonPatternDemo.java

public class SingletonPatternDemo {
    public static void main(String[] args) {
        //不合法的构造函数
        //编译时错误:构造函数 SingleObject() 是不可见的
        //SingleObject object = new SingleObject();

        //获取唯一可用的对象
        SingleObject object1 = SingleObject.getInstance();
        SingleObject object2 = SingleObject.getInstance();

        System.out.println(object1);
        System.out.println(object2);

    }

}

结果

com.jourwon.designpattern.creational.singleton.SingleObject@1b6d3586
com.jourwon.designpattern.creational.singleton.SingleObject@1b6d3586

单例模式在多线程环境中需要特别注意的问题

假设此时需要实例化的类名称为SingleObject,在进行实例化的过程中,期望的执行顺序如下:

  1. 分配内存空间
  2. 执行构造方法,初始化对象
  3. 把这个对象指向这个空间

​ 期望的执行顺序是123,但也有可能是132,如果是在单线程环境下,单例模式是没有问题的,因为至始至终都是一个线程,返回的是就只有一个实例化的对象。

​ 但如果在多线程环境下,需要注意:假如有线程A和线程B,在线程A没有执行完时,线程B进来了,读取到SingleObject不为空,即判断SingleObject已经实例化完成,线程B直接返回这个SingleObject,但线程A还没有执行完成,此时SingleObject还没有完成构造,此时就会出错,所以需要注意多线程环境下的处理,可以对SingleObject对象加volatile关键字,声明此变量为原子变量,也可以在方法加synchronized锁等…

假如有线程A和线程B,在线程A没有执行完时,线程B进来了,读取到lazyMan不为空,即判断lazyMan已经实例化完成,线程B直接返回这个lazyMan,但线程A还没有执行完成,此时lazyMan还没有完成构造,此时就会出错,所以需要注意多线程环境下的处理,可以对lazyMan对象加volatile关键字,声明此变量为原子变量,也可以在方法加synchronized锁等…

单例模式各种实现方式对比

实现方式是否Lazy初始化是否多线程安全实现难度
懒汉式
懒汉式+synchronizd
饿汉式
双检锁DCL较复杂
静态内部类一般
枚举
volatile关键字较复杂
ThreadLocal较复杂
CAS较复杂

单例模式的实现方式如下:

1、懒汉式单例,线程不安全

是否Lazy初始化是否多线程安全实现难度

描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁synchronized,所以严格意义上它并不算单例模式。

这种lazy loading(延迟加载)很明显,不要求线程安全,在多线程不能正常工作。

代码示例

public class Singleton {
    
    private static Singleton singleton;

    private Singleton() {

    }

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

往下的几种实现方式都支持多线程,但是在性能上有所差异

2、懒汉式单例,线程安全

是否Lazy初始化是否多线程安全实现难度

描述:这种方式具备很好的lazy loading(延迟加载),能够在多线程中很好的工作,但是,效率很低,99%情况下不需要同步。

优点:第一次调用才初始化,避免内存浪费

缺点:必须加锁synchronized才能保证单例,但加锁会影响效率。

getInstance()的性能对应用程序不是很关键(该方法使用不太频繁)。

代码示例

public class Singleton {

    private static Singleton singleton;

    private Singleton() {

    }

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

3、饿汉式单例

是否Lazy初始化是否多线程安全实现难度

描述:这种方式比较常用,但容易产生垃圾对象。

优点:没有加锁,执行效率会提高。

缺点:类加载时就初始化,浪费内存。

它基于classloader(类加载器)机制避免了多线程的同步问题,不过,singleton在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法,但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化singleton显然没有达到lazy loading(延迟加载)的效果。

代码示例

public class Singleton {

    private static Singleton singleton = new Singleton();
    
    private Singleton() {}

    public static Singleton getInstance() {
        return singleton;
    }
}

4、双检锁/双重校验锁(DCL,即double-checked locking)

JDK版本JDK1.5起

是否Lazy初始化是否多线程安全实现难度
较复杂

描述:这种方式采用双锁机制,安全且在多线程情况下保持高性能。

getInstance()的性能对应用程序很关键。

优点:线程安全;延迟加载;效率较高。

缺点JVM编译器的指令重排导致单例出现漏洞。

代码示例

public class Singleton {

    private static Singleton singleton;

    private Singleton() {

    }

    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

​ 在该方法中对instance进行了两次判空:第一层判断为了避免不必要的同步,第二层判断则是为了在null的情况下创建实例。对第六种单例的漏洞进行了弥补,但是还是有点小问题的,问题就在instance = new Singleton();语句上。

这语句在这里看起来是一句代码,但实际上它并不是一个原子操作,这句代码最终会被编译成多条汇编指令,它大致做了3件事情:

  1. 给Singleton的实例分配内存
  2. 调用Singleton()的 构造函数,初始化成员字段
  3. 将instance对象指向分配的内存空间(此时instance就不是null了)

​ 但是,由于Java编译器运行处理器乱序执行,以及jdk1.5之前Java内存模型中Cache、寄存器到主内存会写顺序的规定,上面的第二和第三的顺序是无法保证的。也就是说,执行顺序可能是1-2-3也可能是1-3-2.如果是后者,并且在3执行完毕、2未执行之前,被切换到线程B上,这时候instance因为已经在线程A内执行3了,instance已经是非null,所有线程B直接取走instance,再使用时就会出错,这就是DCL失效问题,而且这种难以跟踪难以重现的问题很可能会隐藏很久。

5、登记式/静态内部类

是否Lazy初始化是否多线程安全实现难度
一般

描述:这种方式能达到双检锁方式一样的效果,但实现简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。

​ 这种方式同样利用了classloader机制来保证初始化instance时只有一个线程,它跟3种方式不同的是:第3种方式只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果),而这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,所以想让它延迟加载,另外一方面,又不希望在Singleton类加载时就实例化,因为不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。这个时候,这种方式相比第3种方式就显得很合理。

代码示例:

public class Singleton {

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    private Singleton() {
        
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

6、枚举

JDK版本JDK1.5起

是否Lazy初始化是否多线程安全实现难度

描述:这种实现方式还没有被广泛使用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。

​ 这种方式不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于JDK1.5之后才加入enum特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少使用。不能通过reflection attack来调用私有构造方法。使用时通过Singleton.INSTANCE来访问实例即可。

代码示例

public enum Singleton{
    INSTANCE;
}

7、volatile关键字

是否Lazy初始化是否多线程安全实现难度
较复杂

描述:对于Double-Check这种可能出现的问题(当然这种概率已经非常小了,但还是有的),解决方案是:只需要给instance的声明加上volatile关键字即可volatile关键字的一个作用是禁止指令重排,把instance声明为volatile之后,对它的写操作就会有一个内存屏障,这样,在它的赋值完成之前,就不会调用读操作。注意:volatile阻止的不是singleton=new Singleton()这句话内部[1-2-3]的指令重排,而是保证了在一个写操作([1-2-3])完成之前,不会调用读操作(if(instance==null))。

代码示例

public class Singleton {

    private static volatile Singleton instance = null;

    private Singleton() {

    }

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

8、使用ThreadLocal

是否Lazy初始化是否多线程安全实现难度
较复杂

描述ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

代码示例

public class Singleton {

    private static final ThreadLocal<Singleton> threadLocalSingleton = new ThreadLocal<>() {
        @Override
        protected Singleton initialValue() {
            return new Singleton();
        }
    };

    private Singleton() {
        
    }

    public static Singleton getInstance() {
        return threadLocalSingleton.get();
    }
}

9、使用CAS

是否Lazy初始化是否多线程安全实现难度
较复杂

代码示例:

public class Singleton {

    private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<Singleton>();

    private Singleton() {
        
    }

    public static Singleton getInstance() {
        for (; ; ) {
            Singleton current = INSTANCE.get();
            if (current != null) {
                return current;
            }
            current = new Singleton();
            if (INSTANCE.compareAndSet(null, current)) {
                return current;
            }
        }
    }
}

**补充:**一般情况下,不建议使用第1种和第2种懒汉方式,建议使用第3种饿汉方式。只有在明确实现lazy loading效果时,才会使用第5种登记方式。如果涉及到反序列化创建对象时,可以尝试使用第6种枚举方式。如果没有其他特殊的需求,可以考虑使用第4种双检锁方式。

建造者模式

建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。建造者模式也属于创建型模式,它提供了一种创建对象的最佳方式。一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。

介绍

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

主要作用:在用户不知道对象的建造过程和细节的情况下就可以直接创建复杂的对象

何时使用:一些基本部件不会变,而其组合经常变化的时候。

**如何解决:**将变与不变分离开。

**关键代码:**建造者:创建和提供实例,导演:管理建造出来的实例的依赖关系。

应用实例

  • 工厂(建造者模式):负责制造汽车(组装过程和细节在工厂内)
  • 汽车购买者(用户):你只需要说出你需要的型号(对象的类型和内容),然后直接购买就可以使用了(不需要知道汽车是怎么组装的(车轮、 车门、发动机、方向盘等等)。
  • 去肯德基,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的"套餐"
  • Java中的StringBuilder

优点

  • 产品的建造和表示分离,实现了解耦。使用建造者模式可以使客户端不必知道产品内部组成的细节,便于控制细节风险
  • 建造者独立,易扩展将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰。
  • 建造者独立,易扩展。具体的建造者类之间是相互独立的,这有利于系统的扩展。增加新的具体建造者无需修改原有类库的代码,符合“开闭原则”。

缺点

  • 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似;如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
  • 如果产品的内部变化复杂,可能会导致需要定义很大具体建造者类来实现这种变化,导致系统变得很庞大。

使用场景

  • 需要生成的产品对象有复杂的内部结构,这些产品对象具备共性。

  • 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。

  • 适合于一个具有较多的零件(属性)的产品(对象)的创建过程。

  • 需要生成的对象内部属性本身相互依赖。

建造者与抽象工厂模式的比较

  • 建造者模式更加关注与零件装配的顺序

  • 与抽象工厂模式相比,建造模式返回一个组装好的完整产品,而抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族。

  • 在抽象工厂模式中,客户端实例化工厂类,然后调用工厂方法获取所需产品对象,而建造者模式中,客户端可以不直接调用建造者的相关方法,而是通过指挥者类来指导如何生成对象,包括对象的组装过程和建造步骤,它侧重于一步步构造一个复杂对象,返回一个完整的对象。

  • 如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件得组装可以返回一辆完整得汽车!

实现

  • 下图示例是Builder模式的常规用法,导演类Director在Builder模式中具有很重要的作用,它用于指定具体构建者如何构建产品,控制调用先后次序,并向调用者返回完整的产品类,但是有些情况下需要简化系统结构,可以把Director和抽象建造者进行结合。

    image-20200819215307533

  • 通过静态内部类方式实现零件无序装配构造,这种方式使用更加灵活,更符合定义。内部有复杂对象的默认实现,使用时可以根据用户需求自由定义更改内容,并且无需改变具体的构造方式。就可以生产出不同复杂产品

  • 比如:比如麦当劳的套餐,服务员(具体建造者)可以随意搭配任意几种产品(零件)组成一款套餐(产品),然后出售给客户。比第一种方式少了指挥者,主要是因为第二种方式把指挥者交给用户来操作,使得产品的创建更加简单灵活。

  • 假设造房简化为:以下步骤:(1)地基(2)钢筋工程(3)铺电线(4)粉刷;“如果”要盖一座房子,首先要找一个建筑公司或工程承包商(指挥者)。承包商指挥工人(具体建造者)过来造房子(产品),最后验收。

    image-20200819220803693

步骤一

产品:Product.java

//产品:房子
public class Product {

    private String buildA;
    private String buildB;
    private String buildC;
    private String buildD;

    public String getBuildA() {
        return buildA;
    }

    public void setBuildA(String buildA) {
        this.buildA = buildA;
    }

    public String getBuildB() {
        return buildB;
    }

    public void setBuildB(String buildB) {
        this.buildB = buildB;
    }

    public String getBuildC() {
        return buildC;
    }

    public void setBuildC(String buildC) {
        this.buildC = buildC;
    }

    public String getBuildD() {
        return buildD;
    }

    public void setBuildD(String buildD) {
        this.buildD = buildD;
    }

    @Override
    public String toString() {
        return "Product{" +
                "buildA='" + buildA + '\'' +
                ", buildB='" + buildB + '\'' +
                ", buildC='" + buildC + '\'' +
                ", buildD='" + buildD + '\'' +
                '}';
    }
}

步骤二

抽象的建造者:Builder.java

public abstract class Builder {

    abstract void buildA();//地基
    abstract void buildB();//钢筋工程
    abstract void buildC();//铺电线
    abstract void buildD();//粉刷

    //完工:得到产品
    abstract Product getProduct();
}

步骤三

建造者实现:Worker.java

public class Worker extends Builder{

    private Product product;

    public Worker() {
        product = new Product();
    }

    @Override
    void buildA() {
        product.setBuildA("地基");
        System.out.println("地基");
    }

    @Override
    void buildB() {
        product.setBuildB("钢筋");
        System.out.println("钢筋");
    }

    @Override
    void buildC() {
        product.setBuildC("电线");
        System.out.println("电线");
    }

    @Override
    void buildD() {
        product.setBuildD("粉刷");
        System.out.println("粉刷");
    }

    @Override
    Product getProduct() {
        return product;
    }
}

步骤四

指挥者:Director.java

//指挥:核心。负责指挥构建一个工程,工程如何构建,由它决定
public class Director {

    //指挥工人按照顺序建房子
    public Product build(Builder builder) {
        builder.buildA();
        builder.buildB();
        builder.buildC();
        builder.buildD();
        return builder.getProduct();
    }
}

步骤五

演示建造者模式:Test.java

public class Test {

    public static void main(String[] args) {
        //指挥
        Director director = new Director();
        //指挥具体的工人完成产品
        Product product = director.build(new Worker());
        System.out.println(product.toString());
    }
}

结果

地基
钢筋
电线
粉刷
Product{buildA='地基', buildB='钢筋', buildC='电线', buildD='粉刷'}

Process finished with exit code 0

原型模式

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能,它提供了一种创建对象的最佳方式。

​ 这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。

介绍

意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

主要解决:在运行时期建立和删除原型。

何时使用

  • 当一个系统应该独立于它的产品创建,构成和表示时

  • 当要实例化的类是在运行时刻指定时,例如,通过动态装载。

  • 为了避免创建一个与产品类层次平行的工厂类层次时

  • 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。

如何解决:利用已有的一个原型对象,快速地生成和原型对象一样的实例。

关键代码

  • 实现克隆操作,在Java继承Cloneable,重写clone()来实现对象的浅拷贝或通过序列化的方式来实现深拷贝

  • 原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些“易变类”拥有稳定的接口。

应用实例:

  • 细胞分裂
  • Java中的Object clone()方法

优点

  • 性能提高
  • 逃避构造函数的约束

缺点

  • 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候
  • 必须实现Cloneable接口

使用场景

  • 资源优化场景
  • 类初始化需要消耗非常多的资源(数据、硬件资源等)
  • 性能和安全要求的场景
  • 通过new产生一个对象需要非常繁琐的数据准备或者访问权限
  • 一个对象多个修改的场景
  • 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用
  • 在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与Java融为浑然一体,可以随手拿来使用

注意事项

  • 与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现Cloneable,重写,深拷贝是通过实现Serializable读取二进制流。

  • 克隆是以某个对象为原型,创建新的对象;new是创建了新的对象。克隆出来的新对象,修改时不会影响原来的对象。克隆出来的对象和原来的对象是一样的。

  • 浅克隆:

    public class Video implements Cloneable{
    
        private String name;
        private Date createTime;
    
        public Video(String name, Date createTime) {
            this.name = name;
            this.createTime = createTime;
        }
    
        public Video() {
    
        }
    
        @Override
        public String toString() {
            return "Video{" +
                    "name='" + name + '\'' +
                    ", createTime=" + createTime +
                    '}';
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Date getCreateTime() {
            return createTime;
        }
    
        public void setCreateTime(Date createTime) {
            this.createTime = createTime;
        }
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    
    public class Client {
    
        public static void main(String[] args) throws CloneNotSupportedException {
            Video object_1 = new Video("AAA", new Date());
            System.out.println(object_1.toString());
            System.out.println("Object HashCode : "+object_1.hashCode());
    
            Video object_2 = (Video) object_1.clone();
            System.out.println(object_2.toString());
            System.out.println("Object HashCode : "+object_2.hashCode());
            System.out.println("===================");
        }
    }
    
    Video{name='AAA', createTime=Wed Aug 19 22:47:57 CST 2020}
    Object HashCode : 1836019240
    Video{name='AAA', createTime=Wed Aug 19 22:47:57 CST 2020}
    Object HashCode : 325040804
    ===================
    
    进程完成,退出码 0
    

    深克隆:

    public class Video implements Cloneable{
    
        private String name;
        private Date createTime;
    
        public Video(String name, Date createTime) {
            this.name = name;
            this.createTime = createTime;
        }
    
        public Video() {
    
        }
    
        @Override
        public String toString() {
            return "Video{" +
                    "name='" + name + '\'' +
                    ", createTime=" + createTime +
                    '}';
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Date getCreateTime() {
            return createTime;
        }
    
        public void setCreateTime(Date createTime) {
            this.createTime = createTime;
        }
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            Object object = super.clone();
            //实现深克隆
            Video video = (Video) object;
            //将这个对象的属性也进行克隆
            video.createTime = (Date) this.createTime.clone();
            return object;
        }
    }
    
    public class Client {
        public static void main(String[] args) throws CloneNotSupportedException {
            Date date = new Date();
            Video object_1 = new Video("AAA", date);
            System.out.println(object_1.toString());
            System.out.println("Object HashCode : "+object_1.hashCode());
    
            Video object_2 = (Video) object_1.clone();
            System.out.println(object_2.toString());
            System.out.println("Object HashCode : "+object_2.hashCode());
            System.out.println("===================");
            date.setTime(22131231);
            System.out.println(object_1.toString());
            System.out.println(object_2.toString());
        }
    }
    
    Video{name='AAA', createTime=Wed Aug 19 22:48:40 CST 2020}
    Object HashCode : 1836019240
    Video{name='AAA', createTime=Wed Aug 19 22:48:40 CST 2020}
    Object HashCode : 325040804
    ===================
    Video{name='AAA', createTime=Thu Jan 01 14:08:51 CST 1970}
    Video{name='AAA', createTime=Wed Aug 19 22:48:40 CST 2020}
    
    进程完成,退出码 0
    

image-20200819223549883

实现

​ 创建一个抽象类Shape和扩展了Shape类的实体类。下一步是定义类ShapeCache,该类把对象shape存储在一个HashTable中,并在请求的时候返回它们的克隆。演示类PrototypePatternDemo使用 ShapeCache 类来获取 Shape 对象。

image-20200706165019567

步骤 1
创建一个实现了 Clonable 接口的抽象类:Shape.java

public abstract class Shape implements Cloneable{

    private String id;

    protected String type;

    abstract void draw();

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getType() {
        return type;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Object clone = null;
        try {
            clone = super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return clone;
    }
}

步骤 2

创建扩展了上面抽象类的实体类:Rectangle.java

public class Rectangle extends Shape{

    public Rectangle() {
        type = "Rectangle";
    }

    @Override
    void draw() {
        System.out.println("Inside Rectangle::draw() method.");
    }
}

Square.java

public class Square extends Shape{

    public Square() {
        type = "Square";
    }

    @Override
    void draw() {
        System.out.println("Inside Square::draw() method.");
    }
}

Circle.java

public class Circle extends Shape{

    public Circle() {
        type = "Circle";
    }

    @Override
    void draw() {
        System.out.println("Inside Circle::draw() method.");
    }
}

步骤 3

创建一个类,从数据库获取实体类,并把它们存储在一个 HashTable 中:ShapeCache.java

public class ShapeCache {

    private static Hashtable<String, Shape> shapeTable = new Hashtable<>();

    public static Shape getShape(String shapeId) {
        Shape cachedShape = shapeTable.get(shapeId);
        return cachedShape;
    }

    public static void loadCache() {
        Circle circle = new Circle();
        circle.setId("1");
        shapeTable.put(circle.getId(), circle);

        Square square = new Square();
        square.setId("2");
        shapeTable.put(square.getId(), square);

        Rectangle rectangle = new Rectangle();
        rectangle.setId("3");
        shapeTable.put(rectangle.getId(), rectangle);
    }
}

步骤 4

PrototypePatternDemo使用ShapeCache类来获取存储在Hashtable 中的形状的克隆:PrototypePatternDemo.java

public class PrototypePatternDemo {

    public static void main(String[] args) {
        ShapeCache.loadCache();

        Shape clonedShape = (Shape) ShapeCache.getShape("1");
        System.out.println("Shape : " + clonedShape.getType());

        Shape clonedShape2 = (Shape) ShapeCache.getShape("2");
        System.out.println("Shape : " + clonedShape2.getType());

        Shape clonedShape3 = (Shape) ShapeCache.getShape("3");
        System.out.println("Shape : " + clonedShape3.getType());
    }

}

结果

Shape : Circle
Shape : Square
Shape : Rectangle

工厂模式

​ 工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

​ 在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

介绍

**意图:**定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。

**主要解决:**主要解决接口选择的问题。

**何时使用:**我们明确地计划不同条件下创建不同实例时。

**如何解决:**让其子类实现工厂接口,返回的也是一个抽象的产品。

**关键代码:**创建过程在其子类执行。

核心本质

  • 实例化对象不用new,用工厂方法代替
  • 将选择实现类,创建对象统一管理和控制,从而将调用者跟实现类解耦

应用实例

  • JDKCalendargetInstance方法
  • JDBC中的Connection对象的获取
  • SpringIOC容器创建管理bean对象
  • 反射中Class对象的new Instance方法
  • 您需要一辆汽车,可以直接从工厂里面提货,而不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现
  • Hibernate 换数据库只需换方言和驱动就可以。

优点:

  • 一个调用者想创建一个对象,只要知道其名称就可以了
  • 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以
  • 屏蔽产品的具体实现,调用者只关心产品的接口

缺点:

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

使用场景:

  • 日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。
  • 数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。
  • 设计一个连接服务器的框架,需要三个协议,"POP3"、"IMAP"、"HTTP",可以把这三个作为产品类,共同实现一个接口。

注意事项:

​ 作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。

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

  • 优点:简单工厂模式最大的优点在于工厂类中包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关的类,对于客户端来说,去除了与具体产品的依赖。
  • 缺点:没有遵循”开放—封闭“原则。如果要添加一个新的产品类,就必须在工厂类中添加新的代码,程序的扩展性不高。

Tips:所有产品的生产都在一个工厂中进行,添加新的产品时,需要在工厂中添加新的方法生产此产品。

image-20200625142053066

  • Creator:是简单工厂方法模式的核心,包含应用程序所需要的业务逻辑,当客户类Client需要的时候,委托工厂类创建产品类的对象(工厂类)

  • Product:可以是Java接口或者Java抽象类,是具体子类的超类或者共同接口(产品接口)

  • ConcreteProduct:实现Product接口,或者继承抽象类Product。(具体产品类)

  • 示例类图

image-20200625141347091

步骤一

创建汽车产品接口:Car.java

public interface Car {
    void name();
}

步骤二

创建汽车产品接口实现类:DaZong,Tesla,WuLing

public class DaZong implements Car{
    @Override
    public void name() {
        System.out.println("大众");
    }
}

public class Tesla implements Car{
    @Override
    public void name() {
        System.out.println("特斯拉");
    }
}

public class WuLing implements Car{
    @Override
    public void name() {
        System.out.println("五菱");
    }
}

步骤三

创建汽车工厂:CarFactory.java

public class CarFactory {

    /**
     * 方式一
     * @param car 汽车品牌
     * @return 对应汽车实例
     */
    public static Car getCar(String car) {
        if (car.equals("五菱")) {
            return new WuLing();
        } else if (car.equals("特斯拉")) {
            return new Tesla();
        } else if (car.equals("大众")) {
            return new DaZong();
        } else {
            return null;
        }
    }

    /**
     * 方式二
     * @return 对应汽车实例
     */
    public static Car getWuLing() {
        return new WuLing();
    }

    public static Car getTesla() {
        return new Tesla();
    }

    public static Car DaZong() {
        return new DaZong();
    }
}

步骤四

创建消费者:Consumer.java

public class Consumer {

    public static void main(String[] args) {
       //使用工厂创建
        Car car1 = CarFactory.getCar("五菱");
        Car car2 = CarFactory.getCar("特斯拉");
        Car car3 = CarFactory.getCar("大众");
        car1.name();
        car2.name();
        car3.name();
    }
}

结果

结果:
五菱
特斯拉
大众

进程完成,退出码 0

工厂方法模式

  • 将简单工厂方法模式中单一的工厂类改写为一个层次类。
  • 定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。用来生产同一等级结构中的固定产品(支持增加任意产品)
  • 优点:工厂方法模式是简单工厂模式的进一步抽象和推广,在不修改已有类的前提下,通过增加新的工厂类实现扩展。它遵循了”开放—封闭“原则。
  • 缺点:工厂方法模式把简单工厂模式的内部逻辑判断转移到了客户端代码来执行;每增加一产品就要增加一个产品工厂的类,增加了额外的开发量

Tips:一个产品对应一个工厂,一个工厂只生产一个产品,添加一个新的产品时,添加一个新的产品与生产该产品的工厂即可,无需在原有代码上进行修改。

  • 类图:

image-20200625145026404

  • 示例类图

image-20200625145848486

  • 示例代码:
  1. 汽车产品接口:
public interface Car {
    void name();
}
  1. 汽车产品:
public class MoBai implements Car{
    @Override
    public void name() {
        System.out.println("摩拜单车");
    }
}

public class Tesla implements Car {
    @Override
    public void name() {
        System.out.println("特斯拉");
    }
}

public class WuLing implements Car {
    @Override
    public void name() {
        System.out.println("五菱");
    }
}
  1. 汽车工厂接口:
public interface CarFactory {
    Car getCar();
}
  1. 汽车工厂:
public class MoBaiFactory implements CarFactory{
    @Override
    public MoBai getCar() {
        return new MoBai();
    }
}

public class TeslaFactory implements CarFactory{
    @Override
    public Car getCar() {
        return new Tesla();
    }
}

public class WuLingFactory implements CarFactory{
    @Override
    public Car getCar() {
        return new WuLing();
    }
}
  1. 消费者:
public class Consumer {
    public static void main(String[] args) {
        Car car1 = new WuLingFactory().getCar();
        Car car2 = new TeslaFactory().getCar();
        Car car3 = new MoBaiFactory().getCar();
        car1.name();
        car2.name();
        car3.name();
    }
}

结果:
五菱
特斯拉
摩拜单车

进程完成,退出码 0

抽象工厂模式

​ **抽象工厂模式(Abstract Factory Pattern)**是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

​ 在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。

Tips:超级工厂生产多个其他工厂,一个工厂可以生产多种产品;抽象工厂模式不可以增加产品,可以增加产品族

介绍

意图:抽象工厂模式提供了一个创建一系列相关或者相互依赖对象的接口,无需指定它们具体的类,每个生成的工厂都能按照工厂模式提供对象。

**主要解决:**主要解决接口选择的问题。

**何时使用:**系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。

**如何解决:**在一个产品族里面,定义多个产品。

**关键代码:**在一个工厂里聚合多个同类产品。

image-20200921110215121image-20200921110240414

**应用实例:**工作了,为了参加一些聚会,肯定有两套或多套衣服吧,比如说有商务装(成套,一系列具体产品)、时尚装(成套,一系列具体产品),甚至对于一个家庭来说,可能有商务女装、商务男装、时尚女装、时尚男装,这些也都是成套的,即一系列具体产品。假设一种情况(现实中是不存在的,要不然,没法进入共产主义了,但有利于说明抽象工厂模式),在您的家中,某一个衣柜(具体工厂)只能存放某一种这样的衣服(成套,一系列具体产品),每次拿这种成套的衣服时也自然要从这个衣柜中取出了。用 OOP 的思想去理解,所有的衣柜(具体工厂)都是衣柜类的(抽象工厂)某一个,而每一件成套的衣服又包括具体的上衣(某一具体产品),裤子(某一具体产品),这些具体的上衣其实也都是上衣(抽象产品),具体的裤子也都是裤子(另一个抽象产品)。

image-20200921110301273

优点

  • 具体产品在应用层的代码隔离,无需关心创建的细节
  • 当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。

缺点

  • 规定了所有可能被创建的产品的集合,产品族中扩展新的产品困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。

  • 增加了系统的抽象性和理解难度

使用场景

  • 客户端(应用层)不依赖于产品类实例如何被创建、实现等细节
  • 强调一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量的重复代码
  • 提供一个产品类的库,所有的产品以同样的接口出现,从而使得客户端不依赖于具体的实现
  • QQ 换皮肤,一整套一起换
  • 生成不同操作系统的程序

**注意事项:**产品族难扩展,产品等级易扩展。

实现

image-20200618153451123

步骤一

产品接口:IphoneProduct,IRouterProduct

//手机
public interface IphoneProduct {

    void start();

    void shutdown();

    void callup();

    void sendSMS();
}

//路由器
public interface IRouterProduct {

    void start();

    void shutdown();

    void openWifi();

    void setting();
}

步骤二

产品实现类:HuaweiPhone,HuaweiRouter,XiaomiPhone,XiaomiRouter

//华为手机
public class HuaweiPhone implements IphoneProduct {

    @Override
    public void start() {
        System.out.println("开启华为手机");
    }

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

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

    @Override
    public void sendSMS() {
        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 class XiaomiPhone implements IphoneProduct{

    @Override
    public void start() {
        System.out.println("开启小米手机");
    }

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

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

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

//小米路由
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("小米路由器设置密码");
    }
}

步骤三

工厂接口:IProductFactory.java

public interface IProductFactory {

    /**
     * 生产手机
     */
    IphoneProduct iphoneProduct();

    /**
     * 生产路由器
     */
    IRouterProduct routerProduct();
}

步骤四

工厂接口实现类:HuaweiFactory,XiaomiFactory

//华为工厂
public class HuaweiFactory implements IProductFactory{

    @Override
    public IphoneProduct iphoneProduct() {
        return new HuaweiPhone();
    }

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

//小米工厂
 public class XiaomiFactory implements IProductFactory{

    @Override
    public IphoneProduct iphoneProduct() {
        return new XiaomiPhone();
    }

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

步骤五

测试抽象工厂模式:Client.java

public class Client {

    public static void main(String[] args) {
        System.out.println("==========小米系列产品==========");
        //小米工厂
        XiaomiFactory xiaomiFactory = new XiaomiFactory();

        IphoneProduct iphoneProduct = xiaomiFactory.iphoneProduct();

        iphoneProduct.callup();
        iphoneProduct.sendSMS();

        IRouterProduct routerProduct = xiaomiFactory.routerProduct();
        routerProduct.openWifi();

        System.out.println("===========华为产品==========");
        HuaweiFactory huaweiFactory = new HuaweiFactory();

        iphoneProduct = huaweiFactory.iphoneProduct();
        routerProduct = huaweiFactory.routerProduct();
        iphoneProduct.sendSMS();
        iphoneProduct.callup();
        routerProduct.openWifi();

    }
}

结果

==========小米系列产品==========
小米手机打电话
小米手机发送短信
小米路由器开启wifi
===========华为产品==========
华为手机发送短信
华为手机打电话
华为路由器开启wifi

进程完成,退出码 0

结构型模式

代理模式

​ 在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。

​ 在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。

介绍

**意图:**为其他对象提供一种代理以控制对这个对象的访问。

**主要解决:**在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。

**何时使用:**想在访问一个类时做一些控制。

**如何解决:**增加中间层。

**关键代码:**实现与被代理类组合。

应用实例:

  • Windows里面的快捷方式
  • 猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类。
  • 买火车票不一定在火车站买,也可以去代售点。
  • 一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。
  • spring aop

image-20200618103426469

优点:

  • 职责清晰
  • 高扩展性
  • 智能化

缺点:

  • 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
  • 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

使用场景:

按职责来划分,通常有以下使用场景:

  • 远程代理
  • 虚拟代理
  • Copy-on-Write代理
  • 保护(Protect or Access)代理
  • Cache代理
  • 防火墙(Firewall)代理
  • 同步化(Synchronization)代理
  • 智能引用(Smart Reference)代理

注意事项:

  • 和适配器模式的区别:适配器模式主要考虑对象的接口,而代理模式不能改变所代理类的接口
  • 和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。

实现

静态代理

​ 静态靠一个接口类(代理对象)去实现另一个接口(真实对象)。动态靠一个类继承innovation类,然后用set或构造器方法注入真实对象,然后生成代理对象。

优点

  • 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务,让自己专注于做一件事,例:房东(真实角色)只需要出租房子,不用管其他的事情,剩下的交给中介(代理角色)去处理。
  • 公共业务交给代理角色去处理,实现了业务的分工,提高代码复用性,降低代码耦合度
  • 公共业务发生扩展的时候,方便集中管理

缺点

  • 一个真实角色就会产生一个代理角色;代码量会翻倍,开发效率会变低

角色分析

  • 抽象角色:一般会使用接口或者抽象类来解决
  • 真实角色:被代理的角色
  • 代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作
  • 客户:访问代理对象的人

image-20200618085428439

示例代码

  1. 抽象角色(接口)
//租房
public interface Rent{
    public void rent();
}
  1. 真实角色(房东)
//出租房子
public class Host implements Rent{

    @Override
    public void rent() {
        System.out.println("房东————>出租房子");
    }
}
  1. 代理角色(中介)
//代理房子
public class Proxy implements Rent{

    private Host host;

    public Proxy() {

    }

    public Proxy(Host host) {
        this.host = host;
    }

    @Override
    public void rent() {
        host.rent();
        System.out.println("代理(中介)————>帮忙找房子租给客户");
    }

    public void seeHouse() {
        System.out.println("代理(中介)————>带你看房");
    }

    public void hetong() {
        System.out.println("代理(中介)————>签租凭合同");
    }

    public void fare() {
        System.out.println("代理(中介)————>收中介费");
    }
}
  1. 客户端访问代理角色
//客户端
public class Client {

    public static void main(String[] args) {
        //房东要出租房子
        Host host = new Host();
        //代理(中介),帮房东出租房子,但是呢?代理一般会有一些附属操作,比如说收中介费
        Proxy proxy = new Proxy(host);
        //你不用面对房东,直接找中介租房即可
        proxy.rent();
    }
}
  1. 结果:
房东————>出租房子
代理(中介)————>帮忙找房子租给客户

进程完成,退出码 0

动态代理

  • 动态代理和静态代理角色一样
  • 动态代理的代理类是动态生成的,不是我们直接写好的!
  • 动态代理分为两大类:基于接口的动态代理,基于类的动态代理
    • 基于接口——JDK 动态代理
    • 基于类:cglib
    • java字节码实现:javasist

​ 动态代理需要了解两个核心类:Proxy(代理类),InvocationHandler(调用处理程序)

​ 动态代理代理类没有真实存在,代理类是动态生成的

优点

  • 一个动态代理类代理的是一个接口,一般就是对应的一类业务
  • 一个动态代理类可以代理多个类,只要实现了一个接口即可

示例代码

  1. 抽象角色(接口)(房子)
public interface Rent{
    public void rent();
}
  1. 真实角色(房东)
public class Host implements Rent {

    @Override
    public void rent() {
        System.out.println("房东————>出租房子");
    }
}
  1. 代理角色即动态代理类(中介)
public class ProxyInvocationHandler implements InvocationHandler {

    /**
     * 被代理的接口
     */
    private Rent rent;

    public void setRent(Rent rent) {
        this.rent = rent;
    }

    /**
     * 生成得到代理类
     * @return
     */
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                rent.getClass().getInterfaces(),this);
    }

    /**
     * 处理代理实例,并返回结果
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        seeHouse();
        //动态代理的本质就是使用反射机制实现
        Object result = method.invoke(rent, args);
        fare();
        return result;
    }

    public void seeHouse() {
        System.out.println("代理(中介————>中介带客户看房子");
    }

    public void fare() {
        System.out.println("代理(中介)————>中介收取代理费");
    }
}
  1. 客户
public class Client {

    public static void main(String[] args) {
        //真实角色
        Host host = new Host();

        //代理角色:现在还没有
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        //通过调用程序处理角色来处理我们要调用的接口对象
        pih.setRent(host);
        //获得代理对象
        Rent proxy = (Rent) pih.getProxy();

        proxy.rent();
    }
}

结果

代理(中介————>中介带客户看房子
房东————>出租房子
代理(中介)————>中介收取代理费

进程完成,退出码 0

适配器模式

适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。它结合了两个独立接口的功能。这种模式设计到一个单一的类,该类负责加入独立的或不兼容的接口功能。

介绍

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

主要解决:主要解决在软件系统中,常常要将一些“现存的对象”放到新的环境中,而新环境要求的接口是现对象不能满足的。

何时使用

  • 系统需要使用现有的类,而此类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码。

  • 想要建立一个可以重复使用的类,用于与一些彼此之间没有台太大关联关系的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口

  • 通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口)。

如何解决:继承或依赖(推荐)

关键代码:适配器继承或依赖已有的对象,实现想要的目标接口。

应用实例

  • 美国电器110V,中国220V,就要有一个适配器将110V转换为220V

  • Java JDK 1.1提供了Enumeration接口,而在1.2中提供了Iterator接口,想要使用1.2的JDk,则要将以前系统的Enumeration接口转换为Iterator接口,这时就需要适配器模式

  • Linux上运行Windows程序

  • Java中的JDBC

  • 读卡器是作为内存卡和笔记本之间的适配器。将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡。

  • 音频播放器设备只能播放MP3文件,通过使用一个更高级的音频播放器来播放vlcMP4文件。

  • USB网线转换器。

    image-20200921110435406

优点:

  • 一个对象适配器可以把多个不同的适配者适配到同一个目标
  • 可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据“里氏代换原则”,适配者的子类也可以通过该适配器进行适配。
  • 可以让任何两个没有关联的子类一起运行
  • 提高了类的复用
  • 增加了类的透明度
  • 灵活性好

缺点

  • 对于Java、C#等不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者,而目标类必须是抽象类
  • 在Java、C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性
  • 过多地使用适配器,会让系统非常凌乱,不易整体进行把握。比如,明明看到调用的是A接口,其实内部被适配成了B接口的实现,一个系统如果出现太对这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构

使用场景

  • 有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式。

注意事项

  • 适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。

实现

​ 将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作。目标接口:客户所期待的接口,目标可以是具体的或抽象的类,也可以是接口(USB接口)。需要适配的类:需要适配的类或者适配者类(网线接口)。适配器:通过包装一个需要适配的对象,把原接口转换成目标对象(USB转换器,包装网线)。

image-20200819214227108

步骤一

USB接口:NetToUsb.java

public interface NetToUsb {
    //作用:处理请求,把网线插到usb上
    public void handleRequest();
}

步骤二

转换器(类适配器):Adapter.java

public class Adapter extends Cable implements NetToUsb{

    @Override
    public void handleRequest() {
        //可以上网了
        super.request();
    }
}

或者对象适配器:Adapter2.java

public class Adapter2 extends Cable implements NetToUsb{

    private Cable cable;

    public Adapter2(Cable cable) {
        this.cable = cable;
    }

    @Override
    public void handleRequest() {
        //可以上网了
        cable.request();
    }
}

步骤三

网线:Cable.java

public class Cable {
    public void request() {
        System.out.println("连接网线上网");
    }
}

步骤四

电脑:Computer.java

public class Computer {

    //我们的电脑需要连接上转接器才能上网
    public void net(NetToUsb netToUsb) {
        //上网的具体实现,找一个转接头
        netToUsb.handleRequest();
    }

    public static void main(String[] args) {
        //电脑
        Computer computer = new Computer();
        //网线
        Cable cable = new Cable();
        //将网线插入转接器
        Adapter2 adapter = new Adapter2(cable);
        //将转接器插入电脑,开始上网
        computer.net(adapter);
        //或者
        //将网线插入转接器
        //Adapter adapter = new Adapter();
        //将转接器插入电脑,开始上网
        //computer.net(adapter);
    }
}

结果

连接网线上网

Process finished with exit code 0

桥接模式

桥接模式是将抽象部分与它的实现部分分离,使它们都可以独立地变化。这种类型的设计模式属于结构型模式,又称为柄体模式(Handle and Body)或接口模式(Interface),它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。

​ 这种模式设计到一个作为桥接的接口,使得实体类的功能独立于接口实现类。这两种类型的类可被结构化改变而互不影响。

介绍

定义:将抽象部分与实现部分分离,使它们都可以独立的变化

主要解决:在有多种可能会变化的情况下,用继承会造成类爆炸问题,扩展起来不灵活。

何时使用:实现系统可能有多个角度分类,每一种角度都可能变化。

如何解决:把这种多角度分类分离出来,让它们独立变化,减少它们之间的耦合。

关键代码:抽象类依赖实现类。

应用实例

  • Java语言通过Java虚拟机实现了平台的无关系
  • AWT中的Peer架构
  • JDBC驱动程序也是桥接模式的应用之一
  • 猪八戒从天蓬元帅转世投胎到猪,转世投胎的机制将尘世划分为两个等级,即:灵魂和肉体,前者相当于抽象化,后者相当于实现化。生灵通过功能的委派,调用肉体对象的功能,使得生灵可以动态地选择。
  • 墙上的开关,可以看到的开关是抽象的,不用管里面具体怎么实现的。

优点

  • 桥接模式偶尔类似于多继承方案,但是多继承方案违背了类的单一职责原则,复用性比较差,类的个数也非常多,桥接模式是比多继承方案更好的解决方法。极大的减少了子类的个数,从而降低管理和维护的成本。
  • 优秀的扩展能力,桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。符合开闭原则,就像一座桥,可以把两个变化的维度连接起来。
  • 抽象与实现的分离
  • 实现细节对客户透明

缺点

  • 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
  • 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。

使用场景

  • 如果一个系统需要在构建的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
  • 一个类存在两个独立变化的维度,且这两个维度都需进行扩展。
  • 虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立关联这两者。对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。

image-20200819210108661

注意事项:

  • 对于两个独立变化的维度,使用桥接模式再适合不过了。

实现

​ 创建作为桥接实现的Brand接口和实现了Brand接口的实体类 LenovoAppleComputer是一个抽象类,将使用 Brand的对象。演示类Test使用 Computer类来组合不同品牌的电脑。

image-20200819212323279

步骤一

创建桥接实现接口:Brand.java

//品牌
public interface Brand {
    void info();
}

步骤二

创建实现了Brand接口的实体桥接实现类:Lenovo.java

//联想品牌
public class Lenovo implements Brand{

    @Override
    public void info() {
        System.out.print("联想品牌");
    }
}

Apple.java

public class Apple implements Brand {

    @Override
    public void info() {
        System.out.print("苹果品牌");
    }
}

步骤三

使用Brand接口创建抽象类ComputerComputer.java

//抽象的电脑类型类
public abstract class Computer {

    //组合,品牌 桥
    protected Brand brand;

    public Computer(Brand brand) {
        this.brand = brand;
    }

    public void info() {
        //自带品牌
        brand.info();
    }
}

步骤四

创建实现了Computer接口的实体类:Desktop.java

public class Desktop extends Computer {

    public Desktop(Brand brand) {
        super(brand);
    }

    @Override
    public void info() {
        super.info();
        System.out.println("台式机");
    }
}

Laptop.java

public class Laptop extends Computer {

    public Laptop(Brand brand) {
        super(brand);
    }

    @Override
    public void info() {
        super.info();
        System.out.println("笔记本");
    }
}

步骤五

使用Computer和Brand组合不同品牌的电脑:Test.java

public class Test {

    public static void main(String[] args) {
        //苹果笔记本
        Computer computer = new Laptop(new Apple());
        computer.info();
        //联想台式机
        Computer computer1 = new Desktop(new Lenovo());
        computer1.info();
    }
}

结果

苹果品牌笔记本
联想品牌台式机

进程完成,退出码 0

过滤器模式

过滤器模式(Filter Pattern)标准模式(Criteria Pattern)是一种设计模式,这种模式允许开发人员使用不同的标准来过滤一组对象,通过逻辑运算以解耦的方式把它们连接起来。这种类型的设计模式属于结构型模式,它结合多个标准来获得单一标准。

实现

​ 创建一个Person对象、Criteria接口和实现了该接口的实体类,来过滤Person对象的列表。CriteriaPatternDemo,演示类使用Criteria对象,基于各种标准和它们的结合来过滤Person对象的列表。

image-20200816092013145

步骤一

创建一个类,在该类上应用标准:Person.java

public class Person {

    private String name;
    private String gender;
    private String maritalStatus;

    public Person(String name, String gender, String maritalStatus) {
        this.name = name;
        this.gender = gender;
        this.maritalStatus = maritalStatus;
    }

    public String getName() {
        return name;
    }

    public String getGender() {
        return gender;
    }

    public String getMaritalStatus() {
        return maritalStatus;
    }
}

步骤二

为标准(Criteria)创建一个接口:Criteria.java

import java.util.List;

public interface Criteria {
    public List<Person> meetCriteria(List<Person> persons);
}

步骤三
创建实现了Criteria接口的实体类:CriteriaMale.java

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

public class CriteriaMale implements Criteria {

    @Override
    public List<Person> meetCriteria(List<Person> persons) {
        List<Person> malePersons = new ArrayList<Person>();
        for (Person person : persons) {
            if(person.getGender().equalsIgnoreCase("MALE")){
                malePersons.add(person);
            }
        }
        return malePersons;
    }
}

CriteriaFemale.java

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

public class CriteriaFemale implements Criteria {

    @Override
    public List<Person> meetCriteria(List<Person> persons) {
        List<Person> femalePersons = new ArrayList<Person>();
        for (Person person : persons) {
            if(person.getGender().equalsIgnoreCase("FEMALE")){
                femalePersons.add(person);
            }
        }
        return femalePersons;
    }
}

CriteriaSingle.java

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

public class CriteriaSingle implements Criteria {

    @Override
    public List<Person> meetCriteria(List<Person> persons) {
        List<Person> singlePersons = new ArrayList<Person>();
        for (Person person : persons) {
            if(person.getMaritalStatus().equalsIgnoreCase("SINGLE")){
                singlePersons.add(person);
            }
        }
        return singlePersons;
    }
}

AndCriteria.java

import java.util.List;

public class AndCriteria implements Criteria {

    private Criteria criteria;
    private Criteria otherCriteria;

    public AndCriteria(Criteria criteria, Criteria otherCriteria) {
        this.criteria = criteria;
        this.otherCriteria = otherCriteria;
    }

    @Override
    public List<Person> meetCriteria(List<Person> persons) {
        List<Person> firstCriteriaPersons = criteria.meetCriteria(persons);
        return otherCriteria.meetCriteria(firstCriteriaPersons);
    }
}

OrCriteria.java

import java.util.List;

public class OrCriteria implements Criteria {

    private Criteria criteria;
    private Criteria otherCriteria;

    public OrCriteria(Criteria criteria, Criteria otherCriteria) {
        this.criteria = criteria;
        this.otherCriteria = otherCriteria;
    }

    @Override
    public List<Person> meetCriteria(List<Person> persons) {
        List<Person> firstCriteriaItems = criteria.meetCriteria(persons);
        List<Person> otherCriteriaItems = otherCriteria.meetCriteria(persons);

        for (Person person : otherCriteriaItems) {
            if(!firstCriteriaItems.contains(person)){
                firstCriteriaItems.add(person);
            }
        }
        return firstCriteriaItems;
    }
}

步骤四
使用不同的标准(Criteria)和它们的结合来过滤Person对象的列表:CriteriaPatternDemo.java

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

public class CriteriaPatternDemo {
    public static void main(String[] args) {
        List<Person> persons = new ArrayList<Person>();

        persons.add(new Person("Robert","Male", "Single"));
        persons.add(new Person("John","Male", "Married"));
        persons.add(new Person("Laura","Female", "Married"));
        persons.add(new Person("Diana","Female", "Single"));
        persons.add(new Person("Mike","Male", "Single"));
        persons.add(new Person("Bobby","Male", "Single"));

        Criteria male = new CriteriaMale();
        Criteria female = new CriteriaFemale();
        Criteria single = new CriteriaSingle();
        Criteria singleMale = new AndCriteria(single, male);
        Criteria singleOrFemale = new OrCriteria(single, female);

        System.out.println("Males: ");
        printPersons(male.meetCriteria(persons));

        System.out.println("\nFemales: ");
        printPersons(female.meetCriteria(persons));

        System.out.println("\nSingle Males: ");
        printPersons(singleMale.meetCriteria(persons));

        System.out.println("\nSingle Or Females: ");
        printPersons(singleOrFemale.meetCriteria(persons));
    }

    public static void printPersons(List<Person> persons){
        for (Person person : persons) {
            System.out.println("Person : [ Name : " + person.getName()
                    +", Gender : " + person.getGender()
                    +", Marital Status : " + person.getMaritalStatus()
                    +" ]");
        }
    }
}

结果

Males: 
Person : [ Name : Robert, Gender : Male, Marital Status : Single ]
Person : [ Name : John, Gender : Male, Marital Status : Married ]
Person : [ Name : Mike, Gender : Male, Marital Status : Single ]
Person : [ Name : Bobby, Gender : Male, Marital Status : Single ]

Females: 
Person : [ Name : Laura, Gender : Female, Marital Status : Married ]
Person : [ Name : Diana, Gender : Female, Marital Status : Single ]

Single Males: 
Person : [ Name : Robert, Gender : Male, Marital Status : Single ]
Person : [ Name : Mike, Gender : Male, Marital Status : Single ]
Person : [ Name : Bobby, Gender : Male, Marital Status : Single ]

Single Or Females: 
Person : [ Name : Robert, Gender : Male, Marital Status : Single ]
Person : [ Name : Diana, Gender : Female, Marital Status : Single ]
Person : [ Name : Mike, Gender : Male, Marital Status : Single ]
Person : [ Name : Bobby, Gender : Male, Marital Status : Single ]
Person : [ Name : Laura, Gender : Female, Marital Status : Married ]

进程完成,退出码 0

外观模式

​ **外观模式(Facade Pattern)**隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。

​ 这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用。

介绍
**意图:**为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

主要解决:降低访问复杂系统的内部子系统时的复杂度,简化客户端与之的接口。

何时使用:

  • 客户端不需要知道系统内部的复杂联系,整个系统只需要提供一个“接待员”即可。
  • 定义系统的入口。

**如何解决:**客户端不与系统耦合,外观类与系统耦合。

**关键代码:**在客户端和复杂系统之间再加一层,这一层将调用顺序、依赖关系等处理好。

应用实例:

  • 去医院看病,可能要去挂号、门诊、划价、取药,让患者或患者家属觉得很复杂,如果有提供接待人员,只让接待人员来处理,就很方便。
  • Java三层开发模式。

优点:

  • 减少系统相互依赖。
  • 提高灵活性。
  • 提高了安全性。

缺点:

  • 不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。

使用场景:

  • 为复杂的模块或子系统提供外界访问的模块。
  • 子系统相对独立。
  • 预防低水平人员带来的风险。

注意事项:

  • 在层次化结构中,可以使用外观模式定义系统中每一层的入口。

实现

​ 创建一个Shape接口和实现了Shape接口的实体类。下一步时定义一个外观类ShapeMakerShapeMaker类使用实体类来代表用户对这些类的调用。演示类FacadePatternDemo使用ShapeMaker类来显示结果。

image-20200816111639929

步骤一

创建一个接口:Shape.java

public interface Shape {
    void draw();
}

步骤二
创建实现接口的实体类:Rectangle.java

public class Rectangle implements Shape{

    @Override
    public void draw() {
        System.out.println("Rectangle::draw()");
    }
}

Square.java

public class Square implements Shape {

    @Override
    public void draw() {
        System.out.println("Square::draw()");
    }
}

Circle.java

public class Circle implements Shape{

    @Override
    public void draw() {
        System.out.println("Circle::draw()");
    }
}

步骤三
创建一个外观类:ShapeMaker.java

public class ShapeMaker {
    private Shape circle;
    private Shape rectangle;
    private Shape square;

    public ShapeMaker() {
        circle = new Circle();
        rectangle = new Rectangle();
        square = new Square();
    }

    public void drawCircle() {
        circle.draw();
    }

    public void drawRectangle() {
        rectangle.draw();
    }

    public void drawSquare() {
        square.draw();
    }
}

步骤四
使用该外观类画出各种类型的形状:FacadePatternDemo.java

public class FacadePatternDemo {
    public static void main(String[] args) {
        ShapeMaker shapeMaker = new ShapeMaker();

        shapeMaker.drawCircle();
        shapeMaker.drawRectangle();
        shapeMaker.drawSquare();
    }
}

结果

Circle::draw()
Rectangle::draw()
Square::draw()

进程完成,退出码 0

享元模式

​ **享元模式(Flyweight Pattern)**主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。

​ 享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。通过创建5个对象来画出20个分布于不同位置的圆来演示这种模式。由于只有5种可用的颜色,所以color属性被用来检查现有的Circle对象。

介绍
**意图:**运用共享技术有效地支持大量细粒度的对象。

**主要解决:**在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。

何时使用:

  • 系统中有大量对象。
  • 这些对象消耗大量内存。
  • 这些对象的状态大部分可以外部化。
  • 这些对象可以按照内蕴状态分为很多组,当把外蕴对象从对象中剔除出来时,每一组对象都可以用一个对象代替。
  • 系统不依赖于这些对象身份,这些对象是不可分辨的。

**如何解决:**用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象。

**关键代码:**用HashMap存储这些对象。

应用实例:

  • Java中的String,如果有则返回,如果没有则创建一个字符串保存在字符串缓存池里面。
  • 数据库的连接池。

优点:

  • 大大减少对象的创建,降低系统的内存,使效率提高。

缺点:

  • 提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。

使用场景:

  • 系统有大量相似对象。
  • 需要缓冲池的场景

注意事项:

  • 注意划分外部状态和内部状态,否则可能会引起线程安全问题。

  • 这些类必须有一个工厂对象加以控制。

实现
创建一个Shape接口和实现了Shape接口的实体类Circle。下一步使定义工厂类ShapeFactoryShapeFactory有一个CircleHashMap,其中键名为Circle对象的颜色。无论何时接受到请求,都会创建一个特定颜色的圆。ShapeFactory检查它的HashMap中的circle对象,如果找到Circle对象,则返回该对象,否则将创建一个存储在hashmap中以备后续使用的新对象,并把该对象返回到客户端。

​ 演示类FlyWeightPatternDemo使用ShapeFactory来获取Shape对象。它将向ShapeFactory传递信息(red/green/blue/black/white),以便获取它所需对象的颜色。

image-20200816125206323

步骤一
创建一个接口:Shape.java

public interface Shape {
    void draw();
}

步骤二
创建实现接口的实体类:Circle.java

public class Circle implements Shape{
    private String color;
    private int x;
    private int y;
    private int radius;

    public Circle(String color) {
        this.color = color;
    }

    public void setX(int x) {
        this.x = x;
    }

    public void setY(int y) {
        this.y = y;
    }

    public void setRadius(int radius) {
        this.radius = radius;
    }

    @Override
    public void draw() {
        System.out.println("Circle: Draw() [Color : "+color
        +", x : "+x+", y :"+y+", radius :"+radius);
    }
}

步骤三
创建一个工厂,生成基于给定信息的实体类的对象:ShapeFactory.java

public class ShapeFactory {
    private static final HashMap<String, Shape> circleMap = new HashMap<>();

    public static Shape getCircle(String color) {
        Circle circle = (Circle) circleMap.get(color);

        if (circle == null) {
            circle = new Circle(color);
            circleMap.put(color, circle);
            System.out.println("Creating circle of color : "+color);
        }
        return circle;
    }
}

步骤四
使用该工厂,通过传递颜色信息来获取实体类的对象:FlyweightPatternDemo.java

public class FlyweightPatternDemo {
    private static final String colors[] =
            {"Red", "Green", "Blue", "White", "Black"};

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            Circle circle =
                    (Circle) ShapeFactory.getCircle(getRandomColor());
            circle.setX(getRandomX());
            circle.setY(getRandomY());
            circle.setRadius(100);
            circle.draw();
        }
    }

    private static String getRandomColor() {
        return colors[(int) (Math.random() * colors.length)];
    }

    private static int getRandomX() {
        return (int)(Math.random() * 100);
    }

    private static int getRandomY() {
        return (int) (Math.random() * 100);
    }
}

结果

Creating circle of color : Black
Circle: Draw() [Color : Black, x : 13, y :78, radius :100
Circle: Draw() [Color : Black, x : 61, y :52, radius :100
Creating circle of color : White
Circle: Draw() [Color : White, x : 51, y :59, radius :100
Circle: Draw() [Color : White, x : 51, y :34, radius :100
Circle: Draw() [Color : White, x : 67, y :54, radius :100
Circle: Draw() [Color : White, x : 32, y :27, radius :100
Creating circle of color : Green
Circle: Draw() [Color : Green, x : 42, y :94, radius :100
Creating circle of color : Blue
Circle: Draw() [Color : Blue, x : 60, y :11, radius :100
Creating circle of color : Red
Circle: Draw() [Color : Red, x : 5, y :46, radius :100
Circle: Draw() [Color : Green, x : 60, y :72, radius :100
Circle: Draw() [Color : Green, x : 59, y :57, radius :100
Circle: Draw() [Color : Green, x : 83, y :89, radius :100
Circle: Draw() [Color : White, x : 40, y :7, radius :100
Circle: Draw() [Color : Green, x : 85, y :27, radius :100
Circle: Draw() [Color : Green, x : 93, y :14, radius :100
Circle: Draw() [Color : White, x : 85, y :87, radius :100
Circle: Draw() [Color : Green, x : 92, y :51, radius :100
Circle: Draw() [Color : Black, x : 3, y :43, radius :100
Circle: Draw() [Color : Green, x : 56, y :66, radius :100
Circle: Draw() [Color : Green, x : 76, y :21, radius :100

进程完成,退出码 0

装饰器模式

​ **装饰器模式(Decorator Pattern)**允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包类。

​ 这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。通过下面的实例来演示装饰器模式的用法。其中,将把一个形状装饰上不同的颜色,同时又不改变形状类。

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

**主要解决:**一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。

**何时使用:**在不想增加很多子类的情况下扩展类。

**如何解决:**将具体功能职责划分,同时继承装饰者模式。

关键代码:

  • Component类充当抽象角色,不应该具体实现。
  • 修饰类引用和继承Component类,具体扩展类重写父类方法。

应用实例:

  • 孙悟空有72变,当他变成“庙宇”后,他的根本还是一只猴子,但是他又有了庙宇的功能。
  • 不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。在挂在墙上之前,画可以被蒙上玻璃,装到框子里;这时画、玻璃和画框形成了一个物体。

优点:

  • 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

缺点:

  • 多层装饰比较复杂。

使用场景:

  • 扩展一个类的功能。
  • 动态增加功能,动态撤销。

注意事项:

  • 可代替继承。

实现

​ 创建一个Shape接口和实现了Shape接口的实体类。然后创建一个实现了Shape接口的抽象装饰类ShapeDecorator,并把Shape对象作为它的实例变量。RedShapeDecorator是实现了ShapeDecorator的实体类。演示类DecoratorPatternDemo使用RedShapeDecorator来装饰Shape对象。

image-20200816152222783

步骤一

创建一个接口:Shape.java

public interface Shape {
    void draw();
}

步骤二
创建实现接口的实体类:Rectangle.java

public class Rectangle implements Shape{

    @Override
    public void draw() {
        System.out.println("Shape: Rectangle");
    }
}

Circle.java

public class Circle implements Shape{

    @Override
    public void draw() {
        System.out.println("Shape: Circle");
    }
}

步骤三
创建实现了Shape接口的抽象装饰类:ShapeDecorator.java

public class ShapeDecorator implements Shape{
    protected Shape decoratedShape;

    public ShapeDecorator(Shape decoratedShape) {
        this.decoratedShape = decoratedShape;
    }

    @Override
    public void draw() {
        decoratedShape.draw();
    }
}

步骤四
创建扩展了ShapeDecorator类的实体装饰类:RedShapeDecorator.java

public class RedShapeDecorator extends ShapeDecorator{

    public RedShapeDecorator(Shape decoratedShape) {
        super(decoratedShape);
    }

    @Override
    public void draw() {
        decoratedShape.draw();
        setRedBorder(decoratedShape);
    }

    private void setRedBorder(Shape decoratedShape) {
        System.out.println("Border Color: Red");
    }
}

步骤五
使用RedShapeDecorator来装饰Shape对象:DecoratorPatternDemo.java

public class DecoratorPatternDemo {
    public static void main(String[] args) {

        Shape circle = new Circle();
        ShapeDecorator redCircle = new RedShapeDecorator(new Circle());
        ShapeDecorator redRectangle = new RedShapeDecorator(new Rectangle());
        System.out.println("Circle with normal border");
        circle.draw();

        System.out.println("\nCircle of red border");
        redCircle.draw();

        System.out.println("\nRectangle of red border");
        redRectangle.draw();
    }
}

结果:

Circle with normal border
Shape: Circle

Circle of red border
Shape: Circle
Border Color: Red

Rectangle of red border
Shape: Rectangle
Border Color: Red

进程完成,退出码 0

组合模式

组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。

​ 这种模式创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式。通过下面的实例来演示组合模式的用法。实例演示了一个组织中员工的层次结构。

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

**主要解决:**它在我们树形结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以像处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。

何时使用:

  • 您想表示对象的部分-整体层次结构(树形结构)。
  • 您希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。

如何解决:

  • 树枝和叶子实现统一接口,树枝内部组合该接口。

关键代码:

  • 树枝内部组合该接口,并且含有内部属性List,里面放Component。

应用实例:

  • 在算术表达式包括操作数、操作符和另一个操作数,其中,另一个操作符也可以是操作数、操作符和另一个操作数。
  • JAVA AWTSWING中,对于ButtonCheckbox是树叶,Container是树枝。

优点:

  • 高层模块调用简单。
  • 节点自由增加。

缺点:

  • 在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。

使用场景:

  • 部分、整体场景,如树形菜单,文件、文件夹的管理。

注意事项:

  • 定义时为具体类。

实现

Employee类被当作组合模型类。CompositePatternDemo,演示类使用Employee类来添加部门层次结构,并打印所有员工。

image-20200816161901297

步骤一
创建Employee类,该类带有Employee对象的列表:Employee.java

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

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

    public void addEmployee(Employee employee) {
        subordinates.add(employee);
    }

    public void removeEmployee(Employee employee) {
        subordinates.remove(employee);
    }

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

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

步骤二
使用Employee类来创建和打印员工的层次结构:CompositePatternDemo.java

public class CompositePatternDemo {
    public static void main(String[] args) {
        Employee CEO = new Employee("John","CEO",30000);

        Employee headSales = new Employee("Robert", "Head Sales", 20000);

        Employee headMarketing = new Employee("Michel", "Head Marketing", 20000);

        Employee clerk1 = new Employee("Laura","Marketing", 10000);
        Employee clerk2 = new Employee("Bob","Marketing", 10000);

        Employee salesExecutive1 = new Employee("Richard","Sales", 10000);
        Employee salesExecutive2 = new Employee("Rob","Sales", 10000);

        CEO.addEmployee(headSales);
        CEO.addEmployee(headMarketing);

        headSales.addEmployee(salesExecutive1);
        headSales.addEmployee(salesExecutive2);

        headMarketing.addEmployee(clerk1);
        headMarketing.addEmployee(clerk2);

        //打印该组织的所有员工
        System.out.println(CEO);
        for (Employee headEmployee : CEO.getSubordinates()) {
            System.out.println(headEmployee);
            for (Employee employee : headEmployee.getSubordinates()) {
                System.out.println(employee);
            }
        }
    }
}

结果

Employee :[ Name : John, dept : CEO, salary :30000 ]
Employee :[ Name : Robert, dept : Head Sales, salary :20000 ]
Employee :[ Name : Richard, dept : Sales, salary :10000 ]
Employee :[ Name : Rob, dept : Sales, salary :10000 ]
Employee :[ Name : Michel, dept : Head Marketing, salary :20000 ]
Employee :[ Name : Laura, dept : Marketing, salary :10000 ]
Employee :[ Name : Bob, dept : Marketing, salary :10000 ]

进程完成,退出码 0

行为型模式

备忘录模式

​ **备忘录模式(Memento Pattern)**保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。

介绍
**意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。

**主要解决:**所谓备忘录模式就是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。

何时使用:很多时候总是需要记录一个对象的内部状态,这样做的目的就是为了允许用户取消不确定或者错误的操作,能够恢复到他原先得状态,使得他有“后悔药”可吃。

如何解决:通过一个备忘录类专门存储对象状态。

关键代码:客户不与备忘录类耦合,与备忘录管理类耦合。

应用实例

  • 后悔药。
  • 打游戏时的存档。
  • Windows里的ctrl+z。
  • IE浏览器中的后退。
  • 数据库的事务管理。

优点

  • 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。
  • 实现了信息的封装,使得用户不需要关心状态的保存细节。

缺点

  • 消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。

使用场景

  • 需要保存/恢复数据的相关状态场景。
  • 提供一个可回滚的操作。

注意事项

  • 为了符合迪米特原则,还要增加一个管理备忘录的类。
  • 为了节约内存,可用原型模式+备忘录模式。

实现
备忘录模式使用三个类MementoOriginatorCareTakerMemento包含了要被恢复的对象的状态。Originator创建并在Memento对象中存储状态。Caretaker对象负责从Memento中恢复对象的状态。演示类MementoPatternDemo使用CareTakerOriginator对象来显示对象的状态恢复。

image-20200816174553018

步骤一
创建Memento类:Memento.java

public class Memento {
    private String state;

    public Memento(String state) {
        this.state = state;
    }

    public String getState() {
        return state;
    }
}

步骤二
创建Originator类:Originator.java

public class Originator {
    private String state;

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }

    public Memento saveStateToMemento() {
        return new Memento(state);
    }

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

步骤三

创建CareTaker类:CareTaker.java

public class CareTaker {
    private List<Memento> mementoList = new ArrayList<>();

    public void add(Memento state) {
        mementoList.add(state);
    }

    public Memento get(int index) {
        return mementoList.get(index);
    }
}

步骤四
使用CareTakerOriginator对象:MementoPatternDemo.java

public class MementoPatternDemo {
    public static void main(String[] args) {
        Originator originator = new Originator();
        CareTaker careTaker = new CareTaker();
        originator.setState("State #1");
        originator.setState("State #2");
        careTaker.add(originator.saveStateToMemento());
        originator.setState("State #3");
        careTaker.add(originator.saveStateToMemento());
        originator.setState("State #4");

        System.out.println("Current State: "+originator.getState());
        originator.getStateFromMemento(careTaker.get(0));
        System.out.println("First saved State: "+originator.getState());
        originator.getStateFromMemento(careTaker.get(1));
        System.out.println("Second saved State: "+originator.getState());
    }
}

结果

Current State: State #4
First saved State: State #2
Second saved State: State #3

进程完成,退出码 0

策略模式

​ 在**策略模式(Strategy Pattern)**中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。在策略模式中,创建表示各种策略的对象和一个行为随着策略对象而改变的context对象。策略对象改变context对象的执行算法。

介绍
意图:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。

主要解决:在有多种算法相似的情况下,使用if…else所带来的复杂和难以维护。

何时使用:一个系统有许多许多类,而区分它们的只是他们直接的行为。

如何解决:将这些算法封装成一个一个的类,任意地替换。

关键代码:实现同一个接口。

应用实例

  • 诸葛亮的锦囊妙计,每一个锦囊就是一个策略。
  • 旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。
  • JAVA AWT中的LayoutManager

优点

  • 算法可以自由切换。
  • 避免使用多重条件判断。
  • 扩展性良好。

缺点

  • 策略类会增多。
  • 所有策略类都需要对外暴露。

使用场景

  • 如果一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
  • 一个系统需要动态地在几种算法中选择一种。
  • 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。

注意事项

  • 如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。

实现

​ 创建一个定义活动的Strategy接口和实现了Strategy接口的实体策略类。Context是一个使用了某种策略的类。演示类StrategyPatternDemo使用Context和策略对象来演示Context在它所配置或使用的策略改变时的行为变化。

image-20200816201342590

步骤一
创建一个接口:Strategy.java

public interface Strategy {
    public int doOperation(int num1, int num2);
}

步骤二
创建实现接口的实体类:OperationAdd.java

public class OperationAdd implements Strategy{
    @Override
    public int doOperation(int num1, int num2) {
        return num1 + num2;
    }
}

OperationSubtract.java

public class OperationSubtract implements Strategy {
    @Override
    public int doOperation(int num1, int num2) {
        return num1 - num2;
    }
}

OperationMultiply.java

public class OperationMultiply implements Strategy {
    @Override
    public int doOperation(int num1, int num2) {
        return num1 * num2;
    }
}

步骤三
创建context类:Context.java

public class Context {
    private Strategy strategy;

    public Context(Strategy strategy) {
        this.strategy = strategy;
    }

    public int executeStrategy(int num1, int num2) {
        return strategy.doOperation(num1, num2);
    }
}

步骤四
使用Context来查看当它改变策略Strategy时的行为变化:StrategyPatternDemo.java

public class StrategyPatternDemo {
    public static void main(String[] args) {
        Context context = new Context(new OperationAdd());
        System.out.println("10 + 5 = "+context.executeStrategy(10,5));

        context = new Context(new OperationSubtract());
        System.out.println("10 - 5 = "+context.executeStrategy(10,5));

        context = new Context(new OperationMultiply());
        System.out.println("10 * 5 = "+context.executeStrategy(10,5));
    }
}

结果

10 + 5 = 15
10 - 5 = 5
10 * 5 = 50

进程完成,退出码 0

迭代器模式

​ **迭代器模式(Iterator Pattern)**时Java和.Net编程环境中非常常用的设计模式。这种模式用于顺序访问集合对象的元素,不需要知道集合对象的底层表示。迭代器模式属于行为型模式

介绍
意图:提供一种方法顺序访问一个聚合对象中各个元素,而又无须暴露该对象的内部表示。

主要解决:不同的方式来遍历整个整合对象。

何时使用:遍历一个聚合对象。

如何解决:把在元素之间游走的责任交给迭代器,而不是聚合对象。

关键代码:定义接口:hasNext,next。

应用实例:Java中的iterator。

优点

  • 它支持以不同的方式遍历一个聚合对象。
  • 迭代器简化了聚合类。
  • 在同一个聚合上可以有多个遍历。
  • 在迭代器模式中,增加新的聚合类和迭代器类都很方便,无需修改原有代码。

缺点

  • 由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。

使用场景

  • 访问一个聚合对象的内部而无须暴露它的内部表示。
  • 需要为聚合对象提供多种遍历方式。
  • 为遍历不同的聚合结构提供一个统一的接口。

注意事项

  • 迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可以让外部代码透明地访问集合内部的数据。

实现

​ 创建一个叙述导航方法的Iterator接口和一个返回迭代器的Container接口,实现了Container接口的实体类将负责实现Iterator接口。演示类IteratorPatternDemo使用实体类NamesRepository来打印NamesRepository中存储为集合的Names。

image-20200816205143388

步骤一

创建接口:Iterator.java

public interface Iterator {
    public boolean hasNext();
    public Object next();
}

Container.java

public interface Container {
    public Iterator getIterator();
}

步骤二

创建实现了Container接口的实体类。该类有实现了Iterator接口的内部类NameIteratorNameRepository.java

public class NameRepository implements Container{
    public String names[] = {"Robert", "John", "Julie", "Lora"};

    @Override
    public Iterator getIterator() {
        return new NameIterator();
    }

    private class NameIterator implements Iterator {
        int index;

        @Override
        public boolean hasNext() {
            if (index < names.length) {
                return true;
            }
            return false;
        }

        @Override
        public Object next() {
            if (this.hasNext()) {
                return names[index++];
            }
            return null;
        }
    }
}

步骤三

使用NameRepository来获取迭代器,并打印名字:IteratorPatternDemo.java

public class IteratorPatternDemo {
    public static void main(String[] args) {
        NameRepository nameRepository = new NameRepository();
        for (Iterator iter = nameRepository.getIterator(); iter.hasNext();) {
            String name = (String) iter.next();
            System.out.println("Name : "+name);
        }
    }
}

结果

Name : Robert
Name : John
Name : Julie
Name : Lora

进程完成,退出码 0

访问者模式

​ 在**访问者模式(Visitor Pattern)**中,我们使用了一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变。这种类型的设计模式属于行为型模式。根据模式,元素对象已接受访问者对象,这样访问者对象就可以处理元素对象上的操作。

介绍
意图:主要讲数据结构与数据操作分离。

主要解决:稳定的数据结构和易变的操作耦合问题。

何时使用:需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免这些操作“污染”这些对象的类,使用访问者模式将这些封装到类中。

如何解决:在被访问的类里面加一个对外提供接待访问者的接口。

关键代码:在数据基础类里面有一个方法接收访问者,将自身引用传入访问者。

应用实例

  • 您在朋友家做客,您是访问者,朋友接受您的访问,您通过朋友的描述,然后对朋友的描述做出一个判断,这就是访问者模式。

优点

  • 符合单一职责原则。
  • 优秀的扩展性。
  • 灵活性。

缺点

  • 具体元素对访问者公布细节,违反了迪米特原则。
  • 具体元素变更比较困难。
  • 违反了依赖倒置原则,依赖了具体类,没有依赖抽象。

使用场景

  • 对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。
  • 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免这些操作“污染”这些对象的类,也不希望在增加新操作时修改这些类。

注意事项

  • 访问者可以对功能进行统一,可以做报表、UI、拦截器与过滤器。

实现

​ 创建一个定义接受操作的ComputerPart接口。KeyboardMouseMonitorComputer是实现了ComputerPart接口的实体类。定义另一个接口ComputerPartVisitor,它定义了访问者类的操作。Computer使用实体访问者来执行相应的动作。通过演示类VisitorPatternDemo的使用ComputerComputerPartVisitor类来演示访问者模式的用法。

image-20200816213601249

步骤一

定义一个表示元素的接口:ComputerPart.java

public interface ComputerPart {
    public void accept(ComputerPartVisitor computerPartVisitor);
}

步骤二

创建扩展了上述类的实体类:Keyboard.java

public class Keyboard implements ComputerPart{

    @Override
    public void accept(ComputerPartVisitor computerPartVisitor) {
        computerPartVisitor.visit(this);
    }
}

Monitor.java

public class Monitor implements ComputerPart{

    @Override
    public void accept(ComputerPartVisitor computerPartVisitor) {
        computerPartVisitor.visit(this);
    }
}

Mouse.java

public class Mouse implements ComputerPart{

    @Override
    public void accept(ComputerPartVisitor computerPartVisitor) {
        computerPartVisitor.visit(this);
    }
}

Computer.java

public class Computer implements ComputerPart{

    ComputerPart[] parts;

    public Computer() {
        parts = new ComputerPart[]{new Mouse(), new Keyboard(), new Monitor()};
    }

    @Override
    public void accept(ComputerPartVisitor computerPartVisitor) {
        for (int i = 0; i < parts.length; i++) {
            parts[i].accept(computerPartVisitor);
        }
        computerPartVisitor.visit(this);
    }
}

步骤三

定义一个表示访问者的接口:ComputerPartVisitor.java

public interface ComputerPartVisitor {
    public void visit(Computer computer);
    public void visit(Mouse mouse);
    public void visit(Keyboard keyboard);
    public void visit(Monitor monitor);
}

步骤四

创建实现了上述类的实体访问者:ComputerPartDisplayVisitor.java

public class ComputerPartDisplayVisitor implements ComputerPartVisitor {

    @Override
    public void visit(Computer computer) {
        System.out.println("Displaying Computer");
    }

    @Override
    public void visit(Mouse mouse) {
        System.out.println("Displaying Mouse");
    }

    @Override
    public void visit(Keyboard keyboard) {
        System.out.println("Displaying Keyboard");
    }

    @Override
    public void visit(Monitor monitor) {
        System.out.println("Displaying Monitor");
    }
}

步骤五

使用ComputerPartDisplayVisitor来显示Computer的组成部分:VisitorPatternDemo.java

public class VisitorPatternDemo {
    public static void main(String[] args) {

        ComputerPart computer = new Computer();
        computer.accept(new ComputerPartDisplayVisitor());
    }
}

结果

Displaying Mouse
Displaying Keyboard
Displaying Monitor
Displaying Computer

进程完成,退出码 0

观察者模式

​ 当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。

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

主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。

如何解决:使用面向对象技术,可以将这种依赖关系弱化。

关键代码:在抽象类里有一个ArrayList存放观察者们。

应用实例

  • 拍卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价。
  • 西游记里面悟空请求菩萨降服红孩儿,菩萨洒了一地水招来一个老乌龟,这个乌龟就是观察者,他观察菩萨洒水这个动作

优点

  • 观察者和被观察者是抽象耦合的
  • 建立一套触发机制

缺点:

  • 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
  • 如果观察者和观察目标之间有循环依赖的话,观察目标会触发他们之间进行循环调用,可能导致系统崩溃。
  • 观察者模式没有相应的机制让观察者指定所观察的目标对象是怎么变化的,而仅仅只是知道观察目标发生了变化。

使用场景

  • 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使他们可以各自独立地改变和复用。
  • 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
  • 一个对象必须通知其他对象,而并不知道这些对象是谁。
  • 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象…,可以使用观察者模式创建一种链式触发机制。

注意事项

  • Java中已经有了对观察者模式的支持类。
  • 避免循环引用。
  • 如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步反式。

实现

​ 观察者模式使用三个类Subject(被观察者)、Observer(观察者)和ClientSubject对象带有绑定观察者到Client对象和从Client对象解绑观察者的方法。创建Subject类、Observer抽象类和扩展了抽象类Observer的实体类。演示类ObserverPatternDemo使用Subject和实体类对象来演示观察者模式。

image-20200819174152961

步骤1

创建Subject类:Subject.java

public class Subject {

    private List<Observer> observers = new ArrayList<>();

    private int state;

    public int getState() {
        return state;
    }

    public void setState(int state) {
        this.state = state;
        notifyAllObservers();
    }

    public void attach(Observer observer) {
        observers.add(observer);
    }

    public void notifyAllObservers() {
        for (Observer observer : observers) {
            observer.update();
        }
    }
}

步骤2

创建Observer类:Observer.java

public abstract class Observer {

    protected Subject subject;

    public abstract void update();
}

步骤3

创建实体观察者类:BinaryObserver.java

public class BinaryObserver extends Observer{

    public BinaryObserver(Subject subject) {
        this.subject = subject;
        this.subject.attach(this);
    }

    @Override
    public void update() {
        System.out.println("Binary String: "+Integer.toBinaryString(subject.getState()));
    }
}

OctalObserver.java

public class OctalObserver extends Observer{

    public OctalObserver(Subject subject) {
        this.subject = subject;
        this.subject.attach(this);
    }

    @Override
    public void update() {
        System.out.println("Octal String: "
                +Integer.toOctalString(subject.getState()));
    }
}

HexaObserver.java

public class HexaObserver extends Observer{

    public HexaObserver(Subject subject) {
        this.subject = subject;
        this.subject.attach(this);
    }

    @Override
    public void update() {
        System.out.println("Hex String: "
                +Integer.toHexString(subject.getState()).toUpperCase());
    }
}

步骤4

使用Subject和实体观察者对象:ObserverPatternDemo.java

public class ObserverPatternDemo {

    public static void main(String[] args) {
        Subject subject = new Subject();

        new BinaryObserver(subject);
        new HexaObserver(subject);
        new OctalObserver(subject);

        System.out.println("First state change: 15");
        subject.setState(15);
        System.out.println();

        System.out.println("Second state change: 10");
        subject.setState(10);
    }
}

步骤5

结果:

First state change: 15
Binary String: 1111
Hex String: F
Octal String: 17

Second state change: 10
Binary String: 1010
Hex String: A
Octal String: 12

Process finished with exit code 0

解释器模式

​ **解释器模式(Interpreter Pattern)**提供了评估语言的语法或表达式的方式,它属于行为型模式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在SQL解析、符号处理引擎等。

介绍

意图:给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。

主要解决:对于一些固定文法构建一个解释句子的解释器。

何时使用:如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。

如何解决:构建语法树,定义终结符与非终结符。

关键代码:构建环境类,包含解释器之外的一些全局信息,一般是HashMap

应用实例:编译器、运算表达式计算。

优点

  • 可扩展性比较好,灵活。
  • 增加了新的解释表达式的方式。
  • 易于实现简单文法。

缺点

  • 可利用场景比较少。
  • 对于复杂的文法比较难维护。
  • 解释器模式会引起类膨胀。
  • 解释器模式采用递归调用方法。

使用场景

  • 可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。
  • 一些重复出现的问题可以用一种简单的语言来进行表达。
  • 一个简单语法需要解释的场景。

注意事项

  • 可利用场景比较少,Java中如果碰到可以用expression4J代替。

实现

​ 创建一个接口Expression和实现了Expression接口的实体类。定义作为上下文中主要解释器的TerminalExpression类。其他的类OrExpressionAndExpression用于创建组合式表达式。演示类InterpreterPatternDemo使用Expression类创建规则和演示表达式的解析。

image-20200817090230672

步骤一

创建一个表达式接口:Expression.java

public interface Expression {
    public boolean interpret(String context);
}

步骤二

创建实现了上述接口的实体类:TerminalExpression.java

public class TerminalExpression implements Expression{

    private String data;

    public TerminalExpression(String data) {
        this.data = data;
    }

    @Override
    public boolean interpret(String context) {
        if (context.contains(data)) {
            return true;
        }
        return false;
    }
}

OrExpression.java

public class OrExpression implements Expression{

    private Expression expr1 = null;
    private Expression expr2 = null;

    public OrExpression(Expression expr1, Expression expr2) {
        this.expr1 = expr1;
        this.expr2 = expr2;
    }

    @Override
    public boolean interpret(String context) {
        return expr1.interpret(context) || expr2.interpret(context);
    }
}

AndExpression.java

public class AndExpression implements Expression{

    private Expression expr1 = null;
    private Expression expr2 = null;

    public AndExpression(Expression expr1, Expression expr2) {
        this.expr1 = expr1;
        this.expr2 = expr2;
    }

    @Override
    public boolean interpret(String context) {
        return expr1.interpret(context) && expr2.interpret(context);
    }
}

步骤三

InterpreterPatternDemo使用Expression类来创建规则,并解析它们:InterpreterPatternDemo.java

public class InterpreterPatternDemo {

    //规则:Robert和John是男性
    public static Expression getMaleExpression() {
        Expression robert = new TerminalExpression("Robert");
        Expression john = new TerminalExpression("John");
        return new OrExpression(robert, john);
    }

    //规则:Juile是一个已婚的女性
    public static Expression getMarrieldExpression() {
        Expression julie = new TerminalExpression("Julie");
        Expression married = new TerminalExpression("Married");
        return new AndExpression(julie, married);
    }

    public static void main(String[] args) {
        Expression isMale = getMaleExpression();
        Expression isMarriedWoman = getMarrieldExpression();

        System.out.println("John is male? "+isMale.interpret("John"));
        System.out.println("Julie is a married women? " + isMarriedWoman.interpret("Married Julie"));
    }
}

结果

John is male? true
Julie is a married women? true

进程完成,退出码 0

空对象模式

​ 在**空对象模式(Null Object Pattern)**中,一个空对象取代NULL对象实例的检查。Null对象不是检查空值,而是反应一个不做任何动作的关系。这样的Null对象也可以在数据不可用的时候提供默认的行为。

​ 在空对象模式中,我们创建一个指定各种要执行的操作的抽象类和扩展该类的实体类,还创建一个未对该类做任何实现的空对象类,该空对象类将无缝地使用在需要检查空值的地方。

实现

​ 创建一个定义操作(在这里,是客户的名称)的AbstractCustomer抽象类,和扩展了AbstractCustomer类的实体类。工厂类CustomerFactory基于客户传递的名字来返回RealCustomerNullCustomer对象。演示类NullPatternDemo使用CustomerFactory来演示空对象模式的用法。

image-20200817100853307

步骤一

创建一个抽象类:AbstractCustomer.java

public abstract class AbstractCustomer {
    protected String name;
    public abstract boolean isNil();
    public abstract String getName();
}

步骤二

创建扩展了上述类的实体类:RealCustomer.java

public class RealCustomer extends AbstractCustomer{

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

    @Override
    public boolean isNil() {
        return false;
    }

    @Override
    public String getName() {
        return name;
    }
}

NullCustomer.java

public class NullCustomer extends AbstractCustomer{

    @Override
    public boolean isNil() {
        return true;
    }

    @Override
    public String getName() {
        return "Not Available in Customer Database";
    }
}

步骤三

创建CustomerFactory类。

public class CustomerFactory {

    public static final String[] names = {"Rob", "Joe", "Julie"};

    public static AbstractCustomer getCustomer(String name) {
        for (int i = 0; i < names.length; i++) {
            if (names[i].equalsIgnoreCase(name)) {
                return new RealCustomer(name);
            }
        }
        return new NullCustomer();
    }
}

步骤四

使用CustomerFactory,基于客户传递的名字,来获取RealCustomerNullCustomer对象:NullPatternDemo.java

public class NullPatternDemo {
    public static void main(String[] args) {
        AbstractCustomer customer1 = CustomerFactory.getCustomer("Rob");
        AbstractCustomer customer2 = CustomerFactory.getCustomer("Bob");
        AbstractCustomer customer3 = CustomerFactory.getCustomer("Julie");
        AbstractCustomer customer4 = CustomerFactory.getCustomer("Laura");

        System.out.println("Customers : ");
        System.out.println(customer1.getName());
        System.out.println(customer2.getName());
        System.out.println(customer3.getName());
        System.out.println(customer4.getName());
    }
}

结果:

Customers : 
Rob
Not Available in Customer Database
Julie
Not Available in Customer Database

进程完成,退出码 0

命令模式

​ **命令模式(Command Pattern)**是一种数据驱动型的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。

介绍

意图:将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。

主要解决:在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做等处理时,这种无法抵御变化的紧耦合的设计就不太合适。

何时使用:在某些场合,比如要对行为进行“记录、撤销/重做、事务”等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将“行为请求者”与“行为实现者”解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。

如何解决:通过调用者调用接受者执行命令,顺序:调用者→命令→接受者。

关键代码:定义三个角色:1、received真正的命令执行对象。2、Command。3、invoker使用命令对象的入口。

应用实例struts1中的action核心控制器ActionServlet只有一个,相当于invoker,而模型层的类会随着不同的应用有不同的模型类,相当于具体的Command

优点

  • 降低了系统耦合度。
  • 新的命令可以很容易添加到系统中去。

缺点

  • 使用命令模式可能会导致某些系统有过多的具体命令类。

使用场景

  • 认为是命令的地方都可以使用命令模式,比如:1、GUI中每一个按钮都是一条命令。2、模拟CMD

注意事项

  • 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作,也可以考虑使用命令模式,见命令模式的扩展。

命令模式结构示意图

image-20200817112332114

实现

​ 首先创建作为命令的接口Order,然后创建作为请求的Stock类。实体命令类BuyStockSellStock,实现了Order接口,将执行实际的命令处理。创建作为调用对象的类Broker,它接受订单并能下订单。

Broker对象使用命令模式,基于命令的类型确定哪个对象执行哪个命令。CommandPatternDemo,演示类使用Broker类来演示命令模式。

image-20200817150810043

步骤一

创建一个命令接口:Order.java

public interface Order {
    void execute();
}

步骤二

创建一个请求类:Stock.java

public class Stock {

    private String name = "ABC";
    private int quantity = 10;

    public void buy() {
        System.out.println("Stock [ Name: "+name+",Quantity: "+quantity+" ] bought");
    }

    public void sell() {
        System.out.println("Stock [ Name: "+name+",Quantity: "+quantity+" ] sold");
    }
}

步骤三

创建实现了Order接口的实体类:BuyStock.java

public class BuyStock implements Order{
    private Stock stock;

    public BuyStock(Stock stock) {
        this.stock = stock;
    }

    @Override
    public void execute() {
        stock.buy();
    }
}

SellStock.java

public class SellStock implements Order {
    private Stock stock;

    public SellStock(Stock stock) {
        this.stock = stock;
    }

    @Override
    public void execute() {
        stock.sell();
    }
}

步骤四
创建命令调用类:Broker.java

public class Broker {
    private List<Order> orderList = new ArrayList<>();

    public void takeOrder(Order order) {
        orderList.add(order);
    }

    public void placeOrders() {
        for (Order order : orderList) {
            order.execute();
        }
        orderList.clear();
    }
}

步骤五
使用Broker类来接受并执行命令:CommandPatternDemo.java

public class CommandPatternDemo {
    public static void main(String[] args) {
        Stock stock = new Stock();

        BuyStock buyStockOrder = new BuyStock(stock);
        SellStock sellStock = new SellStock(stock);

        Broker broker = new Broker();
        broker.takeOrder(buyStockOrder);
        broker.takeOrder(sellStock);

        broker.placeOrders();
    }
}

结果

Stock [ Name: ABC,Quantity: 10 ] bought
Stock [ Name: ABC,Quantity: 10 ] sold

进程完成,退出码 0

模板模式

​ 在**模板模式(Template Pattern)**中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。

介绍

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

主要解决:一些方法通用,却在每一个子类都重新写了这一方法。

何时使用:有一些通用的方法。

如何解决:将这些通用算法抽象出来。

关键代码:在抽象类实现,其他步骤在子类实现。

应用实例

  • 在造房子的时候,地基、走线、水管都一样,只有在建筑的后期才有加壁橱加栅栏等差异。
  • 西游记里面菩萨定好的81难,这就是一个顶层的逻辑骨架。
  • Spring中对Hibernate的支持,将一些已经定义好的方法封装起来,比如开启事务、获取Session、关闭Session等,程序员不需要重复写那些已经规范好的代码,直接丢一个实体就可以保存。

优点

  • 封装不变部分,扩展可变部分。
  • 提取公共部分,便于维护。
  • 行为由父类控制,子类实现。

缺点

  • 每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。

使用场景

  • 有多个子类共有的方法,且逻辑相同。
  • 重要的、复杂的方法,可以考虑作为模板方法。

注意事项

  • 为防止恶意操作,一般模板方法都加上final关键字。

实现

​ 创建一个定义操作的Game抽象类,其中,模板方法设置为final,这样它就不会被重写。Cricket和Football是扩展了Game的实体类,它们重写了抽象类的方法。TemplatePatternDemo使用Game来演示模板模式的用法。

image-20200817154151767

步骤一

创建一个抽象类,它的模板方法被设置为final:Game.java

public abstract class Game {
    abstract void initialize();
    abstract void startPlay();
    abstract void endPlay();

    //模板
    public final void play() {
        //初始化游戏
        initialize();
        //开始游戏
        startPlay();
        //结束游戏
        endPlay();
    }
}

步骤二

创建扩展了上述类的实体类:Cricket.java

public class Cricket extends Game{

    @Override
    void initialize() {
        System.out.println("Cricket Game Initialized! Start playing");
    }

    @Override
    void startPlay() {
        System.out.println("Cricket Game Started. Enjoy the game!");
    }

    @Override
    void endPlay() {
        System.out.println("Cricket Game Finished!");
    }
}

Football.java

public class Football extends Game {

    @Override
    void endPlay() {
        System.out.println("Football Game Finished!");
    }

    @Override
    void initialize() {
        System.out.println("Football Game Initialized! Start playing.");
    }

    @Override
    void startPlay() {
        System.out.println("Football Game Started. Enjoy the game!");
    }
}

步骤三

使用Game的模板方法play()来演示游戏的定义方式:TemplatePatternDemo.java

public class TemplatePatternDemo {
    public static void main(String[] args) {
        Game game = new Cricket();
        game.play();
        System.out.println();
        game = new Football();
        game.play();
    }
}

结果

Cricket Game Initialized! Start playing
Cricket Game Started. Enjoy the game!
Cricket Game Finished!

Football Game Initialized! Start playing.
Football Game Started. Enjoy the game!
Football Game Finished!

进程完成,退出码 0

责任链模式

​ 顾名思义,**责任链模式(Chain of Responsibility Pattern)**为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。

​ 在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么会把相同的请求传给下一个接收者,以此类推。

介绍

意图:避免请求发送者与接收者耦合在一起,让多个对象都有可能接受请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。

主要解决:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理中解耦了。

何时使用:在处理消息的时候已过滤很多道。

如何解决:拦截的类都实现统一接口。

关键代码:Handler里面聚合它自己,在HandlerRequest里判断是否合适,如果没达到条件则向下传递,向谁传递之前set进去。

应用实例

  • 红楼梦中的“击鼓传花”。
  • JS中的事件冒泡。
  • JAVA WEBApache TomcatEncoding的处理,Struts2的拦截器,jsp servlet的Filter。

优点

  • 降低耦合度。它将请求的发送者和接收者解耦
  • 简化了对象。使得对象不需要知道链的结构。
  • 增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。
  • 增加新的请求处理类很方便。

缺点

  • 不能保证请求一定被接收。
  • 系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。
  • 可能不容易观察运行时的特征,有碍于除错。

使用场景

  • 有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。
  • 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
  • 可动态指定一组对象处理请求。

注意事项:

  • 在JAVA WEB中遇到很多应用

实现

​ 创建抽象类AbstractLogger,带有详细的日志记录级别。然后创建三种类型的记录器,都扩展了AbstractLogger。每个记录器消息的级别是否属于自己的级别,如果是则相应地打印出来,否则将不打印并把消息传给下一个记录器。

image-20200817161541161

步骤一

创建抽象的记录器类:AbstractLogger.java

public abstract class AbstractLogger {
    public static int INFO = 1;
    public static int DEBUG = 2;
    public static int ERROR = 3;

    protected int level;

    //责任链中的下一个元素
    protected AbstractLogger nextLogger;

    public void setNextLogger(AbstractLogger nextLogger) {
        this.nextLogger = nextLogger;
    }

    public void logMessage(int level, String message) {
        if (this.level <= level) {
            write(message);
        }
        if (nextLogger != null) {
            nextLogger.logMessage(level,message);
        }
    }

    abstract protected void write(String message);
}

步骤二

创建扩展了该记录器类的实体类:ConsoleLogger.java

public class ConsoleLogger extends AbstractLogger{

    public ConsoleLogger(int level) {
        this.level = level;
    }

    @Override
    protected void write(String message) {
        System.out.println("Standard Console::Logger: "+message);
    }
}

ErrorLogger.java

public class ErrorLogger extends AbstractLogger{

    public ErrorLogger(int level) {
        this.level = level;
    }

    @Override
    protected void write(String message) {
        System.out.println("Error Console::Logger: "+message);
    }
}

FileLogger.java

public class FileLogger extends AbstractLogger{

    public FileLogger(int level) {
        this.level = level;
    }

    @Override
    protected void write(String message) {
        System.out.println("File::Logger: "+message);
    }
}

步骤三

创建不同类型的记录器。赋予它们不同的错误级别,并在每个记录器中设置下一个记录器。每个记录器中的下一个记录器代表的是链的一部分:ChainPatternDemo.java

public class ChainPatternDemo {

    private static AbstractLogger getChainOfLoggers() {
        AbstractLogger errorLogger = new ErrorLogger(AbstractLogger.ERROR);
        AbstractLogger fileLogger = new FileLogger(AbstractLogger.DEBUG);
        AbstractLogger consoleLogger = new ConsoleLogger(AbstractLogger.INFO);

        errorLogger.setNextLogger(fileLogger);
        fileLogger.setNextLogger(consoleLogger);

        return errorLogger;
    }

    public static void main(String[] args) {
        AbstractLogger loggerChain = getChainOfLoggers();

        loggerChain.logMessage(AbstractLogger.INFO,"This is an information");

        loggerChain.logMessage(AbstractLogger.DEBUG, "This is an debug level information");

        loggerChain.logMessage(AbstractLogger.ERROR, "This is an error information");
    }
}

结果

Standard Console::Logger: This is an information
File::Logger: This is an debug level information
Standard Console::Logger: This is an debug level information
Error Console::Logger: This is an error information
File::Logger: This is an error information
Standard Console::Logger: This is an error information

进程完成,退出码 0

中介者模式

​ **中介者模式(Mediator Pattern)**是用来降低多个对象和类之间的通信复杂性。这种模式提供了一个中介类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护。中介者模式属于行为型模式。

介绍

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

主要解决:对象与对象之间存在大量的关联关系,这样势必会导致系统的结构变得很复杂,同时若一个对象发生改变,我们也需要跟踪与之相关联的对象,同时做出相应的处理。

何时使用:多个类相互耦合,形成了网状结构。

如何解决:将上述网状结构分离为星型结构。

关键代码:对象Colleague之间的通信封装道一个类中单独处理。

应用实例

  • 中国加入WTO之前是各个国家相互贸易,结构复杂,现在是各个国家通过WTO来互相贸易。
  • 机场调度系统。
  • MVC框架,其中C(控制器)就是M(模型)和V(视图)的中介者。

优点

  • 降低了类的复杂度,将一对多转化成了一对一。
  • 各个类之间的解耦。3、符合迪米特法则。

缺点

  • 中介者会庞大,变得复杂难以维护。

使用场景

  • 系统中对象之间存在比较复杂的引用关系,导致它们之间的依赖关系结构混乱而且难以复用该对象。
  • 想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。

注意事项

  • 不应当在职责混乱的时候使用。

实现

​ 通过聊天室实例来演示中介者模式。实例中,多个用户可以向聊天室发送消息,聊天室向所有的用户显示消息。创建两个类ChatRoomUserUser对象使用ChatRoom方法来分享他们的消息。演示类MediatorPatternDemo使用User对象来显示他们之间的通信。

image-20200817170143589

步骤一

创建中介类:ChatRoom.java

public class ChatRoom {
    public static void showMessage(User user, String message) {
        System.out.println(new Date().toString()
            +" ["+user.getName()+"] : "+message);
    }
}

步骤二

创建User类:User.java

public class User {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

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

    public void sendMessage(String message) {
        ChatRoom.showMessage(this,message);
    }
}

步骤三

使用User对象来显示他们之间的通信:MediatorPatternDemo.java

public class MediatorPatternDemo {
    public static void main(String[] args) {
        User robert = new User("Robert");
        User john = new User("John");

        robert.sendMessage("Hi!John!");
        john.sendMessage("Hi!Robert!");
    }
}

结果

Mon Aug 17 17:07:08 CST 2020 [Robert] : Hi!John!
Mon Aug 17 17:07:08 CST 2020 [John] : Hi!Robert!

进程完成,退出码 0

状态模式

​ 在**状态模式(State Pattern)**中,类的行为是基于它的状态改变的。这种类型的设计模式属于行为型模式。在状态模式中,创建表示各种状态的对象和一个行为随着状态对象改变而改变的context对象。

介绍

意图:允许对象在内部状态发送改变时改变它的行为,对象看起来好像修改了它的类。

主要解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。

何时使用:代码中包含大量与对象状态有关的条件语句。

如何解决:将各种具体的状态类抽象出来。

关键代码:通常命令模式的接口中只有一个方法。而状态模式的接口中有一个或者多个方法。而且,状态模式的实现类的方法,一般返回值,或者是改变实例变量的值。也就是说,状态模式一般和对象的状态有关。实现类的方法有不同的功能,覆盖接口中的方法。状态模式和命令模式一样,也可以用于消除if…else等条件选择语句。

应用实例

  • 打篮球的时候运动员可以有正常状态、不正常状态和超常状态。
  • 曾侯乙编钟中,‘钟是抽象接口’,‘钟A’等是具体状态,‘曾侯乙编钟’是具体环境(Context)。

优点

  • 封装了转换规则。
  • 枚举可能的状态,在枚举状态之前需要确定状态种类。
  • 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
  • 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
  • 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。

缺点

  • 状态模式的使用必然会增加系统类和对象的个数。
  • 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
  • 状态模式对“开闭原则”的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。

使用场景

  • 行为随状态改变而改变的场景。
  • 条件、分支语句的代替者。

注意事项

  • 在行为受状态约束的时候使用状态模式而且状态不超过5个。

实现

​ 创建一个State接口和实现了State接口的实体状态类。Context是一个带有某个状态的类。演示类StatePatternDemo使用Context和状态对象来演示Context在状态改变时的行为变化。

image-20200817172930786

步骤一

创建一个接口:State.java

public interface State {
    public void doAction(Context context);
}

步骤二

创建实现接口的实体类:StartState.java

public class StartState implements State{

    @Override
    public void doAction(Context context) {
        System.out.println("Player is in start state");
        context.setState(this);
    }
    @Override
    public String toString() {
        return "Start State";
    }
}

StopState.java

public class StopState implements State{

    @Override
    public void doAction(Context context) {
        System.out.println("Player is in Stop state");
        context.setState(this);
    }
    @Override
    public String toString() {
        return "Stop State";
    }
}

步骤三

创建Context类:Context.java

public class Context {
    private State state;

    public Context() {
        state = null;
    }

    public State getState() {
        return state;
    }

    public void setState(State state) {
        this.state = state;
    }
}

步骤四

使用Context来查看当前状态State改变时的行为变化:StatePatternDemo.java

public class StartPatternDemo {
    public static void main(String[] args) {
        Context context = new Context();

        StartState startState = new StartState();
        startState.doAction(context);

        System.out.println(context.getState().toString());

        StopState stopState = new StopState();
        stopState.doAction(context);

        System.out.println(context.getState().toString());
    }
}

结果

Player is in start state
Start State
Player is in Stop state
Stop State

进程完成,退出码 0

J2EE模式

MVC模式

介绍

MVC模式代表Model-View-Controller(模型-视图-控制器)模式。这种模式用于应用程序的分层开发。

  • Model(模型)-模型代表一个存取数据的对象或JAVA POJO。它可以带有逻辑,在数据变化时更新控制器。

  • View(视图)-视图代表模型包含的数据的可视化。

  • Controller(控制器)-控制器作用于模型图和视图上。它控制数据流向模型对象,并在数据变化时更新视图。它使视图与模型分离开。

image-20200817211444226

实现

​ 创建一个作为模型的Student对象。StudentView是一个把学生详细信息输出到控制台的视图类,StudentController是负责存储数据到Student对象中的控制器类,并相应地更新视图StudentView

MVCPatternDemo,我们的演示类使用StudentController来演示MVC模式的用法。

image-20200817212419060

步骤一

创建模式:Student.java

public class Student {
    private String rollNo;
    private String name;

    public String getRollNo() {
        return rollNo;
    }

    public void setRollNo(String rollNo) {
        this.rollNo = rollNo;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

步骤二

创建视图:StudentView.java

public class StudentView {
    public void printStudentDetails(String studentName, String studentRollNo) {
        System.out.println("Student: ");
        System.out.println("Name: "+studentName);
        System.out.println("Roll No: "+studentRollNo);
    }
}

步骤三

创建控制器:StudentController.java

public class StudentController {
    private Student model;
    private StudentView view;

    public StudentController(Student model, StudentView view) {
        this.model = model;
        this.view = view;
    }

    public String getRollNo() {
        return model.getRollNo();
    }

    public void setRollNo(String rollNo) {
        model.setRollNo(rollNo);
    }

    public String getName() {
        return model.getName();
    }

    public void setName(String name) {
        model.setName(name);
    }

    public void updateView() {
        view.printStudentDetails(model.getName(),model.getRollNo());
    }
}

步骤四

使用StudentController方法来演示MVC设计模式的用法:MVCPatternDemo.java

public class MVCPatterDemo {
    public static void main(String[] args) {
        //从数据库获取学生记录
        Student model = retrieveStudentFromDatabase();

        //创建一个视图:把学生详细信息输出到控制台
        StudentView view = new StudentView();

        StudentController controller = new StudentController(model, view);

        controller.updateView();

        //更新模型数据
        controller.setName("John");

        controller.updateView();
    }

    private static Student retrieveStudentFromDatabase() {
        Student student = new Student();
        student.setName("Robert");
        student.setRollNo("10");
        return student;
    }
}

结果:

Student: 
Name: Robert
Roll No: 10
Student: 
Name: John
Roll No: 10

进程完成,退出码 0

传输对象模式

​ **传输对象模式(Transfer Object Pattern)**用于从客户端向服务器一次性传递带有多个属性的数据。传输对象也被称为数值对象。传输对象是一个具有getter/setter方法的简单的POJO类,它是可以序列化的,所以通过网络传输。它没有任何的行为。服务器端的业务类通常从数据库读取数据,然后填充POJO,并把它发送到客户端或按值传递它。对于客户端,传输对象是只读的。客户端可以创建自己的传输对象,并把它传递给服务器,以便一次性更新数据库中的数值。以下是这种设计模式的实体。

  • 业务对象(Business Object):为传输对象填充数据的业务服务。
  • 传输对象(Transfer Object):简单的POJO,只有设置/获取属性的方法。
  • 客户端(Client):客户端可以发送请求或者发送传输对象到业务对象。

实现

​ 创建一个作为业务对象的StudentBO和作为传输对象的StudentVO,它们都代表了实体。演示类TransferObjectPatternDemo在这里是作为一个客户端,将使用StudentBO和Student来演示传输对象设计模式。

image-20200819162343715

步骤一

创建传输对象:StudentVO.java

public class StudentVO {
    private String name;
    private int rollNo;

    public StudentVO(String name, int rollNo) {
        this.name = name;
        this.rollNo = rollNo;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getRollNo() {
        return rollNo;
    }

    public void setRollNo(int rollNo) {
        this.rollNo = rollNo;
    }
}

步骤二

创建业务对象:StudentBO.java

public class StudentBO {

    //列表是当作一个数据库
    List<StudentVO> students;

    public StudentBO() {
        students = new ArrayList<>();
        StudentVO student1 = new StudentVO("Robert",0);
        StudentVO student2 = new StudentVO("John", 1);
        students.add(student1);
        students.add(student2);
    }

    public void deleteStudent(StudentVO student) {
        students.remove(student.getRollNo());
        System.out.println("Student: Roll NO "+
                student.getRollNo()+", deleted from database");
    }
    
    //从数据库中检索学生名单
    public List<StudentVO> getAllStudents() {
        return students;
    }

    public StudentVO getStudent(int rollNo) {
        return students.get(rollNo);
    }

    public void updateStudent(StudentVO student) {
        students.get(student.getRollNo()).setName(student.getName());
        System.out.println("Student: Roll No "
        +student.getRollNo()+", updated in the database");
    }
}

步骤三

使用StudentBO来演示传输对象设计模式:TransferObjectPatternDemo.java

public class TransferObjectPatternDemo {
    public static void main(String[] args) {
        StudentBO studentBusinessObject = new StudentBO();

        //输出所有学生
        for (StudentVO studentVO : studentBusinessObject.getAllStudents()) {
            System.out.println("Student: [RollNo : "
                    + studentVO.getRollNo() + ", Name : " + studentVO.getName() + " ]");
        }

        //更新学生
        StudentVO student = studentBusinessObject.getAllStudents().get(0);
        student.setName("Michael");
        studentBusinessObject.updateStudent(student);

        //获取学生
        studentBusinessObject.getStudent(0);
        System.out.println("Student: [RollNo : "
                + student.getRollNo() + ", Name : " + student.getName() + " ]");
    }
}

结果

Student: [RollNo : 0, Name : Robert ]
Student: [RollNo : 1, Name : John ]
Student: Roll No 0, updated in the database
Student: [RollNo : 0, Name : Michael ]

进程完成,退出码 0

服务定位器模式

​ **服务定位器模式(Service Locator Pattern)**用在我们想使用JNDI查询定位各种服务的时候。考虑到为某个服务查找JNDI的代价很高,服务定位器模式充分利用了缓存技术。在首次请求某个服务时,服务定位器在JNDI中查找服务,并缓存该服务对象。当再次请求相同的服务时,服务定位器会在它的缓存中查找,这样可以在很大程度上提高应用程序的性能。以下是这种设计模式的主体。

  • 服务(Service):实际处理请求的服务。对这种服务的引用可以在JNDI服务器中查找到。
  • Context/初始化的ContextJNDI Context带有对要查找的服务的引用。
  • 服务定位器模式(Service Locator:服务定位器是通过JNDI查找和缓存服务来获取服务的单点接触。
  • 缓存(Cache):缓存存储服务的引用,以便复用它们。
  • 客户端(Client):Client是通过ServiceLocator调用服务的对象。

实现

​ 创建ServiceLocatorInitialContext、Cache、Service作为表示实体的各种对象。Service1Service2表示各种实体服务。演示类ServiceLocatorPatternDemo在这里作为一个客户端,将使用ServiceLocator来演示服务定位器设计模式。

image-20200818110108519

步骤一

创建服务接口:Service.java

public interface Service {
    public String getName();
    public void execute();
}

步骤二

创建实体服务(主要表现接口之间的多态性,指定行为方式):Service1.java

public class Service1 implements Service{
    @Override
    public String getName() {
        return "Service1";
    }

    @Override
    public void execute() {
        System.out.println("Executing Service1");
    }
}

Service2.java

public class Service2 implements Service{
    @Override
    public String getName() {
        return "Service2";
    }

    @Override
    public void execute() {
        System.out.println("Executing Service2");
    }
}

步骤三

JNDI查询创建InitialContext(也就是工厂模式的应用,通过类名来确定要实例化的对象):InitialContext.java

public class InitialContext {
    public Object lookup(String jndiName) {
        if (jndiName.equalsIgnoreCase("SERVICE1")) {
            System.out.println("Looking up and creating a new Service1 Object");
            return new Service1();
        } else if (jndiName.equalsIgnoreCase("SERVICE2")) {
            System.out.println("Looking up and creating a new Service2 Object");
            return new Service2();
        }
        return null;
    }
}

步骤四

创建缓存Cache(用的是传输对象模式。对实体类集合进行操作,主要是在集合中获取/添加实体类对象):Cache.java

public class Cache {

    private List<Service> services;

    public Cache() {
        services = new ArrayList<>();
    }

    public Service getService(String serviceName) {
        for (Service service : services) {
            if (service.getName().equalsIgnoreCase(serviceName)) {
                System.out.println("Returning cached "+serviceName+" object");
                return service;
            }
        }
        return null;
    }

    public void addService(Service newService) {
        boolean exists = false;
        for (Service service : services) {
            if (service.getName().equalsIgnoreCase(newService.getName())) {
                exists = true;
            }
        }
        if (!exists) {
            services.add(newService);
        }
    }
}

步骤五

创建服务定位器(定位器:使用步骤3来创建实例,使用步骤4来添加到集合,或者从集合中获取。(缓存中没有才会创建)):ServiceLocator.java

public class ServiceLocator {
    private static Cache cache;

    static {
        cache = new Cache();
    }

    public static Service getService(String jndiName) {
        Service service = cache.getService(jndiName);

        if (service != null) {
            return service;
        }

        InitialContext context = new InitialContext();
        Service newService = (Service) context.lookup(jndiName);
        cache.addService(newService);
        return newService;
    }
}

步骤六

使用ServiceLocator来演示服务定位器设计模式(调用步骤5得到实体类,并执行实体类的方法):ServiceLocatorPatternDemo.java

public class ServiceLocatorPatternDemo {
    public static void main(String[] args) {
        Service service = ServiceLocator.getService("Service1");
        service.execute();
        service = ServiceLocator.getService("Service2");
        service.execute();
        service = ServiceLocator.getService("Service1");
        service.execute();
        service = ServiceLocator.getService("Service2");
        service.execute();
    }
}

结果

Looking up and creating a new Service1 Object
Executing Service1
Looking up and creating a new Service2 Object
Executing Service2
Returning cached Service1 object
Executing Service1
Returning cached Service2 object
Executing Service2

进程完成,退出码 0

拦截过滤器模式

​ **拦截过滤器模式(Intercepting Filter Pattern)**用于对应用程序的请求或响应做一些预处理/后处理。定义过滤器,并在把请求传给实际目标应用程序之前应用在请求上。过滤器可以做认证/授权/记录日志,或者跟踪请求,然后把请求传给相应的处理程序。以下是这种设计模式的实体。

  • 过滤器(Filter):过滤器在请求处理程序执行请求之前或之后,执行某些任务。
  • 过滤器链(Filter Chain):过滤器链带有多个过滤器,并在Target上按照定义的顺序执行这些过滤器。
  • TargetTarget对象是请求处理程序。
  • 过滤管理器(Filter Manager):过滤管理器管理过滤器和过滤器链。
  • 客户端(Client):Client是向Target对象发送请求的对象。

实现

​ 创建FilterChainFilterManager、Target、Client作为表示实体的各种对象。AuthenticationFilterDebugFilter表示实体过滤器。演示类InterceptingFilterDemo使用Client来演示拦截过滤器设计模式。

image-20200818145639730

步骤一

创建过滤器接口Filter:Filter.java

public interface Filter {
    public void execute(String request);
}

步骤二

创建实体过滤器:AuthenticationFilter.java

public class AuthenticationFilter implements Filter{
    @Override
    public void execute(String request) {
        System.out.println("Authenticating request: " + request);
    }
}

DebugFilter.java

public class DebugFilter implements Filter{
    @Override
    public void execute(String request) {
        System.out.println("request log: " + request);
    }
}

步骤三

创建Target:Target.java

public class Target {
    public void execute(String request) {
        System.out.println("Executing request: " + request);
    }
}

步骤四

创建过滤器链:FilterChain.java

public class FilterChain {
    private List<Filter> filters = new ArrayList<>();
    private Target target;

    public void addFilter(Filter filter) {
        filters.add(filter);
    }

    public void execute(String request) {
        for (Filter filter : filters) {
            filter.execute(request);
        }
        target.execute(request);
    }

    public void setTarget(Target target) {
        this.target = target;
    }

}

步骤五

创建过滤管理器:FilterManager.java

public class FilterManager {
    FilterChain filterChain;

    public FilterManager(Target target) {
        filterChain = new FilterChain();
        filterChain.setTarget(target);
    }
    public void setFilter(Filter filter) {
        filterChain.addFilter(filter);
    }

    public void filterRequest(String request) {
        filterChain.execute(request);
    }
}

步骤六

创建客户端Client:Client.java

public class Client {
    FilterManager filterManager;

    public void setFilterManager(FilterManager filterManager) {
        this.filterManager = filterManager;
    }

    public void sendRequest(String request) {
        filterManager.filterRequest(request);
    }
}

步骤七

使用Client来演示拦截过滤器设计模式:InterceptingFilterChainDemo.java

public class InterceptingFilterDemo {
    public static void main(String[] args) {
        FilterManager filterManager = new FilterManager(new Target());
        filterManager.setFilter(new AuthenticationFilter());
        filterManager.setFilter(new DebugFilter());

        Client client = new Client();
        client.setFilterManager(filterManager);
        client.sendRequest("HOME");
    }
}

结果

Authenticating request: HOME
request log: HOME
Executing request: HOME

进程完成,退出码 0

前端控制器模式

​ **前端控制器模式(Front Controller Pattern)**是用来提供一个集中的请求处理机制,所有的请求都将由一个单一的处理程序处理。该处理程序可以做认证/授权/记录日志,或者跟踪请求,然后把请求传给相应的处理程序。以下是这种设计模式的实体。

  • 前端控制器(Front Controller):处理应用程序所有类型请求的单个处理程序,应用程序可以是基于web的应用程序,也可以是基于桌面的应用程序。
  • 调度器(Dispatcher):前端控制器可能使用一个调度器对象来调度请求到相应的具体处理程序。
  • 视图(View):视图是为请求而创建的对象。

实现

​ 创建FrontControllerDispatcher分别当作前端控制器和调度器。HomeViewStudentView表示各种为前端控制器接收到的请求而创建的视图。演示类FrontControllerPatternDemo使用FrontController来演示前端控制器设计模式。

image-20200819163752259

步骤

创建视图:HomeView.java

public class HomeView {
    public void show() {
        System.out.println("Displaying Home Page");
    }
}

StudentView.java

public class StudentView{
    public void show() {
        System.out.println("Displaying Student Page");
    }
}

步骤二

创建调度器Dispatcher:Dispatcher.java

public class Dispatcher {
    private StudentView studentView;
    private HomeView homeView;

    public Dispatcher() {
        studentView = new StudentView();
        homeView = new HomeView();
    }

    public void dispatcher(String request) {
        if (request.equalsIgnoreCase("STUDENT")) {
            studentView.show();
        } else {
            homeView.show();
        }
    }
}

步骤三

创建前端控制器FrontControllerFrontController.java

public class FrontController {

    private Dispatcher dispatcher;

    public FrontController() {
        dispatcher = new Dispatcher();
    }

    private boolean isAuthenticUser() {
        System.out.println("User is authenticated successfully");
        return true;
    }

    private void trackRequest(String request) {
        System.out.println("Page request: " + request);
    }

    public void dispatchRequest(String request) {
        //记录每一个请求
        trackRequest(request);
        //对用户进行身份验证
        if (isAuthenticUser()) {
            dispatcher.dispatcher(request);
        }
    }
}

步骤四

使用FrontController来演示前端控制器设计模式:FrontControllerPatternDemo.java

public class FrontControllerPatternDemo {
    public static void main(String[] args) {
        FrontController frontController = new FrontController();
        frontController.dispatchRequest("HOME");
        frontController.dispatchRequest("STUDENT");
    }
}

结果

Page request: HOME
User is authenticated successfully
Displaying Home Page
Page request: STUDENT
User is authenticated successfully
Displaying Student Page

进程完成,退出码 0

数据访问对象模式

​ **数据对象访问模式(Data Access Object Pattern)**或DAO模式用于把低级别的数据访问API或操作从高级的业务服务中分离出来。以下是数据访问对象模式的参与者。

  • 数据访问对象接口(Data Access Object Interface):该接口定义了在一个模型对象上要执行的标准操作。
  • 数据访问对象实体类(Data Access Object concrete class):该类实现了上述的接口。该类负责从数据源获取数据,数据源可以是数据库,也可以是XML,或者是其他的存储机制。
  • 模型对象/数值对象(Model Object/Value Object):该对象是简单的POJO,包含了get/set方法来存储通过使用DAO类检索到的数据。

实现

​ 创建一个作为模型对象或数值对象的Student对象。StudentDao是数据访问对象接口。StudentDaoImpl是实现了数据访问对象接口的实体类。演示类DaoPatternDemo使用StudentDao来演示数据访问对象模式的用法。

image-20200818161329912

步骤一

创建数值对象:Student.java

public class Student {
    private String name;
    private int rollNo;

    Student(String name, int rollNo){
        this.name = name;
        this.rollNo = rollNo;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getRollNo() {
        return rollNo;
    }

    public void setRollNo(int rollNo) {
        this.rollNo = rollNo;
    }
}

步骤二

创建数据访问对象接口:StudentDao.java

public interface StudentDao {
    public List<Student> getAllStudents();
    public Student getStudent(int rollNo);
    public void updateStudent(Student student);
    public void deleteStudent(Student student);
}

步骤三

创建实现了上述接口的实体类:StudentDaoImpl.java

public class StudentDaoImpl implements StudentDao {

    //列表是当作一个数据库
    List<Student> students;

    public StudentDaoImpl(){
        students = new ArrayList<Student>();
        Student student1 = new Student("Robert",0);
        Student student2 = new Student("John",1);
        students.add(student1);
        students.add(student2);
    }
    @Override
    public void deleteStudent(Student student) {
        students.remove(student.getRollNo());
        System.out.println("Student: Roll No " + student.getRollNo()
                +", deleted from database");
    }

    //从数据库中检索学生名单
    @Override
    public List<Student> getAllStudents() {
        return students;
    }

    @Override
    public Student getStudent(int rollNo) {
        return students.get(rollNo);
    }

    @Override
    public void updateStudent(Student student) {
        students.get(student.getRollNo()).setName(student.getName());
        System.out.println("Student: Roll No " + student.getRollNo()
                +", updated in the database");
    }
}

步骤四

使用StudentDao来演示数据访问对象模式的用法:DaoPatternDemo.java

public class DaoPatternDemo {
    public static void main(String[] args) {
        StudentDao studentDao = new StudentDaoImpl();

        //输出所有的学生
        for (Student student : studentDao.getAllStudents()) {
            System.out.println("Student: [RollNo : "
                    + student.getRollNo() + ", Name : " + student.getName() + " ]");
        }


        //更新学生
        Student student = studentDao.getAllStudents().get(0);
        student.setName("Michael");
        studentDao.updateStudent(student);

        //获取学生
        studentDao.getStudent(0);
        System.out.println("Student: [RollNo : "
                + student.getRollNo() + ", Name : " + student.getName() + " ]");
    }
}

结果

Student: [RollNo : 0, Name : Robert ]
Student: [RollNo : 1, Name : John ]
Student: Roll No 0, updated in the database
Student: [RollNo : 0, Name : Michael ]

进程完成,退出码 0

业务代表模式

业务代表模式(Business Delegate Pattern)用于对表示层和业务层解耦。它基本上是用来减少通信或对表示层代码中的业务层代码的远程查询功能。在业务层中有以下实体。

  • 客户端(Client:表示层代码可以是JSPservletUI java代码
  • 业务代表(Business Delegate:一个为客户端实体提供的入口类,它提供了对业务服务方法的访问。
  • 查询服务(LookUp Service:查找服务对象负责获取相关的业务实现,并提供业务对象对业务代表对象的访问。
  • 业务服务(Business Service:业务服务接口。实现了该业务服务的实体类,提供了实际的业务实现逻辑。

实现

​ 将创建 Client、BusinessDelegateBusinessServiceLookUpServiceJMSServiceEJBService 来表示业务代表模式中的各种实体。演示类BusinessDelegatePatternDemo使用 BusinessDelegateClient来演示业务代表模式的用法。

image-20200818162725678

步骤一

创建BusinessService接口:BusinessService.java

public interface BusinessService {
    public void doProcessing();
}

步骤二

创建实体服务类:EJBService.java

public class EJBService implements BusinessService{

    @Override
    public void doProcessing() {
        System.out.println("Processing task by invoking EJB Service");
    }
}

JMSService.java

public class JMSService implements BusinessService{

    @Override
    public void doProcessing() {
        System.out.println("Processing task by invoking JMS Service");
    }
}

步骤三

创建业务查询服务:BusinessLookUp.java

public class BusinessLookUp {
    public BusinessService getBusinessService(String serviceType) {
        if (serviceType.equals("EJB")) {
            return new EJBService();
        } else {
            return new JMSService();
        }
    }
}

步骤四

创建业务代表:BusinessDelegate.java

public class BusinessDelegate {
    private BusinessLookUp lookUpService = new BusinessLookUp();
    private BusinessService businessService;
    private String serviceType;

    public void setServiceType(String serviceType) {
        this.serviceType = serviceType;
    }

    public void doTask() {
        businessService = lookUpService.getBusinessService(serviceType);
        businessService.doProcessing();
    }
}

步骤五

创建客户端:Client.java

public class Client {

    BusinessDelegate businessService;

    public Client(BusinessDelegate businessService) {
        this.businessService = businessService;
    }

    public void doTask() {
        businessService.doTask();
    }
}

步骤六

使用BusinessDelegateClient类来演示业务代表模式:BusinessDelegatePatternDemo.java

public class BusinessDelegatePatternDemo {
    public static void main(String[] args) {
        BusinessDelegate businessDelegate = new BusinessDelegate();
        businessDelegate.setServiceType("EJB");

        Client client = new Client(businessDelegate);
        client.doTask();

        businessDelegate.setServiceType("JMS");
        client.doTask();
    }
}

结果

Processing task by invoking EJB Service
Processing task by invoking JMS Service

进程完成,退出码 0

组合实体模式

​ **组合实体模式(Composite Entity Pattern)**用在EJB持久化机制中。一个组合实体是一个EJB实体bean,代表了对象的图解。当更新一个组合实体时,内部依赖对象beans会自动更新,因为它们是由EJB实体bean管理的。以下是组合实体bean的参与者。

  • 组合实体(Composite Entity):它是主要的实体bean。它可以是粗粒的,或者可以包含一个粗粒度对象,用于持续生命周期。
  • 粗粒度对象(Coarse-Grained Object):该对象包含依赖对象。它有自己的生命周期,也能管理依赖对象的生命周期。
  • 依赖对象(Dependent Object):依赖对象是一个持续生命周期依赖于粗粒度对象的对象。
  • 策略(Strategies):策略表示如何实现组合实体。

实现

​ 创建作为组合实体的CompositeEntity对象。CoarseGrainedObject是一个包含依赖对象的类。演示类CompositeEntityPattenDemo使用Client类来演示组合实体模式的用法。

image-20200818163649760

步骤一

创建依赖对象:DependentObject1.java

public class DependentObject1 {

    private String data;

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }
}

DependentObject2.java

public class DependentObject2 {

    private String data;

    public void setData(String data){
        this.data = data;
    }

    public String getData(){
        return data;
    }
}

步骤二

创建粗粒度对象:CoarseGrainedObject.java

public class CoarseGrainedObject {

    DependentObject1 dependentObject1 = new DependentObject1();
    DependentObject2 dependentObject2 = new DependentObject2();

    public void setData(String data1, String data2) {
        dependentObject1.setData(data1);
        dependentObject2.setData(data2);
    }

    public String[] getData() {
        return new String[]{dependentObject1.getData(), dependentObject2.getData()};
    }
}

步骤三

创建组合实体:CompositeEntity.java

public class CompositeEntity {

    private CoarseGrainedObject coarseGrainedObject = new CoarseGrainedObject();

    public void setData(String data1, String data2) {
        coarseGrainedObject.setData(data1, data2);
    }

    public String[] getData() {
        return coarseGrainedObject.getData();
    }
}

步骤四

创建使用组合实体的客户端类:Client.java

public class Client {

    private CompositeEntity compositeEntity = new CompositeEntity();

    public void printData() {
        for (int i = 0; i < compositeEntity.getData().length; i++) {
            System.out.println("Data: "+compositeEntity.getData()[i]);
        }
    }

    public void setData(String data1, String data2) {
        compositeEntity.setData(data1, data2);
    }
}

步骤五

使用Client来演示组合实体设计模式的用法:CompositeEntityPatternDemo.java

public class CompositeEntityPatternDemo {
    public static void main(String[] args) {
        Client client = new Client();
        client.setData("Test","Data");
        client.printData();
        client.setData("Second Test","Data1");
        client.printData();
    }
}

结果

Data: Test
Data: Data
Data: Second Test
Data: Data1

进程完成,退出码 0
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

T Head

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值