设计模式基础-1

概述

在 1994 年,由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 四人合著出版了一本名为 Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用的面向对象软件元素) 的书,该书首次提到了软件开发中设计模式的概念。

==对接口编程而不是对实现编程。==

这样一个方法B在调用A的时候,不是调用的实现(A1,A2,A3),而是调用的接口(A),通过多态性,根据调用者的不同而调用不同的实现方法

==优先使用对象组合而不是继承。==

java规定只能有一个父类

设计模式的基石

  • 封装
  • 继承
  • 多态
  • 顺序
  • 判断
  • 循环

总览

  • 创建型模式(Creational Patterns)
    • 单例(Singleton)模式
    • 原型(Prototype)模式
    • 工厂方法(FactoryMethod)模式
    • 抽象工厂(AbstractFactory)模式
    • 建造者(Builder)模式
  • 结构型模式(Structural Patterns)
    • 代理(Proxy)模式
    • 适配器(Adapter)模式
    • 桥接(Bridge)模式
    • 装饰(Decorator)模式
    • 外观(Facade)模式
    • 享元(Flyweight)模式
    • 组合(Composite)模式
    • 过滤器模式(Filter Pattern)
  • 行为型模式(Behavioral Patterns)
    • 模板方法(Template Method)模式.
    • 策略(Strategy)模式
    • 命令(Command)模式
    • 职责链(Chain of Responsibility)模式
    • 状态(State)模式
    • 观察者(Observer)模式
    • 中介者(Mediator)模式
    • 迭代器(Iterator)模式
    • 访问者(Visitor)模式
    • 备忘录(Memento)模式
    • 解释器(Interpreter)模式

image-20211012093705433

设计模式的七大原则

开闭原则

(Open Closed Principle,OCP)

软件实体应当对扩展开放,对修改关闭(Software entities should be open for extension,but closed for modification)

合成复用原则、里氏替换原则相辅相成,都是开闭原则的具体实现规范

==扩展新类而不是修改旧类==

里氏替换原则

(Liskov Substitution Principle,LSP)

继承必须确保超类所拥有的性质在子类中仍然成立(Inheritance should ensure that any property proved about supertype objects also holds for subtype objects)

==继承父类而不去改变父类==

依赖倒置原则

(Dependence Inversion Principle,DIP)

高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象(High level modules shouldnot depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details. Details should depend upon abstractions)
==面向接口编程,而不是面向实现类==

单一职责原则

(Single Responsibility Principle,SRP)

一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分(There should never be more than one reason for a class to change)

==每个类只负责自己的事情,而不是变成万能==

接口隔离原则

(Interface Segregation Principle,ISP)

一个类对另一个类的依赖应该建立在最小的接口上(The dependency of one class to another one should depend on the smallest possible interface)。

==各个类建立自己的专用接口,而不是建立万能接口==

迪米特法则

(Law of Demeter,LoD)

最少知识原则(Least Knowledge Principle,LKP)
只与你的直接朋友交谈,不跟“陌生人”说话(Talk only to your immediate friends and not to strangers)

==无需直接交互的两个类,如果需要交互,使用中间者==

过度使用迪米特法则会使系统产生大量的中介类,从而增加系统的复杂性,使模块之间的通信效率降低

合成复用原则

(Composite Reuse Principle,CRP)

又叫组合/聚合复用原则(Composition/Aggregate Reuse Principle,CARP)
软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现

==优先组合,其次继承==

创建型模式-5

(Creational Patters)

概述

创建型模式关注点“怎样创建出对象?”

“将对象的创建与使用分离”。

降低系统的耦合度

使用者无需关注对象的创建细节

  • 对象的创建由相关的工厂来完成;(各种工厂模式)

  • 对象的创建由一个建造者来完成;(建造者模式)

  • 对象的创建由原来对象克隆完成;(原型模式)

  • 对象始终在系统中只有一个实例;(单例模式)

单例模式

什么是单例模式

单例模式是一种常用的软件设计模式,其定义是==单例对象的类只能允许一个实例存在==。

许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。

img

单例模式应用场景

举一个小例子,在我们的windows桌面上,我们打开了一个回收站,当我们试图再次打开一个新的回收站时,Windows系统并不会为你弹出一个新的回收站窗口。,也就是说在整个系统运行的过程中,系统只维护一个回收站的实例。这就是一个典型的单例模式运用。

继续说回收站,我们在实际使用中并不存在需要同时打开两个回收站窗口的必要性。假如我每次创建回收站时都需要消耗大量的资源,而每个回收站之间资源是共享的,那么在没有必要多次重复创建该实例的情况下,创建了多个实例,这样做就会给系统造成不必要的负担,造成资源浪费。

再举一个例子,网站的计数器,一般也是采用单例模式实现,如果你存在多个计数器,每一个用户的访问都刷新计数器的值,这样的话你的实计数的值是难以同步的。但是如果采用单例模式实现就不会存在这样的问题,而且还可以避免线程安全问题。同样多线程的线程池的设计一般也是采用单例模式,这是由于线程池需要方便对池中的线程进行控制

同样,对于一些应用程序的日志应用,或者web开发中读取配置文件都适合使用单例模式,如HttpApplication 就是单例的典型应用。

从上述的例子中我们可以总结出适合使用单例模式的场景和优缺点:

适用场景:

  • 1.需要生成唯一序列的环境
  • 2.需要频繁实例化然后销毁的对象。
  • 3.创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
  • 4.方便资源相互通信的环境

例如:

  • 多线程中的线程池
  • 数据库的连接池
  • 系统环境信息
  • 上下文(ServletContext)
  • ……

单例模式的优缺点

优点

  • 在内存中只有一个对象,节省内存空间;
  • 避免频繁的创建销毁对象,可以提高性能;
  • 避免对共享资源的多重占用,简化访问;
  • 为整个系统提供一个全局访问点。

缺点

  • 不适用于变化频繁的对象;
  • 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;
  • 如果实例化的对象长时间不被利用,系统会认为该对象是垃圾而被回收,这可能会导致对象状态的丢失;

单例模式实现

饿汉式

jvm加载类的的时候就把单例对象生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 饿汉式单例
public class Singleton1 {

// 指向自己实例的私有静态引用,主动创建
private static Singleton1 instance = new Singleton1();

// 私有的构造方法
private Singleton1(){}

// 以自己实例为返回值的静态的公有方法,静态工厂方法
public static Singleton1 getInstance(){
return instance;
}
}

我们知道,类加载的方式是按需加载,且加载一次。。因此,在上述单例类被加载时,就会实例化一个对象并交给自己的引用,供系统使用;而且,由于这个类在整个生命周期中只会被加载一次,因此只会创建一个实例,jvm底层保证了单例

优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。

缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

懒汉式

jvm加载类的的时候不生成单例对象,而是需要的时候才第一次创建,线程不安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 懒汉式单例
public class Singleton2 {

// 指向自己实例的私有静态引用
private static Singleton2 instance;

// 私有的构造方法
private Singleton2(){}

// 以自己实例为返回值的静态的公有方法,静态工厂方法
public static Singleton2 getInstance(){
// 被动创建,在真正需要使用时才去创建
if (instance == null) {
instance = new Singleton2();
}
return instance;
}
}

们从懒汉式单例可以看到,单例实例被延迟加载,即只有在真正使用的时候才会实例化一个对象并交给自己的引用。

