设计模式

设计模式概念:模式是一套被反复使用、多数人知晓的、经过分类编写的、成功代码设计经验的总结;它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。

设计模式的作用

  • 可以提高程序员的思维能力、编程能力和设计能力。
  • 使程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期。
  • 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。

设计模式类别

  • 创建型模式(5种):工厂方法模式抽象工厂模式单例模式,建造者模式,原型模式。

  • 结构型模式(7种):适配器模式,装饰器模式,代理模式,外观模式,桥接模式,组合模式,享元模式。

  • 行为型模式(11种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

设计模式7个原则

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

    问题:在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。

    解决方案:当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。

  • 里氏代换原则(Liskov Substitution Principle)

  • 依赖倒转原则(Dependency Inversion Principle) 这个是开闭原则的基础,对接口编程,依赖于抽象而不依赖于具体。高层模块不应该依赖低层模块,两者都应该依赖其抽象,抽象不应该依赖细节,细节应该依赖抽象

    问题 :类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。

    解决方案:将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。

  • 接口隔离原则(Interface Segregation Principle)使用多个隔离的接口来降低耦合度。

    问题:类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类B来说不是最小接口,则类B和类D必须去实现他们不需要的方法。

    解决方案:将臃肿的接口I拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。

  • 迪米特法则(最少知道原则)(Demeter Principle)一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。

    问题:类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。

    解决方法:尽量降低类与类之间的耦合。

  • 合成复用原则(Composite Reuse Principle)则是尽量使用合成/聚合的方式,而不是使用继承。继承实际上破坏了类的封装性,超类的方法可能会被子类修改。

    问题:B类如果继承了A类,A类可继承方法m的实现细节暴露给B类,如果A类发生方法m改变,那么B的实现也不得不发生改变。

    解决方法:使用合成或者聚合,不要使用继承

  • 单一职责原则(Single responsibility principle)个类只负责一个功能领域的响应职责。 如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。另外,多个职责耦合在一起,会影响复用性。

    问题:比如一个类T负责两个不同的职责:职责P1,职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。

    解决方法:遵循单一职责原则。分别建立两个类T1、T2,使T1完成职责P1功能,T2完成职责P2功能。这样,当修改类T1时,不会使职责P2发生故障风险;同理,当修改T2时,也不会使职责P1发生故障风险。

工厂模式

工厂模式(Factory Method)工厂模式属于创建型设计模式,它提供了一种创建对象的最佳方式。隐藏复杂的逻辑处理过程, 只关心执行结果。直接用new可以完成的不需要用工厂模式.需要生成复杂对象的地方使用

静态工厂模式

概念:在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类或者接口。

优点:功能强大,对象创建和使用分离,程序员可以只关心对象使用,不用关心对象如何创建

缺点:耦合度高(所有产品都在工厂创建,一旦异常其他产品也受影响),扩展性不强(每次添加一个产品,工厂类都要变化),违背开闭原则

定义接口

1
2
3
4
5
6
public interface SoftwareTechnology {
/**
* 学习软件技术
*/
void studyST();
}

编写实现类

1
2
3
4
5
6
public class JavaDevTechnology implements SoftwareTechnology {
@Override
public void studyST() {
System.out.println("头悬梁,锥刺股,努力学习java技术,当上CTO,迎娶白富美(高富帅),走向人生巅峰!");
}
}
1
2
3
4
5
6
public class PythonDevTechnology implements SoftwareTechnology{
@Override
public void studyST() {
System.out.println("头悬梁,锥刺股,努力学习Python技术,当上CTO,迎娶白富美(高富帅),走向人生巅峰!");
}
}
1
2
3
4
5
6
public class UIDevTechnology implements SoftwareTechnology {
@Override
public void studyST() {
System.out.println("头悬梁,锥刺股,努力学习UIDev技术,当上CTO,迎娶白富美(高富帅),走向人生巅峰!");
}
}

创建工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
public class AAAFactory {
public SoftwareTechnology teachST(int type){
if(type==1){
return new JavaDevTechnology();
}else if(type==2){
return new UIDevTechnology();
}else if(type==3){
return new PythonDevTechnology();
}else {
return null;
}
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
public class FactoryTest {
public static void main(String[] args) {
AAAFactory aaaFactory = new AAAFactory();
SoftwareTechnology softwareTechnology = aaaFactory.teachST(2);
if(softwareTechnology!=null){
softwareTechnology.studyST();
}else {
System.out.println("没有这门技术对应的编号!");
}
}
}

工厂(方法)模式

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

概念解释: 工厂本身不再创建产品,而是规定了工厂规范,即工厂接口,而将产品创建都交给子工厂创建。

优点:遵循了开闭原则(不需要修改工厂类,就可以增加产品)解耦,职责单一(每个工厂只负责创建对应的产品)

缺点: 增加系统复杂度(每新加一个产品需要新加一个工厂)

定义工厂接口

1
2
3
public interface  AAAStanderedFactory {
SoftwareTechnology teachST();
}

编写工厂实现类

1
2
3
4
5
6
public class ChenduAAAFactory implements  AAAStanderedFactory{
@Override
public SoftwareTechnology teachST() {
return new UIDev();
}
}
1
2
3
4
5
6
public class ShenzhenAAAFactory implements AAAStanderedFactory {
@Override
public SoftwareTechnology teachST() {
return new BigDataDev();
}
}
1
2
3
4
5
6
public class UIDev implements SoftwareTechnology {
@Override
public void studyST() {
System.out.println("头悬梁,锥刺股,努力学习UI技术,当上CTO,迎娶白富美(高富帅),走向人生巅峰!");
}
}
1
2
3
4
5
6
public class BigDataDev implements SoftwareTechnology {
@Override
public void studyST() {
System.out.println("头悬梁,锥刺股,努力学习bigData技术,当上CTO,迎娶白富美(高富帅),走向人生巅峰!");
}
}

测试

1
2
3
4
5
6
7
8
9
10
public class MethodTest {
public static void main(String[] args) {
ChenduAAAFactory chenduAAAFactory =new ChenduAAAFactory();
SoftwareTechnology softwareTechnology = chenduAAAFactory.teachST();
softwareTechnology.studyST();
ShenzhenAAAFactory shenzhenAAAFactory =new ShenzhenAAAFactory();
SoftwareTechnology softwareTechnology1 = shenzhenAAAFactory.teachST();
softwareTechnology1.studyST();
}
}

抽象工厂

概念:抽象工厂是工厂方法的升级版,为相关或者相互依赖的对象提供一个接口,而且无须指定他们的具体实现类。

概念解释:抽象工厂模式相对于工厂方法模式来说,就是工厂方法模式是针对一个产品系列的,而抽象工厂模式是针对多个产品系列的,即工厂方法模式是一个产品系列一个工厂类,而抽象工厂模式是多个产品系列一个工厂类

优点:当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。

缺点:难以支持新种类的产品。因为抽象工厂接口确定了可以被创建的产品集合,所以难以扩展抽象工厂以生产新种类的产品。

定义抽象接口(包括简单和工厂方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface AAAAbstractFactory {

/**
* 教授软件技术
* @return
*/
SoftwareTechnology teachST();

/**
* 教授挖掘机技术
* @return
*/
DiggerTechnology teachDT();

}

定义新的产品系列接口

1
2
3
4
5
6
7
public interface DiggerTechnology {

/**
* 学习挖掘机技术
*/
void studyDT();
}

编写实现工厂

1
2
3
4
5
6
7
8
9
public class BeijingAAAFactory implements AAAAbstractFactory {
public SoftwareTechnology teachST() {
return new ProductManagerDesign();
}

public DiggerTechnology teachDT() {
return new SmallDiggerOps();
}
}
1
2
3
4
5
public class SmallDiggerOps implements DiggerTechnology {
public void studyDT() {
System.out.println("头悬梁,锥刺股,努力学习小型挖掘机技术,当上CTO,迎娶白富美(高富帅),走向人生巅峰!");
}
}
1
2
3
4
5
public class ProductManagerDesign implements SoftwareTechnology {
public void studyST() {
System.out.println("头悬梁,锥刺股,努力学习产品设计技术,当上CTO,迎娶白富美(高富帅),走向人生巅峰!");
}
}

测试两种方式

1
2
3
4
5
6
7
8
9
public class Test {
public static void main(String[] args) {
BeijingAAAFactory beijingAAAFactory =new BeijingAAAFactory();
DiggerTechnology diggerTechnology = beijingAAAFactory.teachDT();
diggerTechnology.studyDT();
SoftwareTechnology softwareTechnology = beijingAAAFactory.teachST();
softwareTechnology.studyST();
}
}

单例(态)模式(Singleton)

概念

一种常用的软件设计模式。所谓单例,就是让一个类在项目运行中只存在一个对象,即使用到这个类的地方很多,也只存在一个对象。

好处

  • 节省内存
  • 有些情况下不用单例模式可能会引起代码逻辑错误(例如:网站访问量统计功能ServletContext (servlet) Application(jsp)) application.setAttrbute(“count”,2);

单例模式要点

  • 是单例模式的类只提供私有的构造函数;
  • 是类定义中含有一个该类的静态私有对象;
  • 是该类提供了一个静态的公共的函数用于创建或获取它本身的静态私有对象;

实现方式5种

**懒汉(slacker)**:该单例类非常懒,只有在自身需要的时候才会行动,从来不知道及早做好准备。特点是运行时获得对象的速度比较慢,但加载类的时候比较快。整个应用的生命周期只有一部分时间在占用资源。

懒汉线程不安全

因为没有加锁 synchronized,所以严格意义上它并不算单例模式。

这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。

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

//1,私有构造
private DemoA(){
}
//2,私有静态当前类属性 static 随着类加载执行,而且只执行一次 private 该属性不能在其他类中被调用
private static DemoA instance;

//3, 公共的 静态 返回该类对象的方法
public static DemoA getInstance(){
if(instance==null){
instance = new DemoA();
}
return instance;
}
}

懒汉线程安全

必须加锁 synchronized 才能保证单例,但加锁多少会影响效率

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

//1,私有构造
private DemoB(){
}
//2,私有静态当前类属性 static 随着类加载执行,而且只执行一次 private 该属性不能在其他类中被调用
private static DemoB instance;

//3, 公共的 静态 返回该类对象的方法
public static synchronized DemoB getInstance(){
if(instance==null){
instance = new DemoB();
}
return instance;
}
}

懒汉线程安全双重加锁

采用双锁机制,安全且在多线程情况下能保持高性能。

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
public class DemoC {

//1 私有构造
private DemoC() {
}

//2 私有静态属性 volatile 1,多线程可见 2,防止指令重拍
// DemoC demoC = new DemoC();
/**
* 1)先在堆中开辟空间
* 2)调用构造,实例化对象
* 3)使用引用指向该对象
* 不是原子
* 计算机底层为了程序运行效率,在不影响最终结果的情况下,它会重拍指令 1->2->3 它的顺序可能就1-> 3->2
* 当加上volatile关键字时,执行顺序一定是1->2->3
*/
private volatile static DemoC demoC;

//3 静态公共示例对象方法
public static DemoC getInstance(){
//判断是否为null
if(demoC == null){
//加锁
synchronized (DemoC.class) {
//防止多个线程实例化对象
if (demoC == null) {
demoC = new DemoC();
}
}
}
return demoC;
}
}

饿汉线程安全

没有加锁,执行效率会提高。类加载时就初始化,浪费内存。

1
2
3
4
5
6
7
8
9
public class Starving {
private Starving(){};

private static final Starving STARVING = new Starving();

public Starving getInstance(){
return STARVING;
}
}

饿汉静态线程安全

没有加锁,执行效率会提高。类加载时就初始化,浪费内存。当单例对象产生过程比较复杂时,使用这种方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class StarvingStaticCodeBlock {
private StarvingStaticCodeBlock(){};

private static final StarvingStaticCodeBlock STARVING;

static {
STARVING = new StarvingStaticCodeBlock();
}

public StarvingStaticCodeBlock getInstance(){
return STARVING;
}
}

懒汉和饿汉的区别

  • 类的加载速度:懒汉快 属性被加载时没有实例化对象(节省了实例化对象的时间) 饿汉慢

  • 获取对象的速度:饿汉快 对象在加载时已经被实例化了 懒汉慢

  • 对象的生命周期:饿汉产生的对象生命周期长 懒汉相反

    假如早上8.30 启动项目 晚上9.30 关闭项目 假如有一个工厂类是单例,项目启动时该类就会被加载 如果是饿汉 早8.30 - 晚上9.30 一直存于存活状态 如果是懒汉 中午12点才请求getInstance 中午12– 晚上9.30 这段存于存活状态

代理模式(Proxy)

概念:代理模式是常见的设计模式之一。顾名思义,代理模式就是代理对象具备真实对象的功能,并代替真实对象完成相应操作,并能够在操作执行的前后,对操作进行增强处理。

特点:其特征是代理类与委托类有同样的接口(父类),代理类主要负责为委托类预处理信息(前置通知)、过滤信息(前置通知)、把消息转发给委托类(环绕通知),以及事后处理信息(后置,最终通知)等。

代理类对象本身并不真正实现服务(真正做的业务,委托类需要干什么事),而是通过调用委托类的对象的相关方法,来提供特定的服务(对原服务的增强)。

静态代理

概念:由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。

1
2
3
4
5
6
7
8
9
10
11
12
public interface Account {

/**
* 查询余额
*/
void queryMoney();

/**
* 存取钱
*/
void updateMoney(double money);
}
1
2
3
4
5
6
7
8
9
10
11
public class Client implements Account {
@Override
public void queryMoney() {
System.out.println("我查询了余额。。。。瞬间不热了!!");
}

@Override
public void updateMoney(double money) {
System.out.println("存/取了"+money+"万元!");
}
}
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
public class Employee implements Account{

//定义委托类
private Client client;

/**
* 构造给委托类赋值
* @param client
*/
public Employee(Client client) {
this.client = client;
}

@Override
public void queryMoney() {
System.out.println("-------我是银行雇员,在查询之前要准备好ATM机。。。");
client.queryMoney();
System.out.println("-------我是银行雇员,在查询之后维修ATM机。。。");
}

@Override
public void updateMoney(double money) {
System.out.println("--------我是银行雇员,精心打扮,等待客户!!!");
client.updateMoney(money);
System.out.println("--------我是银行雇员,结算当天业务,下班回家!!!");
}
}

测试

1
2
3
4
5
6
7
8
public class Test {
public static void main(String[] args) {
Client client =new Client();
Employee employee =new Employee(client);
employee.queryMoney();
employee.updateMoney(10);
}
}

缺点:静态代理在代码运行之前就已经存在代理类了,所以每一个委托类都需要去建一个代理类去代理,当需要委托类有很多时,就需要创建很多的代理类,降低了程序可维护性。

如果接口增加一个方法,除了所有委托类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

动态代理

动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。

JDK动态代理

java.lang.reflect包下提供了一个Proxy类和一个Invocationhandler接口,用来完成动态代理。

Invocationhandler接口

当我们通过代理对象调用一个方法时,这个方法的调用就会被转发为由Invocationhandler这个接口的invoke方法进行调用。

Object invoke(Object proxy,Method method ,Object[] args)

参数:Object proxy:指被代理的对象 Method method: 要调用的方法 Object[] args: 方法调用时所需的参数

Proxy类:

该类提供了一个创建动态代理对象的方法。

static Object newProxyInstance(ClassLoader loader ,Class<?>[] interfaces,InvocationHandler h);

参数:ClassLoader loader:定义代理类的类加载器。Class<?>[] interfaces: 代理类要实现的接口列表InvocationHandler h: 指派方法调用的调用处理程序 表示代理对象要关联的InvocationHandler对象。

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 JDKProxy<T> implements InvocationHandler {

//定义委托类
private T obj;

/**
* 绑定委托类并返回动态生成的代理类
* @return
*/
public T bindObj(T obj){
this.obj = obj;

Class<?>[] interfaces = this.obj.getClass().getInterfaces();
//打印委托类的所有接口
System.out.println(Arrays.asList(interfaces));
//参数1 类加载器 参数2 委托类的所有接口 参数3 InvocationHandler的实现类 让当前类实现了该接口重写了invoke方法 所有放this
// 参数3的子类 重写的invoke就是处理参数2接口中的所有的方法的类
return (T)Proxy.newProxyInstance(this.obj.getClass().getClassLoader(),
interfaces ,
this);
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
System.out.println("执行的方法名称为:"+methodName);
System.out.println("------------代理类,在委托类执行前,做什么。。。。。");
//委托类执行方法 args委托执行方法的参数
Object result = method.invoke(this.obj, args);
System.out.println("------------代理类,在委托类执行后,做什么。。。。。");
return result;
}
}

1
2
3
4
5
6
7
8
9
10
public class Test {
public static void main(String[] args) {
Client client =new Client();
JDKProxy<Account> jdkProxy =new JDKProxy<>();
//返回就是代理类 实际是委托类接口的子类
Account account = jdkProxy.bindObj(client);
account.queryMoney();
account.updateMoney(100);
}
}

CGLIB代理

maven依赖

1
2
3
4
5
6
<!-- cglib代理-->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>

主要应用于没有接口的动态代理生成(主要是对指定的类生成一个子类,覆盖其中的所有方法,所以该类或方法不能声明称final的), spring AOP框架,Hibernate都使用了CGLLIB。

net.sf.cglib.proxy.Enhancer类:允许为非接口类型创建一个Java代理。Enhancer动态创建了给定类型的子类,并拦截了所有的方法。(setSuperclass,setCallback,create)

net.sf.cglib.proxy.Callback接口:在CGLIB包中是一个很关键的接口,所有被net.sf.cglib.proxy.Enhancer类调用的回调(callback)接口都要继承这个接口。

net.sf.cglib.proxy.MethodInterceptor接口:是最通用的回调(callback)类型,它经常被AOP用来实现拦截(intercept)方法的调用。这个接口只定义了一个方法。

public Object intercept(Object proxy, Method method, Object[] params, MethodProxy methodProxy)

proxy:代理的对象;method:委托类执行的方法;params:方法中的参数; methodProxy:代理的方法

由于性能的原因,对原始方法的调用我们使用CGLIB的net.sf.cglib.proxy.MethodProxy对象,而不是反射中一般使用java.lang.reflect.Method对象

实现代码

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
public class CGLIBProxy<T> implements MethodInterceptor {

//定义委托类
private T obj;

public T bindObj(T obj){
this.obj = obj;
//实例化增强器类 使用该类生成委托类的子类,作为代理类
Enhancer enhancer = new Enhancer();
//设置委托类为父类
enhancer.setSuperclass(this.obj.getClass());
//使用当前类做回调 因为当前类是Callback接口的子孙类 Callback->MethodInterceptor->this
// 重写委托类的方法,代理类要执行 把委托类的方法交给this的intercept方法一个一个执行,委托的方法是final就不能代理 final修饰的方法不能被重写
enhancer.setCallback(this);
//创建代理类
return (T)enhancer.create();
}

@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println(o.getClass().getName());
System.out.println(method.getName());

System.out.println("------------------代理类执行前,做什么操作----------------------");
//通过反射执行委托类中方法
Object result =method.invoke(this.obj,objects);
//Object result = methodProxy.invoke(this.obj, objects);
System.out.println("------------------代理类执行后,做什么操作----------------------");
return result;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class DeptController {

public final int add(Object o) {
System.out.println("1111");
return 0;
}

public int update(Object accountProject) {
System.out.println("222");
return 0;
}

public int delete(int deptId) {
System.out.println("3333");
return 0;
}

public Object queryById(int deptId) {
System.out.println("4444");
return null;
}
}
1
2
3
4
5
6
7
8
9
10
11
public class Test {
public static void main(String[] args) {
DeptController deptController = new DeptController();
CGLIBProxy<DeptController> controllerCGLIBProxy = new CGLIBProxy<>();
DeptController deptController1 = controllerCGLIBProxy.bindObj(deptController);
deptController1.add(1);
deptController1.update(1);
deptController1.delete(1);
deptController1.queryById(1);
}
}

CGLib动态代理创建代理实例速度慢,但是运行速度快;JDK动态代理创建实例速度快,但是运行速度慢。如果实例是单例的,推荐使用CGLib方式动态代理,反之则使用JDK方式进行动态代理。Spring的实例默认是单例,所以这时候使用CGLib性能高。

观察者模式(Observer Pattern)

定义:观察者模式 是软件设计模式的一种。 它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。在观察者模式中,监视另一个对象状态的对象称为Observer,正在被监视的对象称为Subject。

抽象主题(Subject):抽象主题提供一个接口,提供增加,删除和唤醒观察者对象的方法。

具体主题(Concrete Subject):它把所有观察者对象的引用保存到一个集合里,每个主题都可以有任何数量的观察者。将有关状态存入具体观察者对象;在具体主题内部状态改变时,给所有登记过的观察者发出通知。

抽象观察者(Observer):为所有的具体观察者定义一个接口,在得到主题通知时更新自己。

具体观察者(Concrete Observer):实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题状态协调。

观察者接口

1
2
3
4
5
6
7
public interface Observer {
/**
* 根据主题变化,观察者也发生变化
* @param message
*/
void update(String message);
}

观察者实现

1
2
3
4
5
6
7
8
9
10
11
public class ConcreteObserver implements Observer {
//观察者名称
private String obName;
public ConcreteObserver(String obName) {
this.obName = obName;
}
@Override
public void update(String message) {
System.out.println("观察者"+obName+"根据"+message+"变化,自己做了什么什么调整。。。。");
}
}

被观察者接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface Subject {
/**
* 注册观察者
*/
void registerObserver(Observer observer);
/**
* 移除观察者
* @param observer
*/
void removeObserver(Observer observer);
/**
* 通知所有观察者
* @param message
*/
void notifyObserver(String message);
}

被观察者实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ConcreteSubject implements Subject {
//所有观察者集合
private List<Observer> observerList = new ArrayList<>();

@Override
public void registerObserver(Observer observer) {
observerList.add(observer);
}

@Override
public void removeObserver(Observer observer) {
observerList.remove(observer);
}

@Override
public void notifyObserver(String message) {
for (Observer observer : observerList) {
observer.update(message);
}
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Test {
public static void main(String[] args) {
//实例化OB
Observer observer1 =new ConcreteObserver("ob1");
Observer observer2 =new ConcreteObserver("ob2");
Observer observer3 =new ConcreteObserver("ob3");
Observer observer4 =new ConcreteObserver("ob4");
//实例化Subject
Subject subject =new ConcreteSubject();
subject.registerObserver(observer1);
subject.registerObserver(observer2);
subject.registerObserver(observer3);
subject.registerObserver(observer4);
//主题变化
subject.notifyObserver("变黄了");
System.out.println("----------------------------------");
//移除OB
subject.removeObserver(observer1);
subject.notifyObserver("变红了");
}
}

好处:观察者模式将观察者和主题(被观察者)彻底解耦,主题只知道观察者实现了某一接口(也就是Observer接口)。并不需要观察者的具体类是谁、做了些什么或者其他任何细节。任何时候我们都可以增加新的观察者。因为主题唯一依赖的东西是一个实现了Observer接口的对象列表。

适配器模式(Adaptor Pattern)

定义:转换一个类的接口为客户端所需要的接口,将两个不兼容的类纠合在一起使用,这个转换的类就是适配器类。它属于结构型模式,需要有Adaptee(被适配者)和Adaptor(适配器)两个身份.它的宗旨就是,保留现有类所提供的服务,向客户提供接口,以满足客户的期望。

适配器的角色

目标(Target)角色:期待得到的接口。

源(Adaptee)角色: 需要适配的接口。

适配器(Adaptor)角色:适配器类是本模式的核心,在Target目标角色与Adaptee源角色之间提供一种过渡。适配器把Adaptee源接口转换成Target目标接口。显然,这一角色不可以是接口,而必须是具体类。

适配器模式分类

类适配器模式:适配器继承源角色类(继承),实现目标接口。

对象适配器模式:适配器容纳一个它包裹的类的实例(依赖),实现目标接口。

区别: 不同之处于两者在包装Adaptee源角色时,前者(类适配)包装的是Adaptee类(因为它同时从Target与Adaptee继承而来,可想而知,类适配的Adatper必须是一个具体类,而Target只能是一个接口),后者(对象适配)则直接包裹了一个源Adaptee的实例。

现实中的多功能充电器,就是适配器的一个实现,模拟该功能。

源角色

1
2
3
4
5
6
7
8
9
public class AdapteeHomePower {
/**
* 提供220v交流电
* @return
*/
public int support220ACPower(){
return 220;
}
}

目标角色

1
2
3
4
5
6
7
public interface TargetElectronicProductPower {
/**
* 充电电源
* @param type 1,苹果手机 2,安卓手机 3,平板 4, 其他产品不支持
*/
void chargePower(int type);
}

适配器角色(类模式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ClassAdaptor extends  AdapteeHomePower implements TargetElectronicProductPower{
@Override
public void chargePower(int type) {
//通过继承获取源角色的方法
int ac220V = this.support220ACPower();
//模拟转换,实例开发根据不同业务做处理
int dc5V =ac220V/44;
if(type==1){
System.out.println("当前设备为苹果手机,输出电压为"+dc5V+"v,充电电流1A,输出功率:"+dc5V*1+"w");
}else if(type==2){
System.out.println("当前设备为安卓手机,输出电压为"+dc5V+"v,充电电流2A,输出功率:"+dc5V*2+"w");
}else if(type==3){
System.out.println("当前设备为平板,输出电压为"+dc5V+"v,充电电流2.4A,输出功率:"+dc5V*2.4+"w");
}else {
System.out.println("不支持当前设备");
}
}
}

适配器角色(对象模式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ObjectAdaptor implements TargetElectronicProductPower {
//依赖
private AdapteeHomePower adapteeHomePower =new AdapteeHomePower();
@Override
public void chargePower(int type) {
//通过继承获取源角色的方法
int ac220V = adapteeHomePower.support220ACPower();
//模拟转换,实例开发根据不同业务做处理
int dc5V =ac220V/44;
if(type==1){
System.out.println("当前设备为苹果手机,输出电压为"+dc5V+"v,充电电流1A,输出功率:"+dc5V*1+"w");
}else if(type==2){
System.out.println("当前设备为安卓手机,输出电压为"+dc5V+"v,充电电流2A,输出功率:"+dc5V*2+"w");
}else if(type==3){
System.out.println("当前设备为平板,输出电压为"+dc5V+"v,充电电流2.4A,输出功率:"+dc5V*2.4+"w");
}else {
System.out.println("不支持当前设备");
}
}
}

测试

1
2
3
4
5
6
7
8
9
public class Test {
public static void main(String[] args) {
ClassAdaptor classAdaptor =new ClassAdaptor();
classAdaptor.chargePower(2);
System.out.println("------------------------------");
ObjectAdaptor objectAdaptor =new ObjectAdaptor();
objectAdaptor.chargePower(1);
}
}

策略模式(Strategy Pattern)

定义:定义了一组算法/业务方法,将每个算法/业务方法都封装起来,并且使它们之间可以互换

现实例子:我们出门的时候会选择不同的出行方式,比如骑自行车、坐公交、坐火车、坐飞机、坐火箭等等,这些出行方式,每一种都是一个策略。

使用场景:

如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。

一个系统需要动态地在几种算法中选择一种。

如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。

不希望客户端知道复杂的、与算法相关的数据结构,在具体策略类中封装算法和相关的数据结构,提高算法的保密性与安全性。

实现:

1
2
3
4
5
6
7
8
public interface GameStrategy {
/**
* 角色选择 策略接口
* @param heroName 英雄名称
* @param skillCombo 技能连招
*/
void heroChoice(String heroName,String skillCombo);
}
1
2
3
4
5
6
7
public class MasterHeros implements GameStrategy {
//策略选项
@Override
public void heroChoice(String heroName, String skillCombo) {
System.out.println("我是法师"+heroName+"擅长AP,连招技能为"+skillCombo);
}
}
1
2
3
4
5
6
public class FightWildHeros implements GameStrategy{
@Override
public void heroChoice(String heroName, String skillCombo) {
System.out.println("我是打野"+heroName+"擅长刺杀,连招技能为"+skillCombo);
}
}
1
2
3
4
5
6
public class ShooterHeros implements GameStrategy{
@Override
public void heroChoice(String heroName, String skillCombo) {
System.out.println("我是射手"+heroName+"擅长AD,连招技能为"+skillCombo);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Context {
private GameStrategy gameStrategy;

public Context(GameStrategy gameStrategy){
this.gameStrategy = gameStrategy;
}

/**
* 执行选择英雄,使用不同策略
* @param heroName
* @param skillCombo
*/
public void executeGameStrategy(String heroName,String skillCombo){
gameStrategy.heroChoice(heroName,skillCombo);
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test {
public static void main(String[] args) {
//选择法师
Context context1 =new Context(new MasterHeros());
context1.executeGameStrategy("诸葛亮","2123AA");

//选择射手
Context context3 =new Context(new ShooterHeros());
context3.executeGameStrategy("小鲁班","3A1A2AA");

//选择打野
Context context4 =new Context(new FightWildHeros());0
context4.executeGameStrategy("娜可露露","313A2A");
}
}

优点:策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基- 础上选 择算法或行为,也可以灵活地增加新的算法或行为。策略模式提供了管理相关的算法族的办法。使用策略模式可以避免使用多重条件转移语句。

缺点:策略类会增多。所有策略类都需要对外暴露。(客户端必须知道所有的策略类,并自行决定使用哪一个策略类)

职责链(Chain of Responsibility Pattern)

定义:责任链模式是一种设计模式,在责任链模式里, 为了避免请求的发送者和接受者耦合在一起,于是将所有请求的处理者通过一个对象的引用记住其下家而连接起来形成一条链。并沿着这条链传递该请求,直到有一个对象处理它为止。

现实例子:请假流程:员工提交请假申请 班长审批(3天) 主管审批(7天) 主任审批(30天) 副总审批(180天)

实例

流程数据载体

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
public class LeaveApply {
private String realName;
private int leaveDays;

public LeaveApply(String realName, int leaveDays) {
this.realName = realName;
this.leaveDays = leaveDays;
}

public String getRealName() {
return realName;
}

public void setRealName(String realName) {
this.realName = realName;
}

public int getLeaveDays() {
return leaveDays;
}

public void setLeaveDays(int leaveDays) {
this.leaveDays = leaveDays;
}
}

抽象责任处理类

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
public abstract class AbstractLeaveHandler {
//班长审批
protected final int LEVEL1 = 3;
//主管审批
protected final int LEVEL2 = 7;
//主任审批
protected final int LEVEL3 =30;
//副总审批
protected final int LEVEL4 = 180;
//处理人名称
protected String handlerName;

//下一个处理环节
protected AbstractLeaveHandler nextHandler;

/**
* 设置下一个处理环节
* @param nextHandler
*/
public void setNextHandler(AbstractLeaveHandler nextHandler) {
this.nextHandler = nextHandler;
}

/**
* 处理请假请求
* @param leaveApply
*/
public abstract void handlerApply(LeaveApply leaveApply);
}

班长处理环节类

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
public class MonitorHandler extends AbstractLeaveHandler{
/**
* 构造赋值给当前处理人
* @param handlerName
*/
public MonitorHandler(String handlerName) {
this.handlerName =handlerName;
}

@Override
public void handlerApply(LeaveApply leaveApply) {
//判断在不在班长处理范围之内
if(leaveApply.getLeaveDays()<=LEVEL1){
System.out.println("班长:" + handlerName + "已经处理,流程结束。");
return;
}

//判断下一个环节是否为空,不为空,下一个处理
if(this.nextHandler!=null){
this.nextHandler.handlerApply(leaveApply);
}else {
System.out.println("请假请求被拒绝!");
}
}
}

主管处理

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
public class ManagerHandler  extends AbstractLeaveHandler{
/**
* 构造赋值给当前处理人
* @param handlerName
*/
public ManagerHandler(String handlerName) {
this.handlerName =handlerName;
}

@Override
public void handlerApply(LeaveApply leaveApply) {
//判断在不在班长处理范围之内
if(leaveApply.getLeaveDays()>LEVEL1&&leaveApply.getLeaveDays()<=LEVEL2){
System.out.println("主管:" + handlerName + "已经处理,流程结束。");
return;
}

//判断下一个环节是否为空,不为空,下一个处理
if(this.nextHandler!=null){
this.nextHandler.handlerApply(leaveApply);
}else {
System.out.println("请假请求被拒绝!");
}
}
}

主任处理环节类

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
public class DirectorHandler extends AbstractLeaveHandler{
/**主任处理环节类
* 构造赋值给当前处理人
* @param handlerName
*/
public DirectorHandler(String handlerName) {
this.handlerName =handlerName;
}

@Override
public void handlerApply(LeaveApply leaveApply) {
//判断在不在班长处理范围之内
if(leaveApply.getLeaveDays()>LEVEL2&&leaveApply.getLeaveDays()<=LEVEL3){
System.out.println("主任:" + handlerName + "已经处理,流程结束。");
return;
}

//判断下一个环节是否为空,不为空,下一个处理
if(this.nextHandler!=null){
this.nextHandler.handlerApply(leaveApply);
}else {
System.out.println("请假请求被拒绝!");
}
}
}

副总处理环节类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class VicePresidentHandler extends AbstractLeaveHandler{
/**
* 构造赋值给当前处理人
* @param handlerName
*/
public VicePresidentHandler(String handlerName) {
this.handlerName =handlerName;
}

@Override
public void handlerApply(LeaveApply leaveApply) {
//判断在不在班长处理范围之内
if(leaveApply.getLeaveDays()>LEVEL3&&leaveApply.getLeaveDays()<=LEVEL4){
System.out.println("副总:" + handlerName + "已经处理,流程结束。");
return;
}
//判断下一个环节是否为空,不为空,下一个处理
if(this.nextHandler!=null){
this.nextHandler.handlerApply(leaveApply);
}else {
System.out.println("请假请求被拒绝!");
}
}
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ResponsibilityTest {
public static void main(String[] args) {

//实例化请假申请
LeaveApply leaveApply = new LeaveApply("张三",120);

//实例化责任链
MonitorHandler monitorHandler =new MonitorHandler("赵班长");
ManagerHandler managerHandler = new ManagerHandler("钱主管");
DirectorHandler directorHandler =new DirectorHandler("孙主任");
VicePresidentHandler vicePresidentHandler =new VicePresidentHandler("李副总");

//设置下一个环节
monitorHandler.setNextHandler(managerHandler);
managerHandler.setNextHandler(directorHandler);
directorHandler.setNextHandler(vicePresidentHandler);
//处理请求
monitorHandler.handlerApply(leaveApply);

}
}

总结

责任链主要重在责任分离处理,让各个节点各司其职。

责任链上的各个节点都有机会处理事务,但是也可能不会受理请求。

责任链比较长,调试时可能会比较麻烦。

责任链一般用于处理流程节点之类的实际业务场景中。

Spring拦截器链、servlet过滤器链等都采用了责任链设计模式。