这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。如果在多线程下,一个线程进入了if (instance== null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。

线程安全饿汉式之全锁

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton3 {

private static Singleton3 instance;

public static synchronized Singleton3 getInstance() {
// 创建单例的具体业务逻辑.....省略
if (instance == null) {
instance = new Singleton3();
}
// 创建单例的具体业务逻辑.....省略
return instance;
}
}

优点:保证了线程安全和懒加载

缺点:效率低。这种模式保证的线程安全,但是效率特别低,在高并发情况下,每次调用getInstance方法都需要加锁,显然不符合高并发情况的效率。

线程安全饿汉式之部分锁+双重判空检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Singleton4 {

private static Singleton4 instance; // 不使用volatile关键字
// 双重锁检验
public static Singleton4 getInstance() {
if (instance == null) { // 第7行
synchronized (Singleton4.class) {
//这里必须要在使用一次判空检查,在多线程情况下,可能多个线程都判断为空,进入了synchronized
if (instance == null) {
instance = new Singleton4(); // 第10行
}
}
}
return instance;
}
}

如果发生了重排序(在高并发情况下小概率出现,但是确实会出现):

1
2
3
4
5
6
7
8
9
10
11
instance = new Singleton4(); // 第10行

// 可以分解为以下三个步骤
1 memory=allocate();// 分配内存 相当于c的malloc
2 ctorInstanc(memory) //初始化对象
3 s=memory //设置s指向刚分配的地址

// 上述三个步骤可能会被重排序为 1-3-2,也就是:
1 memory=allocate();// 分配内存 相当于c的malloc
3 s=memory //设置s指向刚分配的地址
2 ctorInstanc(memory) //初始化对象

而一旦假设发生了这样的重排序,比如线程A在第10行执行了步骤1和步骤3,但是步骤2还没有执行完。这个时候另一个线程B执行到了第7行,它会判定instance不为空,然后直接返回了一个未初始化完成的instance!

优点:保证了加锁的效率,线程安全和懒加载

缺点:在高并发情况下,小概率出现实例为空(或者一个默认值)的情况

线程安全饿汉式之双重锁检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Singleton5 {

private static volatile Singleton5 instance;

// 双重锁检验
public static Singleton5 getInstance() {
if (instance == null) {
synchronized (Singleton5.class) {
//这里必须要在使用一次判空检查,在多线程情况下,可能多个线程都判断为空,进入了synchronized
if (instance == null) {
instance = new Singleton5();
}
}
}
return instance;
}
}

这里使用volatile禁止了instance = new Singleton5(); 中发生的指令重排序,保证了线程安全。

静态内部类

利用了jvm底层保证只能创建一次,保证了线程安全

类的装载机制保证初始化实例的时候只有一个线程,静态内部类在Singleton被装载时不会立即初始化,而是在调用getInstance时才会装在静态内部类,从而完成Singleton的实例化。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton6 {
public Singleton() {
}
public void doAction(){
//TODO 实现你需要做的事
}
private static class SingletonInstance{
private final static Singleton SINGLETON = new Singleton();
}
public static Singleton getInstance(){
return SingletonInstance.SINGLETON;
}
}

优点:

  1. 利用JVM加载静态内部类的机制保证多线程安全
  2. 实现Lazy loading效果
  3. 效率高

枚举实现单例模式

在effective java(这本书真的很棒)中说道,最佳的单例实现模式就是枚举模式。利用枚举的特性,让JVM来帮我们保证线程安全和单一实例的问题。除此之外,写法还特别简单。而且枚举还防止了序列化。

利用序列化也就是反射,能直接更改一个类的任意属性,但是如果使用枚举类,可以避免这种情况。因为枚举类在编译后是abstract class,是没有构造方法的,所有无法通过一个构造方法去创建一个对象,所以即使序列化后的对象也是之前的单例对象。

1
2
3
4
5
6
7
public enum Singleton7 {
INSTANCE;

public void doSomething() {
System.out.println("doSomething");
}
}

调用方法:

1
public class Main {    public static void main(String[] args) {        Singleton7.INSTANCE.doSomething();    }}

优点:

  1. 线程安全(枚举实例的创建默认就是线程安全的)
  2. 不会因为序列化而产生新实例
  3. 防止反射攻击

单例防止反射

以双重检测方式为例测试反射,序列化,克隆是否能破环单例模式:

1
public class Singleton5 implements Serializable, Cloneable   {    private static volatile Singleton5 instance;    // 双重锁检验    public static Singleton5 getInstance() {        if (instance == null) {            synchronized (Singleton5.class) {                //这里必须要在使用一次判空检查,在多线程情况下,可能多个线程都判断为空,进入了synchronized                if (instance == null) {                    instance = new Singleton5();                }            }        }        return instance;    }        @Override    protected Object clone() throws CloneNotSupportedException {        return super.clone();    }}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* @author lvxiaoyi
* @date 2021/10/11 9:20
*/
public class Singleton5Reflect {
public static void main(String[] args) throws Exception{
// 通过getInstance()获取
Singleton5 singleton = Singleton5.getInstance();
System.out.println("singleton的hashCode:"+singleton.hashCode());

// 通过反射获取
Constructor<Singleton5> constructor = Singleton5.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton5 reflex = constructor.newInstance();
System.out.println("reflex的hashCode:"+reflex.hashCode());

// 通过克隆获取
Singleton5 clob = (Singleton5)Singleton5.getInstance().clone();
System.out.println("clob的hashCode:"+clob.hashCode());

//通过序列化,反序列化获取
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(Singleton5.getInstance());

ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
Singleton5 serialize = (Singleton5)ois.readObject();
ois.close();
bis.close();
oos.close();
bos.close();
System.out.println("serialize的hashCode:"+serialize.hashCode());
}
}

1
2
3
4
singleton的hashCode:1163157884
reflex的hashCode:1956725890
clob的hashCode:356573597
serialize的hashCode:2074407503

防止反射可以通过一个变量来控制,但是反射也能修改变量,如果先修改变量在创建实例,还是不能防止反射,所以到现在为止最好的方法就是使用枚举来创建防止反射的单例模式

参考文章:https://www.cnblogs.com/call-me-pengye/p/11169051.html

单例模式面试题

系统环境信息(System.getProperties())?

Spring中怎么保持组件单例的?

ServletContext是什么(封装Servlet的信息)?是单例吗?怎么保证?

ApplicationContext是什么?是单例吗?怎么保证?

* ApplicationContext: tomcat:一个应用(部署的一个war包)会有一个应用上下文
* ApplicationContext: Spring:表示整个IOC容器(怎么保证单例的)。ioc容器中有很多组件(怎么保证单例)

数据库连接池一般怎么创建出来的,怎么保证单实例?

原型模式

什么是原型模式

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。

本体给外部提供一个克隆体进行使用

prototype1

原型模式在java中很好理解,就是java中的深拷贝(复制对象中的所有对象,全新复制体)和浅拷贝(复制对象,但是对象中的引用还是共用一个)。

浅克隆:

如果原型对象的成员变量是值类型(八大基本类型,byte,short,int,long,char,double,float,boolean).那么就直接复制,如果是复杂的类型,(枚举,String,对象)就只复制对应的内存地址。

深克隆:

全部复制,然后各自独立。你修改克隆对象对于原型对象没有丝毫影响

原型对象自己不仅是个对象还是个工厂!并且通过克隆方式创建的对象是全新的对象,它们都是有自己的新的地址,通常对克隆模式所产生的新对象进行修改是不会对原型对象造成任何影响的!,每一个克隆对象都是相对独立的,通过不同的方式对克隆对象进行修改后,可以的到一系列相似但不完全相同的对象。

img

原型模式分三个角色,抽象原型类,具体原型类,客户类。

抽象原型类(prototype):它是声明克隆方法的接口,是所有具体原型类的公共父类,它可以是接口,抽象类甚至是一个具体的实现类。

具体原型类(concretePrototype):它实现了抽象原型类中声明的克隆方法,在克隆方法中返回一个自己的克隆对象。

客户类(Client):在客户类中,使用原型对象只需要通过工厂方式创建或者直接NEW(实例化一个)原型对象,然后通过原型对象的克隆方法就能获得多个相同的对象。由于客户端是针对抽象原型对象编程的所以还可以可以很方便的换成不同类型的原型对象!

应用场景

  1. 创建对象成本比较大,比如初始化要很长时间的,占用太多CPU的,新对象可以通过复制已有的对象获得的,如果是相似的对象,则可以对其成员变量稍作修改。

  2. 系统要保存对象状态的,而对象的状态改变很小。

  3. 需要避免使用分层次的工厂类来创建分层次的对象,并且类的对象就只用一个或很少的组合状态!

优缺点

优点

  • 当创建的对象实例较为复杂的时候,使用原型模式可以简化对象的创建过程!

  • 扩展性好,由于写原型模式的时候使用了抽象原型类,在客户端进行编程的时候可以将具体的原型类通过配置进行读取。

  • 可以使用深度克隆来保存对象的状态,使用原型模式进行复制。当你需要恢复到某一时刻就直接跳到。比如我们的idea种就有历史版本,或则SVN中也有这样的操作。

缺点

  • 需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的里面,当对已有的类经行改造时需要修改源代码,违背了开闭原则。
  • 在实现深克隆的时需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用的时候,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现相对麻烦。

实现

在java中实现Cloneable,标志着能够克隆对象,然后重写clone()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package top.lvxiaoyi.creation.prototype;

/**
* 当前对象是可克隆的
*/
public class User implements Cloneable {

private String username;
private Integer age;

public User(){
System.out.println("User对象创建");
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", age=" + age +
'}';
}


/**
* 再创建一个人,赋予我的所有属性
* @return
* @throws CloneNotSupportedException
*/
@Override
protected Object clone() throws CloneNotSupportedException {
User user = new User();
user.setUsername(username);
user.setAge(age);
return user;
}
}

1
2
3
4
5
6
7
8
9
private User getUserFromDb(String username) throws Exception{
System.out.println("从数据库查到:"+username);
User user = new User();
user.setUsername(username);
user.setAge(18);
//给缓存中放一个clone,重点
userCache.put(username, (User) user.clone());
return user;
}

工厂模式(工厂方法和抽象工厂)

什么是工厂模式

工厂模式(Factory Pattern)提供了一种创建对象的最佳方式。我们不必关心对象的创建细节,只需要根据不同情况获取不同产品即可。难点:写好我们的工厂

img

工厂模式分为三种:简单工厂模式、方法工厂模式、抽象工厂模式

实现

简单工厂模式

简单工厂模式是一种创建型模式,又叫静态方法工厂模式,是通过定义一个类用来创建其他的类,被创建的类通常都具有相同的父类。简单工厂设计模式相当于是一个工厂中有各种不同的产品,创建在一个类中,调用者无需知道具体产品的名称,只需要知道产品类所对应的参数即可。但是工厂的职责过重,而且当类型过多时不利于系统的扩展维护。

代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public abstract class AbstractCar {

String engine;
public abstract void run();
}

public class MainTest {

public static void main(String[] args) {

WuLinSimpleFactory factory = new WuLinSimpleFactory();

AbstractCar van = factory.newCar(new VanCar());
AbstractCar mini = factory.newCar(new MiniCar());
AbstractCar zz = factory.newCar("zz");
van.run();
mini.run();

}
}

public class MiniCar extends AbstractCar{

public MiniCar(){
this.engine = "四缸水平对置发动机";
}

@Override
public void run() {
System.out.println(engine+"--> 嘟嘟嘟...");
}
}

public class VanCar extends AbstractCar{
public VanCar(){
this.engine = "单杠柴油机";
}

@Override
public void run() {
System.out.println(engine+"--》嗒嗒嗒....");
}
}

public class WuLinSimpleFactory {

/**
*
* @param className Class: 好像具有扩展性,但是没有解决实际问题
* @return
*/
public AbstractCar newCar(Object className){

//核心方法:一切从简
if(className instanceof VanCar){
// 钣金、喷漆、放发动机、申请环保
return new VanCar();
}else if(className instanceof MiniCar){
return new MiniCar();
}

//.....

//更多的产品,违反开闭原则。应该直接扩展出一个类来造
return null;
}
}
优点:

工厂类含有必要的判断逻辑,调用者给出信息后通过工厂类来决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的责任,而仅仅“消费”产品;简单工厂模式通过这种做法实现了对责任的分割,它提供了专门的工厂类用于创建对象,有利于整个软件体系结构的优化。

缺点:

由于工厂类集中了所有实例的创建逻辑,违反了高内聚责任分配原则,将全部创建逻辑集中到了一个工厂类中;它所能创建的类只能是事先考虑到的,如果需要添加新的类,则就需要改变工厂类了。当系统中的具体产品类不断增多时候,可能会出现要求工厂类根据不同条件创建不同实例的需求.这种对条件的判断和对具体产品类型的判断交错在一起,很难避免模块功能的蔓延,对系统的维护和扩展非常不利;

应用场景

工厂类负责创建的对象比较少,客户只知道传入了工厂类的参数,对于始何创建对象(逻辑)不关心。

工厂方法

方法工厂模式其实非常简单,和简单工厂模式差不多。简单工厂模式时同一个工厂根据你输入的类型返回不同的汽车,而工厂方法模式是通过不同的对象而创建出不同的工厂,单独生产,也就是说,以原来的工厂为模型==创建不同的工厂生产不同的商品==。

代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* 抽象工厂的层级
*/
public abstract class AbstractCarFactory {

public abstract AbstractCar newCar();
//.....
}

public class WulinMinCarFactory extends AbstractCarFactory{
@Override
public AbstractCar newCar() {
return new MiniCar();
}
}

public class WulinVanCarFactory extends AbstractCarFactory {
@Override
public AbstractCar newCar() {
return new VanCar();
}
}

public class WulinRacingCarFactory extends AbstractCarFactory{
@Override
public AbstractCar newCar() {
return new RacingCar();
}
}


public class MainTest {

public static void main(String[] args) {
AbstractCarFactory carFactory = new WulinRacingCarFactory();
AbstractCar abstractCar = carFactory.newCar();
abstractCar.run();


carFactory = new WulinVanCarFactory();
AbstractCar abstractCar1 = carFactory.newCar();

abstractCar1.run();
}
}

优点:

工厂方法模式是为了克服简单工厂模式的缺点(主要是为了满足OCP)而设计出来的。简单工厂模式的工厂类随着产品类的增加需要增加很多方法(或代码),而工厂方法模式每个具体工厂类只完成单一任务,代码简洁。工厂方法模式完全满足OCP,即它有非常良好的拓展性

缺点:

假如某个具体产品类需要进行一定的修改,很可能需要修改对应的工厂类。当同时需要修改多个产品类的时候,对工厂类的修改会变得相当麻烦。比如说,==每增加一个产品,相应的也要增加一个子工厂,会加大了额外的开发量==。

应用场景

当一个类不知道它所必须创建对象的类或一个类希望由子类来指定它所创建的对象时,当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候,可以使用工厂方法,支持多扩展少修改的OCP原则

抽象工厂模式

抽象工厂模式,提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,属于对象创建型模式,是工厂方法模式的升级版,在有多个业务品种、业务分类时,通过抽象工厂模式产生需要的对象是一种非常好的解决方式。

代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public abstract class WulinFactory {

List<String> rules;

abstract AbstractCar newCar();
abstract AbstractMask newMask();
}
public abstract class WulinCarFactory extends WulinFactory{
@Override
abstract AbstractCar newCar();

@Override
AbstractMask newMask() {
return null;
}
}
public abstract class WulinMaskFactory extends WulinFactory{
@Override
AbstractCar newCar() {
return null;
}


@Override
abstract AbstractMask newMask();
}
public class WulinHangZhouMaskFactory extends WulinMaskFactory {

@Override
AbstractMask newMask() {
return new CommonMask();
}

}public class WulinWuHanMaskFactory extends WulinMaskFactory{

@Override
AbstractMask newMask() {
return new N95Mask();
}
}
public class WulinRacingCarFactory extends WulinCarFactory {
@Override
AbstractCar newCar() {
return new RacingCar();
}
}
public class WulinVanCarFactory extends WulinCarFactory{
@Override
AbstractCar newCar() {
return new VanCar();
}

}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MainTest {

public static void main(String[] args) {

//
WulinFactory wulinFactory = new WulinWuHanMaskFactory();
AbstractCar abstractCar = wulinFactory.newCar();
//abstractCar.run();
//报错

AbstractMask abstractMask = wulinFactory.newMask();
abstractMask.protectedMe();


wulinFactory = new WulinHangZhouMaskFactory();
AbstractMask abstractMask1 = wulinFactory.newMask();
abstractMask1.protectedMe();
}
}
优点
  • 抽象工厂模式隔离了具体类的生产,使得客户并不需要知道什么被创建。
  • 当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
  • 增加新的具体工厂和产品族很方便,无须修改已有系统,符合“开闭原则”。
缺点

增加新的产品等级结构很复杂,需要修改抽象工厂和所有的具体工厂类,对“开闭原则”的支持呈现倾斜性。(不过说这个缺点好像有点吹毛求疵了)

应用场景

适用于产品工厂较多且会发生变化的的场景

  • NumberFormat、SimpleDateFormat
  • LoggerFactory:
  • SqlSessionFactory:MyBatis
  • BeanFactory:Spring的BeanFactory(就是为了造出bean)
  • ……

工厂模式的退化

当抽象工厂模式中每一个具体工厂类只创建一个产品对象,也就是只存在一个产品等级结构时,抽象工厂模式退化成工厂方法模式;当工厂方法模式中抽象工厂与具体工厂合并,提供一个统一的工厂来创建产品对象,并将创建对象的工厂方法设计为静态方法时,工厂方法模式退化成简单工厂模式。

建造者模式

是什么

创建者模式又叫建造者模式,是将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。创建者模式隐藏了复杂对象的创建过程,它把复杂对象的创建过程加以抽象,通过子类继承或者重载的方式,动态的创建具有复合属性的对象。

简而言之:创建的东西细节复杂,还必须暴露给使用者。

适用场景:

  • 隔离复杂对象的创建和使用,相同的方法,不同执行顺序,产生不同事件结果
  • 多个部件都可以装配到一个对象中,但产生的运行结果不相同
  • 产品类非常复杂或者产品类因为调用顺序不同而产生不同作用
  • 初始化一个对象时,参数过多,或者很多参数具有默认值
  • Builder模式不适合创建差异性很大的产品类
    产品内部变化复杂,会导致需要定义很多具体建造者类实现变化,增加项目中类的数量,增加系统的理解难度和运行成本
  • 需要生成的产品对象有复杂的内部结构,这些产品对象具备共性;
  • StringBuilder:append(); 给谁append呢?
  • Swagger-ApiBuilder:
  • 快速实现。Lombok-Builder模式

优缺点

Builder模式:优点是可以将构造器的setter方法名取成类似注释的方式,这样我们可以很清晰的知道刚才究竟设置的什么值,可读性较高,缺点是比较冗长。

优点

  • 使用建造者模式可以使客户端不必知道产品内部组成的细节。
  • 具体的建造者类之间是相互独立的,这有利于系统的扩展。
  • 具体的建造者相互独立,因此可以对建造的过程逐步细化,而不会对其他模块产生任何影响。

缺点

  • 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似;如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。

  • 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import lombok.*;

//@Getter
//@Setter
//@NoArgsConstructor
//@AllArgsConstructor
//@Data

// 添加builder注解后,会在当前类中生成一个静态内部类,
// 可以直接调用当前类的静态builder方法,拿到建造者,然后传递参数,最后调用build方法返回对象
@Builder
public class Phone {

protected String cpu;
protected String mem;
protected String disk;
protected String cam;

@Override
public String toString() {
return "Phone{" +
"cpu='" + cpu + '\'' +
", mem='" + mem + '\'' +
", disk='" + disk + '\'' +
", cam='" + cam + '\'' +
'}';
}

public String getCpu() {
return cpu;
}

public String getMem() {
return mem;
}

public String getDisk() {
return disk;
}

public String getCam() {
return cam;
}
}


public abstract class AbstractBuilder {

Phone phone;
abstract AbstractBuilder customCpu(String cpu);
abstract AbstractBuilder customMem(String mem);
abstract AbstractBuilder customDisk(String disk);
abstract AbstractBuilder customCam(String cam);

Phone getProduct(){
return phone;
}
}
public class XiaomiBuilder extends AbstractBuilder{


public XiaomiBuilder(){
// phone =
// new Phone();
// lombok使用builder后禁用了构造器
phone = Phone.builder().build();
}

/**
*
* @param cpu
*/
@Override
AbstractBuilder customCpu(String cpu) {
phone.cpu = cpu;
return this;
}

@Override
AbstractBuilder customMem(String mem) {
phone.mem = mem;
return this;
}

@Override
AbstractBuilder customDisk(String disk) {
phone.disk = disk;
return this;
}

@Override
AbstractBuilder customCam(String cam) {
phone.cam = cam;
return this;
}
}

public class MainTest {

public static void main(String[] args) {

AbstractBuilder builder = new XiaomiBuilder();


//建造这个手机
// builder.customMem("16G");
// builder.customDisk("1T");
// builder.customCam("2亿");
// builder.customCpu("骁龙8个8");


//链式建造者 Swagger

Phone phone = builder.customCpu("骁龙8个8")
.customCam("2亿")
.customDisk("1T")
.customMem("16G")
.getProduct();
System.out.println(phone);

// 使用builder方法的建造者模式,不需要在额外创建建造者
Phone build = Phone.builder()
.cpu("1")
.mem("2")
.cam("3")
.disk("4")
.build();

System.out.println(build);
}
}

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

  • 与抽象工厂模式相比,建造者模式返回一个组装好的完整产品,而抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族 。
  • 在抽象工厂模式中,客户端实例化工厂类,然后调用工厂方法获取所需产品对象,而在建造者模式中,客户端可以不直接调用建造者的相关方法,而是通过指挥者类来指导如何生成对象,包括对象的组装过程和建造步骤,它侧重于一步步构造一个复杂对象,返回一个完整的对象 。
  • 如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车

结构型模式-7

(Struictural Patterns)

概述

结构型模式关注点“==怎样组合对象/类==”所以我们关注下类的组合关系

类结构型模式关心类的组合,由多个类可以组合成一个更大的(继承)

对象结构型模式关心类与对象的组合,通过关联关系在一个类中定义另一个类的实例对象(组合)

根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系,因此大部分结构型模式都是对象结构型模式。

  • 适配器模式(Adapter Pattern):两个不兼容接口之间适配的桥梁
  • 桥接模式(Bridge Pattern):相同功能抽象化与实现化解耦,抽象与实现可以独立升级。
  • 过滤器模式(Filter、Criteria Pattern):使用不同的标准来过滤一组对象
  • 组合模式(Composite Pattern):相似对象进行组合,形成树形结构
  • 装饰器模式(Decorator Pattern):向一个现有的对象添加新的功能,同时又不改变其结构
  • 外观模式(Facade Pattern):向现有的系统添加一个接口,客户端访问此接口来隐藏系统的复杂性。
  • 享元模式(Flyweight Pattern):尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象
  • 代理模式(Proxy Pattern):一个类代表另一个类的功能

适配器模式

Adapter Pattern

什么是适配器模式

==将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作==,适配器模式分为类结构型模式(继承)和对象结构型模式(组合)两种,前者(继承)类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。

别名也可以是Wrapper,包装器

适配器模式(Adapter)包含以下主要角色 :

  • 目标(Target)接口:可以是抽象类或接口。客户希望直接用的接口

  • 适配者(Adaptee)类:隐藏的转换接口

  • 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口。

img

应用场景

  1. 封装有缺陷的接口设计

    假设我们依赖的外部系统在接口设计方面有缺陷(比如包含大量静态方法),引入之后会影响到我们自身代码的可测试性。为了隔离设计上的缺陷,我们希望对外部系统提供的接口进行二次封装,抽象出更好的接口设计,这个时候就可以使用适配器模式了。

  2. 统一多个类的接口设计

    某个功能的实现依赖多个外部系统(或者说类)。通过适配器模式,将它们的接口适配为统一的接口定义,然后我们就可以使用多态的特性来复用代码逻辑。

  3. 替换依赖的外部系统

    当我们把项目中依赖的一个外部系统替换为另一个外部系统的时候,利用适配器模式,可以减少对代码的改动。

  4. 兼容老版本接口

    在做版本升级的时候,对于一些要废弃的接口,我们不直接将其删除,而是暂时保留,并且标注为 deprecated,并将内部实现逻辑委托为新的接口实现。这样做的好处是,让使用它的项目有个过渡期,而不是强制进行代码修改。这也可以粗略地看作适配器模式的一个应用场景。

  5. 适配不同格式的数据

    适配器模式主要用于接口的适配,实际上,它还可以用在不同格式的数据之间的适配。比如,把从不同征信系统拉取的不同格式的征信数据,统一为相同的格式,以方便存储和使用。再比如,Java 中的 Arrays.asList() 也可以看作一种数据适配器,将数组类型的数据转化为集合容器类型。

  • Tomcat如何将Request流转为标准Request;
    • tomcat.Request接口
    • servlet.Request接口
    • tomcat === CoyoteAdapte === ServletRequest
  • Spring AOP中的AdvisorAdapter是什么:增强的适配器
    • 前置、后置、返回、结束 Advisor(通知方法)
    • 底层真的目标方法
  • Spring MVC中经典的HandlerAdapter是什么;
    HelloController.hello()
    • HandlerAdapter
    • Servlet.doGet()
  • SpringBoot 中 WebMvcConfigurerAdapter为什么存在又取消

优缺点

适配器模式有两种实现方式:类适配器和对象适配器

类适配方式:

优点

  • 高扩展性和复用率:类适配器直接借助了继承,让程序变得灵活,让被继承的类的api复用率很高。
  • 符合开闭原则,任意新建适配类,不用修改基类的代码。

缺点

  • 单继承 也就是java语言本身继承的缺点 : 单继承结构。基类中的子类无法访问到。
    ps:如果能做到这点就显得特别强大了。

对象适配(组合)方式:

  • 优点 : 相对类适配继承方式,显然访问范围更广,通过对象可以访问类中的各个属性方法。也是面向接口编程的一个思想体现。
  • 缺点 : 由于整个对象强引入,耦合度高。一旦类中方法修改,比较不好改。

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
* 1、系统原有接口 player:可以播放电影,并且返回字幕
*
*
*/
public interface Player {

String play();
}
/**
* 2、系统原有接口,可以翻译文字内容
*/
public interface Translator {
String translate(String content);
}
/**
* ZH_JP翻译器
* ZH_EN翻译器
* .....
*/
public class Zh_JPTranslator implements Translator{
@Override
public String translate(String content) {
if("你好".equals(content)){
return "空尼几哇";
}
if ("什么".equals(content)){
return "纳尼";
}
return "*******";
}
}
/**
* 电影播放器
* 阅读器
* ....
*/
public class MoviePlayer implements Player {
@Override
public String play() {
System.out.println("正在播放:宋老师的宝贵时间.avi");
String content = "你好";
System.out.println(content); //并且打印出字幕
return content;
}
}

类适配器:

继承原来的功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* 继承的方式:类结构模型,适配转换到了翻译器的功能上
*
*
*/
public class JPMoviePlayerAdapter extends Zh_JPTranslator implements Player {

private Player target;//被适配对象
public JPMoviePlayerAdapter(Player target){
this.target = target;
}

@Override
public String play() {

String play = target.play();
//转换字幕
String translate = translate(play);
System.out.println("日文:"+translate);
return play;
}
}
public class MainTest {
public static void main(String[] args) {
JPMoviePlayerAdapter adapter = new JPMoviePlayerAdapter(new MoviePlayer());
adapter.play();
}
}

对象适配器:

组合原来的功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class JPMoviePlayerAdapter implements Player {

//组合的方式
private Translator translator = new Zh_JPTranslator();
private Player target;//被适配对象
public JPMoviePlayerAdapter(Player target){
this.target = target;
}

@Override
public String play() {

String play = target.play();
//转换字幕
String translate = translator.translate(play);
System.out.println("日文:"+translate);
return play;
}
}

public class MainTest {
public static void main(String[] args) {
JPMoviePlayerAdapter adapter = new JPMoviePlayerAdapter(new MoviePlayer());
adapter.play();
}
}

桥接模式

Bridge Pattern

是什么

将抽象与实现解耦,使两者都可以独立变化

在现实生活中,某些类具有两个或多个维度的变化,如图形既可按形状分,又可按颜色分。如何设计类似于 Photoshop 这样的软件,能画不同形状和不同颜色的图形呢?如果用继承方式,m 种形状和 n 种颜色的图形就有 m×n 种,不但对应的子类很多,而且扩展困难。不同颜色和字体的文字、不同品牌和功率的汽车

桥接将继承转为关联,降低类之间的耦合度,减少代码量

桥接(Bridge)模式包含以下主要角色。
系统设计期间,如果这个类里面的一些东西,会扩展很多,这个东西就应该分离出来
抽象化(Abstraction)角色:定义抽象类,并包含一个对实现化对象的引用。
扩展抽象化(Refined Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用。

应用场景

当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。

当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。

当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。

InputStreamReader桥接模式。An InputStreamReader is a bridge from byte streams to character streams:

InputStreamReader 桥接+适配器

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public abstract class AbstractPhone {

//桥接在此.....设计期间就得想好
//【真正会引起此类变化的一个维度直接抽取出来,通过组合的方式接起来】
//桥接+适配器 ...
AbstractSale sale; //分离渠道【桥接的关注点】
// abstract int getPrice(); 如果这么写需要多少个实现。违反开闭原则


/**
* 当前手机的描述
* @return
*/
abstract String getPhone();

public void setSale(AbstractSale sale) {
this.sale = sale;
}
}

public abstract class AbstractSale {

private String type;
private Integer price;
public AbstractSale(String type,Integer price){
this.type = type;
this.price = price;
}

String getSaleInfo(){
return "渠道:"+type+"==>"+"价格:"+price;
}

void howToSale(){
//都不一样
}
}
public class IPhone extends AbstractPhone{

@Override
String getPhone() {
return "IPhone:"+sale.getSaleInfo();
}
}public class MiPhone extends AbstractPhone{
@Override
String getPhone() {
return "小米:";
}
}
public class OfflineSale extends AbstractSale{
public OfflineSale(String type, Integer price) {
super(type, price);
}
}
public class OnlineSale extends AbstractSale{
public OnlineSale(String type, Integer price) {
super(type, price);
}
}
public class StudentSale extends AbstractSale{
public StudentSale(String type, Integer price) {
super(type, price);
}
}


装饰器模式

是什么

适配器是连接两个类,可以增强一个类;装饰器是增强一个类

向一个现有的对象添加新的功能,同时又不改变其结构。属于对象结构型模式。

创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

  • 抽象构件(Component)角色:

    • 定义一个抽象接口以规范准备接收附加责任的对象。
  • 具体构件(ConcreteComponent)角色:

    • 实现抽象构件,通过装饰角色为其添加一些职责。
  • 抽象装饰(Decorator)角色:

    • 继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
  • 具体装饰(ConcreteDecorator)角色:

    • 实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。

应用场景

  • 无处不在…..

  • SpringSession中如何进行session与redis关联?HttpRequestWrapper

    • session:数据存在了内存
    • session:数据存在redis
    • HttpSession;getAttribute();
    • Wrapper(session){
      getAttribute(String param){ redis.get(param) };
      }
  • MyBatisPlus提取了QueryWrapper,这是什么?

  • Spring中的BeanWrapper是做什么?包装了Bean。bean的功能增强?

  • Spring Webflux中的 WebHandlerDecorator?

  • 已存的类,每一天在某个功能使用的时候发现不够,就可以装饰器。

优缺点

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
/**
* 抽象构建
*/
public interface ManTikTok {
void tiktok();
}
public class LvTikTok implements ManTikTok{
@Override
public void tiktok() {
System.out.println("吕小医,tiktok.... ");
}
}
/**
* 抽象装饰器
* 抖音直播装饰器
*/
public interface TiktokDecorator extends ManTikTok{

void enable();
}
/**
* 美颜装饰器
* 装饰谁?
*
* 装饰器只关系增强这个类的方法。
*/
public class MeiYanDecorator implements TiktokDecorator{

// private Framework framework; 适配器

private ManTikTok manTikTok;
public MeiYanDecorator(ManTikTok manTikTok){
this.manTikTok = manTikTok;
}

@Override
public void tiktok() {
//开启美颜
enable();
//这里如果调用被人已经写好的方法,需要调用(链接)这个方法,那么这个就是适配器模式,装饰器和适配器都属于包装模式
// framework.enableMeiYan();

//我开始直播
manTikTok.tiktok();
}


/**
* 定义的增强功能
*/
@Override
public void enable() {
System.out.println("看这个美女.....");
System.out.println("花花花花花花花花花花花");
}
}
public class ZhangTikTok implements ManTikTok{
@Override
public void tiktok() {
System.out.println("张三,tiktok.... ");
}
}
/**
* 核心:想要不改变原来接口方法的情况下扩展新功能,或者增强方法.....
*/
public class MainTest {

public static void main(String[] args) {
//被装饰对象
ManTikTok manTikTok = new LeiTikTok();
// manTikTok.tiktok();

/**
* LiMingTiktokProxy proxy = new LiMingTiktokProxy(new LeiTikTok());
* proxy.tiktok();
*/

MeiYanDecorator decorator = new MeiYanDecorator(manTikTok);
decorator.tiktok();
}
}

代理模式(静态代理和动态代理)

是什么

代理模式(Proxy Pattern) ,给某一个对象提供一个代理,并由代理对象控制对原对象的引用,对象结构型模式。这种也是静态代理

代理模式包含如下角色:

  • Subject: 抽象主体角色(抽象类或接口)
  • Proxy: 代理主体角色(代理对象类)
  • RealSubject: 真实主体角色(被代理对象类)

应用场景

  • MyBatis的mapper到底是什么?怎么生成的?
    • 动态代理
    • UserMapper、CityMapper,mybatis帮我们写实现MapperProxy
  • Alibaba Seata的DataSourceProxy是什么?
  • DruidDataSource存在的Proxy模式
  • 监控链…
  • ……

实现

代理模式分为两种:静态代理和动态代理

静态代理就是装饰器,装饰模式是代理模式的一种

静态代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public  interface ManTikTok {
void tiktok();
}
public class LvTikTok implements ManTikTok {

@Override
public void tiktok() {

}
}
public class LiMingTiktokProxy implements ManTikTok{

private ManTikTok manTikTok; //被代理对象
public LiMingTiktokProxy(ManTikTok manTikTok){
this.manTikTok = manTikTok;
}


/**
* 宋喆(代理) 宝强(被代理对象)
*/
@Override
public void tiktok() {
//增强功能
System.out.println("渲染直播间....");
System.out.println("课程只要666,仅此一天....");
manTikTok.tiktok();
}
}
public class MainTest {

public static void main(String[] args) {
LiMingTiktokProxy proxy = new LiMingTiktokProxy(new LvTikTok());
proxy.tiktok();
//静态代理就是装饰器
//装饰器模式是代理模式的一种
}
}

动态代理之Proxy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
public  interface ManTikTok {
void tiktok();
}
public class LvTikTok implements ManTikTok,SellTikTok {
// @Override
@Override
public void tiktok() {
System.out.println("吕小医,tiktok.... ");
}

@Override
public void sell() {
System.out.println("吕小医,只要666,赶紧来包...");
}

public void haha(){
System.out.println("hahaha ,......");
}
}


public class JdkTiktokProxy<T> implements InvocationHandler
private T target;
//接受被代理对象
JdkTiktokProxy(T target) {
this.target = target;
}

/**
* 获取被代理对象的 代理对象
*
* @param t
* @param <T>
* @return
*/
public static <T> T getProxy(T t) {

/**
* ClassLoader loader, 当前被代理对象的类加载器
* Class<?>[] interfaces, 当前被代理对象所实现的所有接口
* InvocationHandler h,
* 当前被代理对象执行目标方法的时候我们使用h可以定义拦截增强方法
*/
Object o = Proxy.newProxyInstance(
t.getClass().getClassLoader(),
t.getClass().getInterfaces(), //必须接口
new JdkTiktokProxy(t));
return (T) o;
}


/**
* 定义目标方法的拦截逻辑;每个方法都会进来的
*
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy,
Method method,
Object[] args) throws Throwable {

//反射执行
System.out.println("真正执行被代理对象的方法");
Object invoke = method.invoke(target, args);
System.out.println("返回值:一堆美女");
return invoke;
}
}
public class MainTest {
public static void main(String[] args) {
ManTikTok leiTikTok = new LvTikTok();

/**
* 动态代理机制。
*/
ManTikTok proxy = JdkTiktokProxy.getProxy(leiTikTok);
proxy.tiktok();

((SellTikTok)proxy).sell();

//能不能代理被代理对象本类自己的方法?proxy只能转成接口类
// ((LeiTikTok)proxy).haha();
System.out.println(Arrays.asList(proxy.getClass().getInterfaces()));

}
}

动态代理之Cglib

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class LvTikTok {

public void tiktokHaha() {
System.out.println("吕小医,tiktok.... haha....");
}
}
public class CglibTest {

public static void main(String[] args) {

//原来的对象都不用new
LvTikTok tikTok = new LvTikTok();

LvTikTok proxy = CglibProxy.createProxy(tikTok);

proxy.tiktokHaha();
}
}
public class CglibProxy {

//为任意对象创建代理
public static<T> T createProxy(T t){
//1、创建一个增强器
Enhancer enhancer = new Enhancer();

//2、设置要增强哪个个类的功能。增强器为这个类动态创建一个子类
enhancer.setSuperclass(t.getClass());

//3、设置回调
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj,
Method method, //为了能获取到原方法的一些元数据信息
Object[] args,
MethodProxy proxy) throws Throwable {
//编写拦截的逻辑
System.out.println("cglib上场le .......xxx");
//当前方法的信息
// method.get
// method.getAnnotation()
//目标方法进行执行
Object invoke = proxy.invokeSuper(obj,args);
return invoke;
}
});

Object o = enhancer.create();
return (T) o;
}

}

组合模式

组合多个对象形成树形结构以表示有整体-部分关系层次结构,组合模式可以让客户端统一对待单个对象和组合对象

把一组相似的对象当作一个单一的对象。如:树形菜单

20180824010022241

component (抽象构件:容器):它可以是接口或者抽象类,为叶子构建和子容器构建对象声明接口,在该角色中可以包含所有子类共有的行为的实现和声明。在抽象构建中定义了访问及管理它的子构件的方法,如增加子构件,删除子构件,获取子构件等。

leaf(叶子构建):叶子构建可以说就是各种类型的文件!叶子构建没有子构件。它实现了抽象构建中的定义的行为。对于那些访问子容器,删除子容器,增加子容器的就报错。

compsite(子容器构建):它在组合模式中表示容器节点对象,容器结点是子节点,可以是子容器,也可以是叶子构建,它提供一个集合来存储子节点。

应用场景

  1. 在具有整体和部分的层次结构种希望通过一种忽略整体与个体之间差异的,客户端一致对待的情况。

  2. 在一个使用面向对象语言开发的系统中需要处理一个树形结构的。

  3. 在一个系统中能分离出叶子和容器的,而且他们的类型还固定不变,需要增加一些新的类型……

优缺点

优点:

  1. 可以清楚地定义分层次的复杂类型,表示对象的全部层次或者部分层次 ,它让客户端忽略了层次的差异,方便对整个层次经行控制。
  2. 客户端可以一致的使用一个组合模式或对单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端的代码。
  3. 在组合模式种增加新的容器构件和叶子构件都很方便,无需对现有类库进行任何修改,符合开闭原则。
  4. 为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子对象和容器对象的递归组合可以形成复杂的树形机构,但对树形结构的控制却很简单。

缺点:

在增加新的构件时就比较难咯。而且难以限定,有时候希望在一个容器种能有某些特定的对象,例如在某个文件夹只能有image或者gif等。这个就比较难以实现。

实现

杀毒软件,该软件能够对某个文件夹杀毒,也可以指定对某些文件杀毒。

AbstractFile: 抽象文件类,充当抽象构建。

1
2
3
4
5
6
7

public abstract class AbstractFiles {
public abstract void add(AbstractFiles af);
public abstract void remove(AbstractFiles af);
public abstract AbstractFiles get(int i);
public abstract void killVirus();
}

叶子节点:文件类型,就写了一种。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
 public class ImageFile extends AbstractFiles {
private String name;

public ImageFile(String name) {

this.name=name;
}

@Override
public void add(AbstractFiles af) {
// TODO Auto-generated method stub
System.out.println("不支持该方法");
}

@Override
public void remove(AbstractFiles af) {
// TODO Auto-generated method stub
System.out.println("不支持该方法");
}

@Override
public AbstractFiles get(int i) {
// TODO Auto-generated method stub
System.out.println("不支持该方法");
return null;
}

@Override
public void killVirus() {
// TODO Auto-generated method stub
System.out.println("开始进行--"+name+"--文件杀毒");
}

}

文件类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class Folder extends AbstractFiles {
//文件夹类,所有的都可以用
private ArrayList<AbstractFiles> list = new ArrayList<AbstractFiles>();
private String name;
public Folder(String name) {
this.name=name;
}

@Override
public void add(AbstractFiles af) {
list.add(af);
System.out.println("添加成功");

}

@Override
public void remove(AbstractFiles af) {
// TODO Auto-generated method stub
if(list.remove(af)) {
System.out.println("删除成功");
}else{
System.out.println("删除失败");
}

}

@Override
public AbstractFiles get(int i) {
// TODO Auto-generated method stub

return list.get(i);
}

@Override
public void killVirus() {
// TODO Auto-generated method stub
System.out.println("对文件夹"+name+"进行杀毒");
//递归调用
for(Object o:list) {
((AbstractFiles)o).killVirus();

}
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Client {
public static void main(String[] args) {
//创建一个文件类型
AbstractFiles f1 = new Folder("主文件夹");
//创建文件
AbstractFiles file1= new ImageFile("孙悟空.png");
AbstractFiles file2= new ImageFile("龙珠.jpg");
AbstractFiles file3= new ImageFile("帅哥威.gif");
f1.add(file1);
f1.add(file2);
f1.add(file3);
f1.killVirus();
file1.killVirus();
}

}
1
2
3
4
5
6
7
8
添加成功
添加成功
添加成功
对文件夹主文件夹进行杀毒
开始进行--孙悟空.png--文件杀毒
开始进行--龙珠.jpg--文件杀毒
开始进行--帅哥威.gif--文件杀毒
开始进行--孙悟空.png--文件杀毒

外观模式

是什么

外观(Facade)模式又叫作门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式

20180905214139506

Facade(外观角色):这是核心类,在客户端可以调用他的方法,在外观角色中可以知道相关的子系统的功能和责任;在正常情况下,他将所有从客户端的请求委派到各个相应的子系统,传递给相应的子系统对象处理

SubSystem(子系统角色):在软件系统中可以有一个或多个子系统角色,每一个子系统可以不是一个单独的类,而是一个类的集合,它实现子系统的功能;每一个子系统都可以被客户端直接调用,或者被外观角色调用。对于子系统而言,外观类只是另一个客户。

优缺点

优点:

  • 它对客户端屏蔽了子系统的组件(可以直接调用外观模式),减少了系统的耦合度,使客户端所需要调用的所处理的类的数目减少,全让外观模式,小二去解决。简化客户端代码!
  • 它实现了系统与客户端之间的松耦合关系,子系统的修改不会影响客户端代码,只要修改外观模式就好。
  • 一个子系统的修改该对其它子系统没有任何影响,而且子系统内部变化也不会影响到外观模式。

缺点:

  • 并不能限制客户端直接调用子系统,如果客户端对子系统类做太多限制则减少了可变形和灵活性。
  • 如果设计不当,增加新的子系统就可能修改外观类的源代码,不符合开闭原则。

应用场景

1、当客户端需要调用一系列复杂子系统时候,可以提供一个简单入口的时候使用外观模式。

2、客户端与子系统有很大的耦合性,使用外观模式可以充分解耦。

3、再层次化结构中可以使用外观模式的定义系统中每一层的入口,层与层之间不直接产生联系,而通过外观类建立联系,减低层与层之间的耦合。JAVA 的三层开发模式。

分布式系统的网关

Tomcat源码中的RequestFacade干什么的?

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public class Edu {

public void assignSchool(String name){
System.out.println(name+",你的孩子明天去 硅谷大学附属幼儿园 报道......");
}
}

public class Police {

public void resgister(String name){
System.out.println(name + "已办理落户");
}
}
public class Social {

public void handleSocial(String name){
System.out.println(name+",你的社保关系已经转移....");
}
}
public class WeiXinFacade {

Police police = new Police();
Edu edu = new Edu();
Social social = new Social();

/**
* 封装起来只留一个方法
* @param name
*/
public void handleXxx(String name){
police.resgister(name);
edu.assignSchool(name);
social.handleSocial(name);
}

public void resgister(String name){
police.resgister(name);
}
public void assignSchool(String name){
edu.assignSchool(name);
}

}
public class MainTest {

public static void main(String[] args) {

// Police police = new Police();
// police.resgister("吕小医");
//
// Edu edu = new Edu();
// edu.assignSchool("吕小医");
//
// Social social = new Social();
// social.handleSocial("吕小医");

WeiXinFacade facade = new WeiXinFacade();

facade.handleXxx("吕小医");
//
// facade.resgister("");
// facade.assignSchool("");

}
}

享元模式

  • 享元模式(Flyweight Pattern),运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。对象结构型
  • 在享元模式中可以共享的相同内容称为内部状态(IntrinsicState),而那些需要外部环境来设置的不能共享的内容称为外部状态(Extrinsic State),由于区分了内部状态和外部状态,因此可以通过设置不同的外部状态使得相同的对象可以具有一些不同的特征,而相同的内部状态是可以共享的。
  • 在享元模式中通常会出现工厂模式,需要创建一个享元工厂来负责维护一个享元池(Flyweight Pool)用于存储具有相同内部状态的享元对象。

2018091222080621

享元模式包含如下角色:

  • Flyweight (享元抽象类):一般是接口或者抽象类,定义了享元类的公共方法。这些方法可以分享内部状态的数据,也可以调用这些方法修改外部状态。
  • ConcreteFlyweight(具体享元类):具体享元类实现了抽象享元类的方法,为享元对象开辟了内存空间来保存享元对象的内部数据,同时可以通过和单例模式结合只创建一个享元对象。
  • UnshareConcreteFlyweight(非共具体享元类):并不是所有的享元类都需要被共享的有的享元类就不要被共享,可以通过享元类来实例一个非共享享元对象。
  • Flyweight(享元工厂类):享元工厂类创建并且管理享元类,享元工厂类针对享元类来进行编程,通过提供一个享元池来进行享元对象的管理。一般享元池设计成键值对,或者其他的存储结构来存储。当客户端进行享元对象的请求时,如果享元池中有对应的享元对象则直接返回对应的对象,否则工厂类创建对应的享元对象并保存到享元池。

享元模式和原型模式:原型模式是返回的克隆,而享元模式返回的是本身,享元模式存在的意义是共享一个实体,怎么保证有序的拿到实体

优缺点

优点:

  • 节约系统的开销。保证一个常用的对象只有一个!
  • 外部状态不会影响内部状态,可以在不同环境下进行共享哦。

缺点:

  • 享元模式使逻辑变得更加复杂,需要将享元对象分出内部状态和外部状态。
  • 并且为了使对象可以共享,外部状态在很多情况下是必须有的,比如围棋的位置。当读取外部状态时明显会增加运行时间。

应用场景

  • 一个系统有大量细粒度化的对象,占据大量的内存。
  • 对象大部分属性可以外部化,并且能将外部的属性放入内部属性中来。
  • 使用享元模式需要维护享元池,所以要用那种常用的经常调用的对象可以使用享元模式。

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/**
* 可共享和不可共享状态
*/
public abstract class AbstractWaitressFlyweight {

boolean canService = true;//能否服务

//正在服务。 享元的不可共享属性留给外部进行改变的接口
abstract void service();
//服务完成。 享元的不可共享属性留给外部进行改变的接口
abstract void end();

public boolean isCanService() {
return canService;
}
}

import lombok.AllArgsConstructor;

/**
* 具体享元类
*/
@AllArgsConstructor
public class BeautifulWaitress extends AbstractWaitressFlyweight{
String id;//工号
String name;//名字
int age;//年龄
//以上是不变的



@Override
void service() {
System.out.println("工号:"+id+";"+name+" "+age+" 正在为您服务...");
//改变外部状态
this.canService = false;
}

@Override
void end() {
System.out.println("工号:"+id+";"+name+" "+age+" 服务结束...请给五星好评");

this.canService = true;
}
}

public class ZuDao {

private static Map<String,AbstractWaitressFlyweight> pool = new HashMap<>();
//享元,池子中有对象
static {
BeautifulWaitress waitress =
new BeautifulWaitress("1111","张三",18);


BeautifulWaitress waitress2 =
new BeautifulWaitress("9527","李四",20);


pool.put(waitress.id,waitress);
pool.put(waitress2.id,waitress2);
}

public void addWaitress(AbstractWaitressFlyweight waitressFlyweight){
pool.put(UUID.randomUUID().toString(),waitressFlyweight);
}

public static AbstractWaitressFlyweight getWaitress(String name){
AbstractWaitressFlyweight flyweight = pool.get(name);
if(flyweight == null){
for (AbstractWaitressFlyweight value : pool.values()) {
//当前共享对象能否是否
if(value.isCanService()){
return value;
}
};
return null;
}

return flyweight;

}

}
public class MainTest {

public static void main(String[] args) {


AbstractWaitressFlyweight waitress = ZuDao.getWaitress("");
waitress.service();
System.out.println(waitress);

AbstractWaitressFlyweight waitress1 = ZuDao.getWaitress("");
waitress1.service();
System.out.println(waitress1);

waitress1.end();

AbstractWaitressFlyweight waitress2 = ZuDao.getWaitress("");
System.out.println(waitress2);

}
}

行为型模式-11

(Behavioral Patterns)

概述

  • 行为型模式关注点“==怎样运行对象/类==”所以我们关注下类/对象的运行时流程控制
  • 行为型模式用于描述程序在运行时复杂的流程控制,
  • 描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
  • 行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。
  • 模板方法(Template Method)模式:父类定义算法骨架,某些实现放在子类
  • 策略(Strategy)模式:每种算法独立封装,根据不同情况使用不同算法策略
  • 状态(State)模式:每种状态独立封装,不同状态内部封装了不同行为
  • 命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开
  • 职责链(Chain of Responsibility)模式:所有处理者封装为链式结构,依次调用
  • 备忘录(Memento)模式:把核心信息抽取出来,可以进行保存
  • 解释器(Interpreter)模式:定义语法解析规则
  • 观察者(Observer)模式:维护多个观察者依赖,状态变化通知所有观察者
  • 中介者(Mediator)模式:取消类/对象的直接调用关系,使用中介者维护
  • 迭代器(Iterator)模式:定义集合数据的遍历规则
  • 访问者(Visitor)模式:分离对象结构,与元素的执行算法

除了模板方法模式和解释器模式是类行为型模式,其他的全部属于对象行为型模式

模板方法

在模板模式(Template Pattern)中,==一个抽象类公开定义了执行它的方法的方式模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行==。

模式中的角色:

抽象类(AbstractClass):实现了模板方法,定义了算法的骨架。

具体类(ConcreteClass):实现抽象类中的抽象方法,已完成完整的算法。

优缺点

优点

模板方法模式通过把不变的行为搬移到超类,去除了子类中的重复代码。

子类实现算法的某些细节,有助于算法的扩展。

通过一个父类调用子类实现的操作,通过子类扩展增加新的行为,符合“开放-封闭原则”。

缺点

每个不同的实现都需要定义一个子类,这会导致类的个数的增加,设计更加抽象。

适用场景

在某些类的算法中,用了相同的方法,造成代码的重复。

控制子类扩展,子类必须遵守算法规则。

  • Spring的整个继承体系都基本用到模板方法;
  • BeanFactory.getBean(1,2,3,4)–A1—A2—A3—A4(全部被完成).
  • JdbcTemplate、RedisTemplate都允许我们再扩展…..
  • 我们自己的系统也应该使用模板方法组织类结构
    .

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package top.lvxiaoyi.behavioral.template;

/**
* 1、定义模板
*/
public abstract class CookTemplate {

/**
* 定义算法: 定义好了模板
* 父类可以实现某些步骤
* 留关键给子类
*/
public void cook(){
//定义算法步骤
heating(); //v
addfood();
addsalt();
stirfry(); //v
end(); //v
}

//加热方法
public void heating(){
System.out.println("开火...");
};

//添加食物
public abstract void addfood();

//加盐
public abstract void addsalt();

//翻炒
public void stirfry(){
System.out.println("翻炒中...");
};

//出锅
public void end(){
System.out.println("出锅....");
};

}


public class AutoCookMachine extends CookTemplate{

@Override
public void addfood() {
System.out.println("放了三个小白菜");
}

@Override
public void addsalt() {
System.out.println("放了三勺盐");
}
}

public class MainTest {

public static void main(String[] args) {

AutoCookMachine cookMachine = new AutoCookMachine();

cookMachine.cook();

}
}

策略模式

策略(Strategy)模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。属于对象行为模式。

策略模式的主要角色如下:

  • 抽象策略(Strategy)类:公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。
  • 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现。
  • 环境(Context)类:持有一个策略类的引用,最终给客户端调用。

优缺点

优点

1、可以动态的改变对象的行为

缺点

1、客户端必须知道所有的策略类,并自行决定使用哪一个策略类

2、策略模式将造成产生很多策略类

应用场景

使用策略模式可以避免使用多重条件语句,如 if…else 语句、switch…case 语句
什么是Spring的 InstantiationStrategy
线程池拒绝策略

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public interface GameStrategy {

//战斗策略
void warStrategy();

}
public class RandomStrategy implements GameStrategy{

@Override
public void warStrategy() {
System.out.println("大乱斗...");
}

}
public class SteadyStrategy implements GameStrategy {
@Override
public void warStrategy() {
System.out.println("各路小心...及时支援...");
}
}
public class UziStrategy implements GameStrategy{
@Override
public void warStrategy() {
System.out.println("uzi.....");
}
}
public class TeamGNR {

//抽取游戏策略算法,并进行引用
private GameStrategy gameStrategy;

public void setGameStrategy(GameStrategy gameStrategy) {
this.gameStrategy = gameStrategy;
}

public void startGame(){
System.out.println("游戏开始.....");
//游戏策略
gameStrategy.warStrategy();
System.out.println("win......");
}
}
public class MainTest {

public static void main(String[] args) {

TeamGNR gnr = new TeamGNR();

gnr.setGameStrategy(new RandomStrategy());
gnr.startGame();
}
}

状态模式

状态(State)模式:对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。

状态模式包含以下主要角色:

  • 环境类(Context)角色:也称为上下文,它定义了客户端需要的接口,内部维护一个当前状态,并负责具体状态的切换。
  • 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为,可以有一个或多个行为。
  • 具体状态(Concrete State)角色:实现抽象状态所对应的行为,并且在需要的情况下进行状态切换。

总结:
使用状态模式前,客户端外界需要介入改变状态,而状态改变的实现是琐碎或复杂的。
使用状态模式后,客户端外界可以直接使用事件Event实现,根本不必关心该事件导致如何状态变化,这些是由状态机等内部实现。
这是一种Event-condition-State,状态模式封装了condition-State部分。
每个状态形成一个子类,每个状态只关心它的下一个可能状态,从而无形中形成了状态转换的规则。如果新的状态加入,只涉及它的前一个状态修改和定义。

优缺点

优点

在于封装了转换规则,并枚举可能的状态,它将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为,还可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数;

缺点

缺点在于使用状态模式会增加系统类和对象的个数,且状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱,对于可以切换状态的状态模式不满足“开闭原则”的要求。

应用场景

状态模式在工作流或游戏等各种系统中有大量使用,甚至是这些系统的核心功能设计,例如ERP系统中,一个批文的状态有多种:未办;正在办理;正在批示;正在审核;已经完成等各种状态,使用状态机可以封装这个状态的变化规则,从而达到扩充状态时,不必涉及到状态的使用者。

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
public interface TeamState {
//玩游戏
void playGame();
//切换到下一个状态
TeamState next();
}
public class BeafNodleState implements TeamState {

@Override
public void playGame() {
System.out.println("饱饱的一顿牛肉面......中了诅咒,输了");
}

@Override
public TeamState next() {
return new MatchState();
}
}
public class MatchState implements TeamState{
@Override
public void playGame() {
System.out.println("全力以赴打比赛....");
}


//状态模式的核心
@Override
public TeamState next() {
return new VocationState();
}
}
public class SKTTeam {


private TeamState teamState;

public void setTeamState(TeamState teamState) {
this.teamState = teamState;
}

//开始游戏
public void startGame(){
//状态不同会导致不同的游戏结果
teamState.playGame();
}


//下一个状态
void nextState(){
teamState = teamState.next();
}
}
public class MainTest {

public static void main(String[] args) {

SKTTeam sktTeam = new SKTTeam();
TeamState state = new VocationState();
sktTeam.setTeamState(state);
sktTeam.startGame();


// sktTeam.startGame();
//
// sktTeam.nextState();

// sktTeam.startGame();
//
// sktTeam.nextState();
// sktTeam.startGame();

state = state.next();
sktTeam.setTeamState(state);
sktTeam.startGame();

//状态需要维护自己的切换逻辑
state = state.next();
sktTeam.setTeamState(state);
sktTeam.startGame();
}
}

中介者模式

中介者模式(Mediator Pattern):用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,减少对象间混乱的依赖关系,从而使其耦合松散,而且可以独立地改变它们之间的交互。对象行为型模式。

图片1

  • Mediator: 抽象中介者
  • ConcreteMediator: 具体中介者
  • Colleague: 抽象同事类
  • ConcreteColleague: 具体同事类

优缺点

应用场景

  • 当对象之间存在复杂的网状结构关系而导致依赖关系混乱且难以复用时。
  • 当想创建一个运行于多个类之间的对象,又不想生成新的子类时。
  • SpringMVC 的 DispatcherServlet是一个中介者,他会提取Controller、Model、View来进行调用。而无需controller直接调用view之类的渲染方法
  • 分布式系统中的网关
  • 迪米特法则的一个典型应用
  • 中介者和外观(门面)模式区别?
  • 中介者双向操作,门面偏向于封装某一方
  • …….

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
public abstract class Captain {

//起飞
abstract void fly();
//降落
abstract void land();
//完成
abstract void success();

}
public class ControlTower {

private boolean canDo = true;

//接受请求...
public void acceptRequest(Captain captain,String action){
if("fly".equals(action)||"land".equals(action)){
//
if(canDo == true){
System.out.println("允许......");
canDo = false;
}else {
System.out.println("不允许.....");
}
}
if("success".equals(action)){
canDo = true;
}
}
}
public class HU8778 extends Captain{

ControlTower controlTower;

public void setControlTower(ControlTower controlTower) {
this.controlTower = controlTower;
}

@Override
void fly() {
System.out.println("HU8778请求起飞......");
//问每个机长能否起飞?
controlTower.acceptRequest(this,"fly");
}

@Override
void land() {
System.out.println("HU8778请求降落......");
controlTower.acceptRequest(this,"land");
}

@Override
void success() {
System.out.println("完成......");
controlTower.acceptRequest(this,"success");
}
}
public class SC8633 extends Captain{
ControlTower controlTower ;

public void setControlTower(ControlTower controlTower) {
this.controlTower = controlTower;
}

@Override
void fly() {
System.out.println("SC8633 请求起飞......");
//问每个机长能否起飞?
controlTower.acceptRequest(this,"fly");

}

@Override
void land() {

System.out.println("SC8633 请求降落......");
//问每个机长能否起飞?
controlTower.acceptRequest(this,";land");
}

@Override
void success() {

System.out.println("SC8633 完成......");
//问每个机长能否起飞?
controlTower.acceptRequest(this,"fly");
}
}
public class XC9527 extends Captain{
@Override
void fly() {
System.out.println("XC9527请求起飞....");
}

@Override
void land() {
System.out.println("XC9527请求降落....");
}

@Override
void success() {
System.out.println("XC9527请求降落....");
}
}

观察者模式

观察者模式(Observer Pattern):定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。对象行为型模式

Subject: 目标
ConcreteSubject: 具体目标
Observer: 观察者
ConcreteObserver: 具体观察者

优缺点

优点

  • 观察者和被观察者是抽象耦合的。

  • 建立一套触发机制。

缺点

  • 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。

  • 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。

  • 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

应用场景

  1. 当一个抽象模型有两个方面, 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
  2. 当对一个对象的改变需要同时改变其它对象, 而不知道具体有多少对象有待改变。
  3. 当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之, 你不希望这些对象是紧密耦合的。
  • Spring事件机制如何实现?
  • Vue的双向绑定核心
  • 响应式编程核心思想

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public abstract class AbstractFans {

List<AbstractTikToker> tikTokers;//双向观察

abstract void acceptMsg(String msg);

void follow(AbstractTikToker tikToker){
tikToker.addFans(this);
};

}
public abstract class AbstractTikToker {

//添加粉丝
abstract void addFans(AbstractFans fans);

//通知粉丝
abstract void notifyFans(String msg);
}
public class HumanFans extends AbstractFans {
@Override
void acceptMsg(String msg) {
System.out.println("主播说:"+msg);
}
}
public class MMTikToker extends AbstractTikToker{

//1、观察者的核心1
List<AbstractFans> fansList = new ArrayList<>();

void startSell() {
System.out.println("雷丰阳... 开始卖货...源码设计课");
notifyFans("我开始卖东西了,是源码设计课,只要666");
}
void endSell() {
System.out.println("雷丰阳... 结束卖货...源码设计课");
notifyFans("课已经卖完了,记得五星好评...");
}


@Override
void addFans(AbstractFans fans) {
fansList.add(fans);
}

//通知所有观察者
@Override
void notifyFans(String msg) {
//1、所有粉丝拿来通知
for (AbstractFans fans : fansList) {
fans.acceptMsg(msg);
}
}
}public class RobotFans extends AbstractFans {
@Override
void acceptMsg(String msg) {
System.out.println("呸....");
}
}

备忘录模式

备忘录(Memento)模式:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。该模式又叫快照模式。对象行为型模式

beiwanglu

发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。

优缺点

优点

  • 它提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原。

  • 备忘录实现了对信息的封装,一个备忘录对象是一种原发器对象状态的表示,不会被其他代码所改动。备忘录保存了原发器的状态,采用列表、堆栈等集合来存储备忘录对象可以实现多次撤销操作。

缺点

  • 资源消耗过大,如果需要保存的原发器类的成员变量太多,就不可避免需要占用大量的存储空间,每保存一次对象的状态都需要消耗一定的系统资源。

应用场景

  • 保存一个对象在某一个时刻的全部状态或部分状态,这样以后需要时它能够恢复到先前的状态,实现撤销操作。
  • 防止外界对象破坏一个对象历史状态的封装性,避免将对象历史状态的实现细节暴露给外界对象。

例如:

  • session的活化钝化;

  • 浏览器回退:浏览器一般有浏览记录,当我们在一个网页上点击几次链接之后,可在左上角点击左箭头回退到上一次的页面,然后也可以点击右箭头重新回到当前页面

  • 数据库备份与还原:一般的数据库都支持备份与还原操作,备份即将当前已有的数据或者记录保留,还原即将已经保留的数据恢复到对应的表中

  • 编辑器撤销与重做:在编辑器上编辑文字,写错时可以按快捷键 Ctrl + z 撤销,撤销后可以按 Ctrl + y 重做

  • 虚拟机生成快照与恢复:虚拟机可以生成一个快照,当虚拟机发生错误时可以恢复到快照的样子

  • Git版本管理:Git是最常见的版本管理软件,每提交一个新版本,实际上Git就会把它们自动串成一条时间线,每个版本都有一个版本号,使用 git reset –hard 版本号 即可回到指定的版本,让代码时空穿梭回到过去某个历史时刻

  • 棋牌游戏悔棋:在棋牌游戏中,有时下快了可以悔棋,回退到上一步重新下

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
public class GameServer {

//管理备忘录信息的
Map<Integer,GameRecord> records = new HashMap<>();
int i = 1;

void add(GameRecord gameRecord){
gameRecord.setId(i++);
records.put(gameRecord.id,gameRecord);
}

LvGamer getRecord(Integer id) throws Exception {
GameRecord gameRecord = records.get(id);
//获取到备忘录里面的内容以后还要逆转

LvGamer lvGamer = new LvGamer();
// leiGamer.setCoin(gameRecord.getCoin());

//BeanUtils:工具类,属性对拷
BeanUtils.copyProperties(lvGamer,gameRecord);

return lvGamer;
}

}

@Data
public class GameRecord {
Integer id; //代表生成记录的id
Integer coin;//剩余金币
Integer hp;//血量
Integer mp;//蓝量
Integer level;//等级


//获取当前备忘录信息
void getCurrent(){

System.out.println("coin:"+coin+";\t"+"hp:"+hp+";\t mp:"+mp);
}

}

@Data
public class LvGamer {
Integer coin;//剩余金币
Integer hp;//血量
Integer mp;//蓝量
Integer level;//等级
//以上的是内部状态,我们需要记录保存的信息

GameServer gameServer = new GameServer();




//保存游戏记录
void saveGameRecord() throws Exception {
System.out.println("正在保存当前记录....");
GameRecord gameRecord = new GameRecord();
//当前游戏信息保存到备忘录
BeanUtils.copyProperties(gameRecord,this);
//
gameServer.add(gameRecord);
}

//从备忘录获取游戏历史存档
LvGamer getFromMemento(Integer id) throws Exception {
System.out.println("获取历史存档信息....");
LvGamer record = gameServer.getRecord(id);
return record;
}

//玩游戏
void playGame(){
int i = new Random().nextInt();
System.out.println("......(〃'▽'〃)......"+i);

coin = i;
hp = i;
mp = i;
level = i;


}

//退出游戏
void exitGame() throws Exception {
System.out.println("退出&存档");
saveGameRecord();
}

}

public class MainTest {

public static void main(String[] args) throws Exception {

LvGamer lvGamer = new LvGamer();

lvGamer.playGame();

//第一次保存
lvGamer.saveGameRecord();

lvGamer.playGame();

lvGamer.playGame();

lvGamer.saveGameRecord();


LvGamer fromMemento = lvGamer.getFromMemento(1);

fromMemento.playGame();


}
}

解释器模式

解释器(Interpreter)模式:给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。也就是说,用编译语言的方式来分析应用中的实例。这种模式实现了文法表达式处理的接口,该接口解释一个特定的上下文。类行为型模式

抽象表达式(Abstract Expression)角色:

定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。

终结符表达式(Terminal Expression)角色:

是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。

非终结符表达式(Nonterminal Expression)角色:

也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。

环境(Context)角色:

通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。

客户端(Client):

主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。

优缺点

优点

  • 易于改变和扩展文法。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
  • 每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言。
  • 实现文法较为容易。在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂,还可以通过一些工具自动生成节点类代码。
  • 增加新的解释表达式较为方便。如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式类,原有表达式类代码无须修改,符合 “开闭原则”。

缺点

  • 对于复杂文法难以维护。在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护,此时可以考虑使用语法分析程序等方式来取代解释器模式。
  • 执行效率较低。由于在解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦。

应用场景

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

例如:

  • Spring EL表达式中的解释器模式
  • Thymeleaf等模板引擎的语法解析
  • 编译原理

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
public abstract class IDCardExpression {

/**
* 定义解析逻辑
* 假设我们需要解析的信息格式为:
* 上海市:张文宏-医生
* 武汉市:雷丰阳-程序员
* 北京市:宋宋-老人
* 表达式中,“:”以前的是城市,“-”以后的是职业
* @param expression
* @return
*/
abstract boolean interpret(String expression);

}
public class TerminalExpression extends IDCardExpression {

IDCardExpression childExp;

Set<String> data; //免费数据
String symbol; //定义解析用的符号如 : -

public TerminalExpression( Set<String> data,String symbol){
this.data = data;
this.symbol = symbol;
}

@Override
boolean interpret(String expression) {

String[] split = expression.split(symbol);

boolean result = false;
for (String s : split) {
if(data.contains(s)){
return true;
};//说明是免费的信息里面的
}

//可以继续子解析
// childExp.interpret(expression);

//不在免费行列
return false;
}
}
public class OrExpression extends IDCardExpression {

//组合两个终结表达式。最终的判断结果是终结表达式判断出来的,这个表达式只是一个桥梁
private IDCardExpression cityExp;
private IDCardExpression typeExp;

public OrExpression(IDCardExpression cityExp, IDCardExpression typeExp) {
this.cityExp = cityExp;
this.typeExp = typeExp;
}

@Override
boolean interpret(String expression) {

//定义所有终结表达式的合并逻辑
return cityExp.interpret(expression) || typeExp.interpret(expression);
}
}
public class Area {
Set<String> city = new HashSet<>();


Set<String> type = new HashSet<>();
IDCardExpression idCardReader;//读卡器,表达式解析器

//环境类持有表达式

public Area(){
// {"武汉市","上海市"};//免费城市
// {"医生","老人","儿童"};//免费人群
city.add("武汉市");
city.add("上海市");

type.add("医生");
// type.add("老人");
type.add("儿童");
//最小解析
TerminalExpression city =
new TerminalExpression(this.city, ":");
//最小解析
TerminalExpression type =
new TerminalExpression(this.type, "-");


//以上满足一个即可
idCardReader = new OrExpression(city,type);

}

/**
* 传入表达式,判断当前表达式是否指定为免费人群
* @param expression
*/
void getTicket(String expression){
boolean interpret = idCardReader.interpret(expression);
if(interpret){
System.out.println("恭喜你,免票通过....");
}else {
System.out.println("对不起,请2元购票....");
}
}
}
public class MainTest {

public static void main(String[] args) {

Area area = new Area();
String sr = "吕小医";
area.getTicket(sr);

}
}

命令模式

命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。

img

命令模式包含以下主要角色:

  • 抽象命令类(Command)角色:
    • 声明执行命令的接口,拥有执行命令的抽象方法 execute()。
  • 具体命令类(Concrete Command)角色:
    • 是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。
  • 实现者/接收者(Receiver)角色:
    • 执行命令功能的相关操作,是具体命令对象业务的真正实现者。
  • 调用者/请求者(Invoker)角色:
    • 是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。

优缺点

优点

  • 类间解耦:调用者角色与接收者角色之间没有任何依赖关系,调用者实现功能时只需调用Command 抽象类的execute方法就可以,不需要了解到底是哪个接收者执行。
  • 可扩展性:Command的子类可以非常容易地扩展,而调用者Invoker和高层次的模块Client不产生严 重的代码耦合。
  • 命令模式结合其他模式会更优秀:命令模式可以结合责任链模式,实现命令族解析任务;结合模板方法模式,则可以减少 Command子类的膨胀问题。

缺点

命令模式也是有缺点的,请看Command的子类:如果有N个命令,问题就出来 了,Command的子类就可不是几个,而是N个,这个类膨胀得非常大,这个就需要读者在项 目中慎重考虑使用。

应用场景

对于大多数请求-响应模式的功能,比较适合使用命令模式,正如命令模式定义说的那样,命令模式对实现记录日志、撤销操作等功能比较方便。

mvc就是典型的命令模式。

当系统需要执行一组操作时,命令模式可以定义宏命令(一个命令组合了多个命令)来实现该功能。

结合备忘录模式还可以实现命令的撤销和恢复。

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public interface  Command {
/**
* 命令的执行方法
*/
void execute();
}
/**
* 命令接受者(执行者)。
*/
public class LvReceiver {

public void online(){
System.out.println("leifengyang 在线上直播大厂课......");
}
public void travel(){
System.out.println("leifengyang 出差.....");
}
}
public class OnlineCommand implements Command{

//Dao
private LvReceiver receiver = new LvReceiver();
@Override
public void execute() {
System.out.println("要去上(吹)课(牛)....");
receiver.online();
}
}
public class WuHanTravelCommand implements Command{

private LvReceiver receiver = new LvReceiver();

@Override
public void execute() {
System.out.println("我要出(旅)差(游)....");
receiver.travel();
}
}
public class TeacherTongInvoker {
Command command;

public void call(){
//命令
command.execute();
}

public void setCommand(Command command) {
this.command = command;
}
}
public class MainTest {
public static void main(String[] args) {
// LeiReceiver leiReceiver = new LeiReceiver();
// leiReceiver.travel();
TeacherTongInvoker invoker = new TeacherTongInvoker();
invoker.setCommand(new OnlineCommand());
invoker.call();
}
}

迭代器模式

迭代器(Iterator)模式:提供一个对象(迭代器)来顺序访问聚合对象(迭代数据)中的一系列数据,而不暴露聚合对象的内部表示。对象行为型模式

1338213169_8415

  • 抽象聚合(Aggregate)角色:
    • 定义存储、添加、删除聚合对象以及创建迭代器对象的接口。
  • 具体聚合(ConcreteAggregate)角色:
    • 实现抽象聚合类,返回一个具体迭代器的实例。
  • 抽象迭代器(Iterator)角色:
    • 定义访问和遍历聚合元素的接口,通常包含 hasNext()、first()、next() 等方法。
  • 具体迭代器(Concretelterator)角色:
    • 实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。

优缺点

优点

  • 它支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式。在迭代器模式中只需要用一个不同的迭代器来替换原有迭代器即可改变遍历算法,我们也可以自己定义迭代器的子类以支持新的遍历方式。
  • 迭代器简化了聚合类。由于引入了迭代器,在原有的聚合对象中不需要再自行提供数据遍历等方法,这样可以简化聚合类的设计。
  • 在迭代器模式中,由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无须修改原有代码,满足 “开闭原则” 的要求。

缺点

  • 由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。
  • 抽象迭代器的设计难度较大,需要充分考虑到系统将来的扩展,例如JDK内置迭代器Iterator就无法实现逆向遍历,如果需要实现逆向遍历,只能通过其子类ListIterator等来实现,而ListIterator迭代器无法用于操作Set类型的聚合对象。在自定义迭代器时,创建一个考虑全面的抽象迭代器并不是件很容易的事情。

应用场景

  • 访问一个聚合对象的内容而无须暴露它的内部表示。将聚合对象的访问与内部数据的存储分离,使得访问聚合对象时无须了解其内部实现细节。
  • 需要为一个聚合对象提供多种遍历方式。
  • 为遍历不同的聚合结构提供一个统一的接口,在该接口的实现类中为不同的聚合结构提供不同的遍历方式,而客户端可以一致性地操作该接口。

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/**
* 抽象聚合类
*/
public abstract class BeautifulMan {

//不方便暴露给外界的集合。只允许外界获取而不可以操作
private List<String> girlFriends = new ArrayList<>();

void likeYou(String name){
girlFriends.add(name);
};
void sayBye(String name){
girlFriends.remove(name);
};

/**
* 获取迭代器
* @return
*/
public Itr getIterator(){
return new Iterator();
}

/**
* 具体迭代器
*/
class Iterator implements Itr{

private int cursor = 0; //当前指针


@Override
public boolean hasNext(){
return cursor < girlFriends.size();
}

@Override
public String next(){
//第一次调用返回第一个数据
//下一次再调用自动访问下一个数据
String s = girlFriends.get(cursor);
cursor++;
return s;
}

@Override
public String firstLove() {
return girlFriends.get(0);
}

@Override
public String current() {
return girlFriends.get(girlFriends.size()-1);
}
}


/**
* 抽象迭代器,写在外部该怎么写?
*/
interface Itr {
//有没有下一个
boolean hasNext();
//返回下一个
String next();

//返回初恋(第一个)
String firstLove();

//返回现任(最后一个女朋友)
String current();
}

}

public class MaYuCheng extends BeautifulMan{
}

public class MainTest {

public static void main(String[] args) {

MaYuCheng cheng = new MaYuCheng();

cheng.likeYou("王刚");
cheng.likeYou("李强");
cheng.likeYou("赵根");

BeautifulMan.Itr itr = cheng.getIterator();
String s = itr.firstLove();
System.out.println(s);

String current = itr.current();
System.out.println(current);

System.out.println("=================");
while (itr.hasNext()){
String next = itr.next();
System.out.println(next);
}
}
}

总结

创建型模式

  • ==单例模式==:保证一个对象
  • 原型模式:快速克隆一个自己
  • ==工厂方法模式:==
    • 简单工厂(一个方法):if-else判断创造对象(抽象父类传递)。产品线单一
    • 工厂方法(很多方法):每个产品一个方法
  • ==抽象工厂模式:==
    • 很多工厂+很多方法
  • ==建造者模式:==每一个建造细节都可以传入,但是这些细节都不是必须的,如果是必须就是用构造器

构造型模式

  • ==适配器模式:==连接两个不同的接口
  • 桥接模式:连接同一个东西的两个不同纬度
  • ==代理模式:==动态代理
  • ==装饰模式:==静态代理=装饰。被装饰的对象传入。
  • ==外观模式:==把多个类的方法,合成到一个类里面引用。
  • 享元模式:将原对象通过池技术共享
  • ==组合模式:==a里面属性有b对象就是组合
  • 过滤器模式:

行为型模式

  • ==模板方法模式:==父类定义一个大方法,里面调用一堆小方法步骤,小方法的步骤由子类实习
  • ==策略模式:==每一种算法封装成一个策略。
  • 命令模式:Controller、service、do
  • ==责任链模式:==Fileter、维护链条+游标+递归
  • ==状态模式:==和策略模式很像,状态要维护状态流转
  • ==观察者模式:==把所有观察者list起来,然后foreach
  • 中介者模式:所有类都引入一个类的对象,通过这一个对象调用
  • 迭代器模式:自定义遍历算法。自己本类的迭代器遍历自己本类里面的集合
  • 访问者模式:小爱机器人,cpu、disk要能够被升级包升级
  • 备忘录模式:把复杂对象的关键属性抽取出来进行保存
  • 解释器模式:语法分析,大解析器组合了很多小解释器,进行语法解析