Java设计模式
设计模式是对面向对象软件开发的一种经验总结,它是注重设计思想,一般用来提高代码的可复用性、可维护性和稳定性等方面。在1995年,GoF(Gang of Four)出版的《设计模式:可复用面向对象软件的基础》书内,收录了23种设计模式,从此开启了设计模式的大门,此后也称设计模式为GoF设计模式。
# 设计模式的六大原则
# 1.开闭原则
开闭原则就是对扩展开放,对修改关闭。
在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。
# 2.里氏替换原则
里氏替换原则是继承必须确保超类所拥有的性质在子类中依然成立。
LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。
# 3.依赖倒转原则
针对接口编程,依赖于抽象而不依赖于具体。
# 4.接口隔离原则
接口隔离原则是指要为各个类建立它们需要的专用接口,也就是使用多个隔离的接口,而不是单个接口。它还有另外一个意思是:降低类之间的耦合度。
# 5.迪米特法则
迪米特法则是指一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
# 6.合成复用原则
合成复用原则是指尽量使用合成/聚合的方式,而不是使用继承。
# 设计模式的分类
设计模式共有23种,可分为三大类型,分别是创建型模式(Creational Patterns)、结构型模式(Structural Patterns)和行为型模式(Behavioral Patterns)。
创建型模式 :用于帮助创建对象
- 单例模式(Singleton Pattern)
- 工厂模式(Factory Pattern)
- 抽象工程模式(Abstract Factory Pattern)
- 建造者模式(Builder Pattern)
- 原型模式(Prototype Pattern)
结构型模式 :关注对象和类的组织
- 适配器模式(Adapter Pattern)
- 代理模式(Proxy Pattern)
- 装饰器模式(Decorator Pattern)
- 桥接模式(Bridge Pattern)
- 过滤器模式(Filter Pattern)
- 组合模式(Composite Pattern)
- 外观模式(Faced Pattern)
- 享元模式(Flyweight Pattern)
行为型模式 :关乎对象之间的相互交互、通信和协作,明确对象的职责。
- 观察者模式(Observer Pattern)
- 策略模式(Strategy Pattern)
- 责任链模式(Chain of Responsibility Pattern)
- 迭代器模式(Iterator Pattern)
- 中介者模式(Mediator Pattern)
- 命令模式(Command Pattern)
- 解释器模式(Interpreter Pattern)
- 访问者模式(Visitor Pattern)
- 模板模式(Template Pattern)
- 状态模式(State Pattern)
- 备忘录模式(Memento Pattern)
由于设计模式比较多,暂时无法全面的记录每个设计模式,本次注重高频使用的设计模式进行详细的说明,分别是单例模式、工厂模式、抽象工厂模式、原型模式、适配器模式、代理模式、装饰器模式、观察者模式和策略模式。——(2021.12.26)
在实际应用中,根据项目结构需求使用合适的设计模式,解决相应的问题。
# 单例模式
# 简介
单例模式是指保证一个类只有一个实例,并且提供一个访问该实例的全局访问方法。
优点:由于单例模式只有一个实例,减少系统性能开销。当一个对象产生时需要较多的资源和依赖,则可以通过应用启动时直接产生一个单例对象,在提供对外访问该实例的全局方法,以优化共享资源的访问。
常见的单例模式实现方式有饿汉式、懒汉式、双层检测锁、静态内部类和枚举五种方式。
# 单例实现的方法——饿汉式
package singleton;
/**
* 单例实现方式——饿汉式
*/
public class SingletonDemo01 {
// 静态在类初始化时加载,以避免多线程问题
private static SingletonDemo01 instance = new SingletonDemo01();
// 私有化构造方法
private SingletonDemo01(){}
// 提供对外访问的方法
public static SingletonDemo01 getInstance(){
return instance;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
在饿汉式单例模式中,首先是私有化构造方法和提供对外访问的方法,接着初始化单例的静态成员变量,在对外访问方法内返回该静态成员变量。
饿汉式单例优点:简单且线程安全。由于static静态变量会在类初始化时加载,保证了线程安全。
饿汉式单例缺点:资源浪费。由于static静态变量直接初始化,导致了如果仅加载类,而没有调用类的
getInstance()
,则会造成资源浪费。
# 单例实现的方法——懒汉式(线程不安全)
package singleton;
/**
* 单例实现方式——懒汉式(线程不安全)
*/
public class SingletonDemo02 {
// 不初始化,实现延迟加载
private static SingletonDemo02 instance;
// 私有化构造方法
private SingletonDemo02(){}
// 提供对外访问的方法
public static SingletonDemo02 getInstance(){
if(instance == null) {
instance = new SingletonDemo02();
}
return instance;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
在懒汉式单例模式中,首先是私有化构造方法和提供对外访问的方法,对于单例的静态成员变量不初始化,在对外访问方法内进条件判断,若该单例对象未初始化则初始化,若已经初始化则直接返回该单例对象。
- 懒汉式单例优点:延迟加载,懒加载,实现资源节约。
- 懒汉式单例缺点:线程不安全,在并发时可能造成多个实例出现。
# 单例实现的方法——懒汉式(线程安全)
解决线程不安全的方式是通过在方法声明上添加synchronized
,虽然资源效率提高,但是并发效率降低。
package singleton;
/**
* 单例实现方式——懒汉式(线程安全)
*/
public class SingletonDemo03 {
// 不初始化,实现延迟加载
private static SingletonDemo03 instance;
// 私有化构造方法
private SingletonDemo03(){}
// 提供对外访问的方法
public static synchronized SingletonDemo03 getInstance(){
if(instance == null) {
instance = new SingletonDemo03();
}
return instance;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 单例实现的方法——双重检测锁(DCL)
package singleton;
/**
* 单例实现方式——双重校验锁
*/
public class SingletonDemo04 {
// 不初始化,实现延迟加载
private volatile static SingletonDemo04 instance;
// 私有化构造方法
private SingletonDemo04(){}
// 提供对外访问的方法
public static SingletonDemo04 getInstance(){
if(instance == null) {
// 同步代码块检测
synchronized (SingletonDemo04.class) {
if(instance == null) { // 单例检测
instance = new SingletonDemo04();
}
}
}
return instance;
}
}
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
懒汉式的线性安全问题除了在方法上使用synchronized
外,还可以使用同步代码块方式,这种方式称为DCL懒汉式。
这种解决方式关键在于一是getInstance()
内进行两次检测,即同步检测和单例检测;二是由于new
操作不是原子性的,存在指令排序(开辟内存空间,初始化变量和指针指向),在同步时可能会发送指令重排,因此需要在静态成员变量上添加volatile
关键字,禁止指令重排。
getInstance()
最外层的if
条件判断,是为了提高并发效率,即存在实例时,直接返回,不需要进行锁的获取和释放。
# 单例实现的方法——静态内部类
package singleton;
/**
* 单例实现方式——静态内部类
*/
public class SingletonDemo05 {
// 静态内部类,实现延迟加载,同时避免线程安全问题
private static class SingletonDemo05Instance {
private static final SingletonDemo05 instance = new SingletonDemo05();
}
// 私有化构造方法
private SingletonDemo05(){}
// 提供对外访问的方法
public static SingletonDemo05 getInstance(){
return SingletonDemo05Instance.instance;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
静态内部类方法兼备延迟加载和线性安全的两大特性,利用静态域特性,确保在getInstance()
调用时才会创建对象,实现了延迟初始化,同时也引用静态会在类初始化时加载,保证了线程安全。
# 单例实现的方法——枚举单例
package singleton;
/**
* 单例实现方式——枚举
*/
public enum SingletonDemo06 {
INSTANCE;
public void whateverMethod() {
}
}
2
3
4
5
6
7
8
9
10
11
由于枚举本身就是单例对象,由JVM根本上提供保障,且实现简单。但是缺点是没有延迟加载,同时还没有被广泛使用。
# 单例实现的问题
以上5种单例实现的方式,除了枚举方式都可以利用反射和反序列化进行破解。
反射可以暴力破坏访问权限,直接调用类的私有构造方法达到破坏单例的目的。常用避免手段之一是在构造方法中进行条件判断,手动抛出异常。
package singleton; /** * 单例实现方式——DCL懒汉式,避免反射破坏 */ public class SingletonDemo07 { // 不初始化,实现延迟加载 private volatile static SingletonDemo07 instance; // 私有化构造方法 private SingletonDemo07(){ if(instance != null) { throw new RuntimeException("该类只能存在一个对象"); } } // 提供对外访问的方法 public static SingletonDemo07 getInstance(){ if(instance == null) { // 同步代码块检测 synchronized (SingletonDemo07.class) { if(instance == null) { // 单例检测 instance = new SingletonDemo07(); } } } 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该方法在调用
getInstance()
后的反射破坏是有效的,在反射直接实例化对象时无效,因此一般还需要在构造函数内通过某些标识符进行处理。反序列化的
readObject()
方法从反序列文件内重新获取类信息实例化对象也能破坏单例。常用避免手段是在类中的定义readResolve()
方法,指定在反序化时返回指定的对象,以防止获得不同的对象。
# 单例的应用场景
在实际的应用场景内,需要程序都有体现单例模式的思想。
Windows的Task Manager(任务管理器)就是很典型的单例模式
项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,每次new一个对象去读取。
数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理
在spring MVC框架/struts1框架中,控制器对象也是单例
......
# 总结
![单例模式](java-dp.assets/单例模式.png)
# 工厂模式
# 简介
工厂模式目的是将创建者和调用者分离(解耦),即在实例化对象时,用工厂方法替代new。
- 工厂模式的优点:调用者和创建者解耦、产品可扩展性高、屏蔽产品的具体实现,调用者只关心产品的接口。
- 工厂模式的缺点:由于新的产品引入就需要扩展新的工厂类型,使得系统中的类个数成倍增加。
常见的工厂模式实现方式:简单工厂、工厂方法和抽象工厂。
以实现一个汽车工厂为例子,说明三种实现方法。
首先是在不适用工厂的情况下,调用者和创建者直接关系,也就是说汽车购买者直接找汽车厂商购买汽车。
package Factory;
/**
* 不使用工厂模式
*/
public class Client01 {
public static void main(String[] args) {
Car c1 = new BydCar();
Car c2 = new AudiCar();
c1.run();
c2.run();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# 简单工厂
简单工厂又称静态工厂,通过接收的参数的不同来返回不同的对象实例。
package Factory;
public class SimpleFactory {
// 方式一
public static Car createCar(String type) {
Car c = null;
if("Audi".equals(type)) {
c = new AudiCar();
}else if("Byd".equals(type)) {
c = new BydCar();
}
return c;
}
// 方式二
public static Car createAudi() {
return new AudiCar();
}
public static Car createByd() {
return new BydCar();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
简单工厂虽然达到工厂模式目的,但是违反了开闭原则,因为每增加一个产品都需要修改工厂的源码。
# 工厂方法
工厂方法是对简单工厂的优化,它用一组实现相同接口的工厂类来实现。
首先是定义工厂接口,接口定义工厂生产产品的方法。
package Factory;
public interface CarFactory {
Car craeteCar();
}
2
3
4
5
接着是定义产品工厂类实现工厂接口,实现生产产品的方法。
package Factory;
public class AudiCarFactory implements CarFactory{
@Override
public Car craeteCar() {
return new AudiCar();
}
}
2
3
4
5
6
7
8
package Factory;
public class BydCarFactory implements CarFactory{
@Override
public Car craeteCar() {
return new BydCar();
}
}
2
3
4
5
6
7
8
工厂方法的优点在于遵守了开闭原则,但是缺点在于使得类的数量增加,造成结构的复杂和冗余。
# 简单工厂和工厂方法比较
- 结构复杂度:简单工厂相对工厂方法而言更为简单。
- 代码复杂度:简单工厂在结构方面简单,但是代码结构上复杂。工厂方法相反。
- 客户端编程难度:简单工厂比工厂方法相对容易。
- 维护上的难度:工厂方法由于遵循开闭原则,在维护上相对简单工厂较容易。
根据设计理论建议:工厂方法模式。但实际上,我们一般都用简单工厂模式。
# 总结
![工厂模式](java-dp.assets/工厂模式.png)
# 抽象工厂模式
抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。它用于生产不同产品族的全部产品。但是对于新增的产品,无能为力。
1.Engine.java
public interface Engine {
void start();
void speedUp();
}
class LuxuryEngine implements Engine {
@Override
public void start() {
System.out.println("快速启动!");
}
@Override
public void speedUp() {
System.out.println("快速加速!");
}
}
class LowEngine implements Engine {
@Override
public void start() {
System.out.println("慢速启动!");
}
@Override
public void speedUp() {
System.out.println("慢速加速!");
}
}
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
- Seat.java
public interface Seat {
void massage();
}
class LuxurySeat implements Seat {
@Override
public void massage() {
System.out.println("按摩!");
}
}
class LowSeat implements Seat {
@Override
public void massage() {
System.out.println("不能按摩!");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
3.Tire.java
public interface Tire {
void revolve();
}
class LuxuryTire implements Tire {
@Override
public void revolve() {
System.out.println("旋转不磨损!");
}
}
class LowTire implements Tire {
@Override
public void revolve() {
System.out.println("旋转磨损快!");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
4.CarFactory.java
public interface CarFactory {
Engine createEngine();
Seat createSeat();
Tire createTire();
}
class LuxuryCarFactory implements CarFactory {
@Override
public Engine createEngine() {
return new LuxuryEngine();
}
@Override
public Seat createSeat() {
return new LuxurySeat();
}
@Override
public Tire createTire() {
return new LuxuryTire();
}
}
class LowCarFactory implements CarFactory {
@Override
public Engine createEngine() {
return new LowEngine();
}
@Override
public Seat createSeat() {
return new LowSeat();
}
@Override
public Tire createTire() {
return new LowTire();
}
}
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
5.Client.java
public class Client {
public static void main(String[] args) {
CarFactory carFactory = new LuxuryCarFactory();
Engine engine = carFactory.createEngine();
engine.start();
engine.speedUp();
}
}
2
3
4
5
6
7
8
9
10
# 应用场景
JDK中Calendar.getInstance()方法。
JDBC中Connection对象的获取。
Hibernate中SessionFactory创建Session。
Spring中IoC容器创建管理Bean对象。
反射中Class对象的newInstance()方法。
# 原型模式
# 简介
原型模式就是Java中的克隆技术,以某个对象为原型,复制出新的对象。新的对象具备原型对象的特点,也就是克隆出来的对象属性值和原型对象相同。
优势:效率高(直接克隆,避免了重新执行构造方法),短时间内大量对象创建。
原型模式实现两个关键点是:
- 类实现Cloneable接口的clone()方法
- 实现原型模式最困难的是内存复制操作,所幸Java提供了clone()方法。
# 浅克隆
所谓浅克隆是被赋值的对象的所有变量都含有与原来的对象相同的值,同时引用类型对象的均指向同一个地址,即修改引用对象的值,会导致克隆和被克隆对象的值是同步变化的。
Sheep.java
package Prototype; import java.util.Date; /** * 原型模式实现——浅克隆 */ // 实现Cloneable接口 public class Sheep implements Cloneable { private String name; private Date birthday; @Override protected Object clone() throws CloneNotSupportedException { // 调用Object提供的clone()方法 return super.clone(); } public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } }
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
38Client.java
package Prototype; import java.util.Date; public class Client01 { public static void main(String[] args) throws CloneNotSupportedException { Sheep sheep1 = new Sheep(); sheep1.setName("多利"); sheep1.setBirthday(new Date()); System.out.println(sheep1.getName()); System.out.println(sheep1.getBirthday()); Sheep sheep2 = (Sheep) sheep1.clone(); sheep2.setName("多利Clone"); System.out.println(sheep2.getName()); System.out.println(sheep2.getBirthday()); // 浅克隆 System.out.println(sheep1.getBirthday() == sheep2.getBirthday()); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 深克隆
深克隆是引用的变量指向新的地址,而不是原来的地址,即创建新的地址给克隆对象的引用变量。
基于JDK的深克隆
Sheep.java
import java.util.Date; public class Sheep implements Cloneable { private String name; private Date birthday; @Override protected Object clone() throws CloneNotSupportedException { Sheep sheep = (Sheep) super.clone(); // 引用对象也克隆 sheep.setBirthday((Date) birthday.clone()); return sheep; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } }
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
基于序列化的深克隆
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.Date; public class Sheep implements Cloneable, Serializable { private static final long serialVersionUID = 2155997264135266066L; private String name; private Date birthday; @Override protected Object clone() throws CloneNotSupportedException { Sheep sheep = null; ByteArrayOutputStream baos = null; ObjectOutputStream oos = null; ByteArrayInputStream bais = null; ObjectInputStream ois = null; try { baos = new ByteArrayOutputStream(); oos = new ObjectOutputStream(baos); oos.writeObject(this); byte[] bytes = baos.toByteArray(); bais = new ByteArrayInputStream(bytes); ois = new ObjectInputStream(bais); sheep = (Sheep) ois.readObject(); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } return sheep; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } }
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
# 应用场景
- 原型模式很少单独使用,一般与工厂模式一起出现。通过clone()方法创建对象后由工厂模式返回。
- Spring的Bean创建有单例模式和原型模式两种方式。
# 总结
![原型模式](java-dp.assets/原型模式.png)
# 适配器模式
# 简介
适配器是将一个类的接口转换成另一个接口,使原本由于接口不兼容而不能一起工作的那些类可以在一起工作。
适配器的主要三个类是
- 目标接口(Target):客户所期望的接口(接口、抽象类或具体类)。
- 适配的类(Adaptee):被适配的类。
- 适配器(Adapter):通过包装适配的类,把原接口转换成目标接口。
# 实现
Target.java
package Adapter; /** * 目标接口 */ public interface Target { void handleRequest(); }
1
2
3
4
5
6
7
8Adaptee.java
package Adapter; /** * 适配的类 */ public class Adaptee { public void request() { System.out.println("处理请求"); } }
1
2
3
4
5
6
7
8
9
10Adapter.java
使用继承实现:Adapter继承Adaptee。
package Adapter; /** * 适配器:使用继承 */ public class Adapter01 extends Adaptee implements Target{ @Override public void handleRequest() { request(); } }
1
2
3
4
5
6
7
8
9
10
11Adapter.java
用关联实现:Adapter持有Adaptee的引用,Adapter可以继承其他类,更灵活。
package Adapter; /** * 适配器:使用关联 */ public class Adapter02 implements Target{ // 使关联更灵活,便于适配器继承其他类 private Adaptee adaptee; public Adapter02(Adaptee adaptee) { this.adaptee = adaptee; } @Override public void handleRequest() { this.adaptee.request(); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19client.java
package Adapter; public class Client { public static void execute(Target target) { target.handleRequest(); } public static void main(String[] args) { Target target = new Adapter(); execute(target); } }
1
2
3
4
5
6
7
8
9
10
11
12
# 应用场景
做旧系统改造和升级。
java.io.InputStreamReader(InputStream)。
java.io.OutputStreamWriter(OutputStream)。
# 总结
# 代理模式
# 简介
代理模式是指将统一流程代码放到代理类中做处理。它是AOP(Aspect Oriented Programming,面向切面编程)的核心实现机制。
代理模式的主要三个角色分别是
- 抽象角色:定义代理角色和真实角色的公共对外方法。
- 真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。
- 代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
# 静态代理
静态代理是实现代理模式一种手段,它通过手动创建代理类,实现对真实角色的代理。以下面的实际案例进行展开。
Star.java
package Proxy.StaticProxy; /** * 抽象角色 */ public interface Star { void confer(); void signContract(); void bookTicket(); void sing(); void collectMoney(); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17RealStar.java
package Proxy.StaticProxy; /** * 真实角色 */ public class RealStar implements Star { @Override public void confer() { System.out.println("RealStar.confer()"); } @Override public void signContract() { System.out.println("RealStar.signContract()"); } @Override public void bookTicket() { System.out.println("RealStar.bookTicket()"); } @Override public void sing() { System.out.println("RealStar.sing()"); } @Override public void collectMoney() { System.out.println("RealStar.collectMoney()"); } }
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
33ProxyStar.java
package Proxy.StaticProxy; /** * 代理角色 */ public class ProxyStar implements Star { private Star realStar; public ProxyStar(Star realStar) { this.realStar = realStar; } @Override public void confer() { System.out.println("ProxyStar.confer()"); } @Override public void signContract() { System.out.println("ProxyStar.signContract()"); } @Override public void bookTicket() { System.out.println("ProxyStar.bookTicket()"); } @Override public void sing() { realStar.sing(); } @Override public void collectMoney() { System.out.println("ProxyStar.collectMoney()"); } }
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
39Client.java
package Proxy.StaticProxy; /** * 静态代理 */ public class Client { public static void main(String[] args) { RealStar realStar = new RealStar(); ProxyStar proxyStar = new ProxyStar(realStar); // 代理角色完成相应任务 proxyStar.confer(); proxyStar.signContract(); proxyStar.bookTicket(); proxyStar.sing(); // 调用真实角色 proxyStar.collectMoney(); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 动态代理
在静态代理中代理角色是由用户手动创建,而动态代理则是由Java的反射机制动态生成的。
常见实现动态代理的方式有:
- JDK自带的动态代理
- CGLIB
- javaassist字节码操作
- ASM(底层使用指令,可维护性较差)
# JDK自带的动态代理
JDK自带的动态代理,主要是使用以下两个类
java.lang.reflect.Proxy
:动态生成代理类和对象java.lang.reflect.InvocationHandler
:处理器接口,可以通过invoke方法实现对真实角色的代理访问。每次Proxy生成代理类对象都要指定对应的处理器对象。
Star.java
package Proxy.StaticProxy; /** * 抽象角色 */ public interface Star { void confer(); void signContract(); void bookTicket(); void sing(); void collectMoney(); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17RealStar.java
package Proxy.StaticProxy; /** * 真实角色 */ public class RealStar implements Star { @Override public void confer() { System.out.println("RealStar.confer()"); } @Override public void signContract() { System.out.println("RealStar.signContract()"); } @Override public void bookTicket() { System.out.println("RealStar.bookTicket()"); } @Override public void sing() { System.out.println("RealStar.sing()"); } @Override public void collectMoney() { System.out.println("RealStar.collectMoney()"); } }
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
33StarHandler.java
package Proxy.DynamicProxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * 处理器接口 */ public class StarHandler implements InvocationHandler { private Object realStar; public StarHandler(Object realStar) { this.realStar = realStar; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { switch (method.getName()) { case "confer": System.out.println("ProxyStar.confer()"); break; case "signContract": System.out.println("ProxyStar.signContract()"); break; case "bookTicket": System.out.println("ProxyStar.bookTicket()"); break; case "sing": method.invoke(realStar, args); break; case "collectMoney": System.out.println("ProxyStar.collectMoney()"); break; } 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
39Client.java
package Proxy.DynamicProxy; import java.lang.reflect.Proxy; public class Client { public static void main(String[] args) { Star realStar = new RealStar(); StarHandler starHandler = new StarHandler(realStar); Star proxyStar = (Star) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[] {Star.class}, starHandler); proxyStar.confer(); proxyStar.signContract(); proxyStar.bookTicket(); proxyStar.sing(); proxyStar.collectMoney(); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 应用场景
- spring中AOP的实现
- AspectJ的实现
- 数据库连接池关闭处理
- mybatis中实现拦截器插件
- struts2中拦截器的实现
- Hibernate中延时加载的实现
- RMI远程方法调用
实际上,大多数技术框架都会用到代理模式!
# 总结
![代理模式](java-dp.assets/代理模式.png)
# 装饰器模式
# 简介
装饰器是一种用于替代继承的技术,它可以无需通过继承来增加子类就能扩展对象的新功能。其核心在于使用对象的关联关系替代继承关系,避免继承导致类型体系快速膨胀的问题。
- 优点:扩展对象功能,比继承灵活,不会导致类个数增加;可以对一个对象进行多次装饰,创建不同行为的组合;具体构建类和具体装饰类可以独立变化,用户可以根据需要自己增加 新的具体构件子类和具体装饰子类。
- 缺点:产生很多小对象。大量小对象占据内存,一定程度上影响性能;装饰模式易于出错,调试排查比较麻烦。
装饰器的核心角色:
- 抽象构建角色(Component):具体构建角色和装饰角色的公共对外方法。
- 具体构建角色(ConcretComponent)。
- 装饰角色(Decorator):持有一个抽象构建角色引用,接收所有客户端请求,并把这个请求转发给具体装饰角色。
- 具体装饰角色(ConcreteDecorator):给构建增加新功能。
# 实现
以车为基础,扩展飞行车,水上车和自动车,再利用装饰器来动态扩展不同组合的新型汽车。
ICar.java(抽象构建角色)
package Decorator; /** * 抽象构建角色 */ public interface ICar { void move(); }
1
2
3
4
5
6
7
8Car.java(具体构建角色)
package Decorator; /** * 具体构建角色 */ public class Car implements ICar{ @Override public void move() { System.out.println("路上跑"); } }
1
2
3
4
5
6
7
8
9
10
11具体构建角色,或者说基础对象,其他装饰对象再此基础上扩展。
SuperCar.java(装饰角色)
package Decorator; // 装饰器角色 public abstract class SuperCar implements ICar { protected ICar car; public SuperCar(ICar car) { this.car = car; } public void move(){ car.move(); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16装饰角色,或者说装饰桥梁,持有抽象构建对象,接收所有客户端请求,并把这些请求转发给具体装饰角色。
FlyCar.java | WaterCar.java | AICar.java (具体装饰角色)
package Decorator; /** * 具体装饰角色 */ public class FlyCar extends SuperCar { public FlyCar(ICar car) { super(car); } @Override public void move() { super.move(); fly(); } public void fly() { System.out.println("天上飞!"); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22package Decorator; /** * 具体装饰角色 */ public class WaterCar extends SuperCar { public WaterCar(ICar car) { super(car); } @Override public void move() { super.move(); water(); } public void water() { System.out.println("水上游!"); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22package Decorator; /** * 具体装饰角色 */ public class AICar extends SuperCar { public AICar(ICar car) { super(car); } @Override public void move() { super.move(); auto(); } public void auto() { System.out.println("自动跑!"); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22Client.java
package Decorator; public class Client { public static void main(String[] args) { // 陆地上跑 ICar car = new Car(); car.move(); //陆地上跑+天上飞 System.out.println("================="); FlyCar flyCar = new FlyCar(car); flyCar.move(); //陆地上跑+水上游 System.out.println("================="); WaterCar waterCar = new WaterCar(car); waterCar.move(); //陆地上跑+自动跑 System.out.println("================="); AICar aiCar = new AICar(car); aiCar.move(); //陆地上跑+天上飞+水上游 System.out.println("================="); WaterCar waterCar1 = new WaterCar(new FlyCar(new Car())); waterCar1.move(); //陆地上跑+天上飞+水上游+自动跑 System.out.println("================="); AICar aiCar1 = new AICar(new WaterCar(new FlyCar(new Car()))); aiCar1.move(); } }
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
理解:核心在于理解装饰角色:“接收所有客户端请求,并把这些请求转发给具体装饰角色“。
以下面代码为例,进行描述:
WaterCar waterCar1 = new WaterCar(new FlyCar(new Car()));
waterCar1.move();
- 调用
waterCar1.move();
,到相应方法体内,接着super.move()
。- 由于waterCar1的父类SuperCar,则调用SuperCar内的
move()
。在SuerpCar内需要car,是在waterCar1初始化时传入的对象,即new FlyCar(new Car())
,那么调用是FlyCar的move()
。- 调用
new FlyCar(new Car()).move()
,到相应方法体内,接着又是super.move()
。- 由于
new FlyCar(new Car())
的父类是SuperCar,则调用SuperCar内的move()
。在SuerpCar内需要car,是在new FlyCar(new Car())
初始化时传入的对象,即new Car()
,那么调用是Car的move()
。- 接着调用
fly()
,再接着调用water()
,以此类推。其中整个过程核心是SuperCar父类,即装饰角色,它接收其子类的对象,并调用子类的相应方法,也就是这句”接收所有客户端请求,并把这些请求转发给具体装饰角色“的含义。
有点类属于递归的思想
# 应用场景
- IO中输入流和输出流设计。
- Servlet API 中提供了一个request对象的Decorator设计模式的默认实 现类HttpServletRequestWrapper,HttpServletRequestWrapper 类,增强了request对象的功能。
- Struts2中,request,response,session对象的处理。
# 总结
![装饰器模式](java-dp.assets/装饰器模式.png)
# 观察者模式
# 简介
观察者模式用于用于1:N的消息通知。
观察模式角色:
- 目标对象(Subject或Observable)
- 观察者对象(Observer)
当目标对象(Subject或Observable)的状态变化(消息发布)时,他及时告知一系列观察者对象(Observer),令他们做出相应(消息订阅)。
通知观察者的方式:
- 推:每次都会把消息以广播方式发送给所有观察者,所有观察者只能被动接收。
- 推:每次都会把消息以广播方式发送给所有观察者,所有观察者只能被动接收。
# 实现
Subject.java
package Observer; import java.util.ArrayList; import java.util.List; /** * 目标对象 */ public abstract class Subject { private List<Observer> observers = new ArrayList<>(); public void subscribe(Observer observer) { observers.add(observer); } public void unsubscribe(Observer observer) { observers.remove(observer); } public void notifyAllObservers() { for (Observer observer : observers) { observer.update(this); } } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25ConcreteSubject.java
package Observer; /** * 具体目标对象 */ public class ConcreteSubject extends Subject { private int state; public int getState() { return state; } public void setState(int state) { this.state = state; notifyAllObservers(); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18Observer.java
package Observer; /** * 观察者对象 */ public interface Observer { void update(Subject subject); }
1
2
3
4
5
6
7
8ConcreteObserver.java
package Observer; /** * 具体观察者 */ public class ConcreteObserver implements Observer { private String name; public ConcreteObserver(String name) { this.name = name; } @Override public void update(Subject subject) { System.out.println(name + "收到消息:state=" + ((ConcreteSubject) subject).getState()); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19Client.java
package Observer; public class Client { public static void main(String[] args) { // 目标对象 ConcreteSubject subject = new ConcreteSubject(); // 观察者 Observer observer1 = new ConcreteObserver("张三"); Observer observer2 = new ConcreteObserver("李四"); Observer observer3 = new ConcreteObserver("王五"); // 观察者关联目标对象 subject.subscribe(observer1); subject.subscribe(observer2); subject.subscribe(observer3); // 目标对象状态变化,观察者接收信息 subject.setState(1); subject.setState(2); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 基于JDK观察模式
ConcreteSubject.java
package Observer; import java.util.Observable; /** * 具体目标对象 基于JDK继承Observable */ public class ConcreteSubject1 extends Observable { private int state; public int getState() { return state; } public void setState(int state) { this.state = state; // 目标对象已变化 setChanged(); // 通知观察者 notifyObservers(state); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24ConcreteObserver.java
package Observer; import java.util.Observable; /** * 具体目标对象 基于JDK继承Observable */ public class ConcreteSubject1 extends Observable { private int state; public int getState() { return state; } public void setState(int state) { this.state = state; // 目标对象已变化 setChanged(); // 通知观察者 notifyObservers(state); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24Client.java
package Observer; import java.util.Observer; public class Client1 { public static void main(String[] args) { // 目标对象 ConcreteSubject1 subject = new ConcreteSubject1(); // 观察者 Observer observer1 = new ConcreteObserver1("张三"); Observer observer2 = new ConcreteObserver1("李四"); Observer observer3 = new ConcreteObserver1("王五"); // 观察者关联目标对象 subject.addObserver(observer1); subject.addObserver(observer2); subject.addObserver(observer3); // 目标对象状态变化,观察者接收信息 subject.setState(1); subject.setState(2); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 应用场景
聊天室,服务器转发给所有客户端。
网络游戏多人联机对战,服务器将客户端的状态进行分发。
邮件订阅。
Servlet编程,监听器的实现。
Android,广播机制。
京东商城,群发某商品打折信息。
# 总结
![观察者模式](java-dp.assets/观察者模式.png)
# 策略模式
# 简介
策略模式对用于解决某一问题的一个算法族,允许用户从该算法族中任选一个算法解决某一问题,同时可以方便的更换算法或者新增算法。
本质:分离算法,选择实现。
# 实现
定义算法的公共接口,不同算法采用不同的方式实现该接口,对外访问提供上下文对象来对接外部请求。
某个市场人员接到单后的报销策略(CRM系统的常见问题)。报价策略很复杂:
- 普通客户小批量报价;
- 普通客户大批量报价;
- 老客户小批量报价;
- 老客户大批量报价。
# 应用场景
Java的GUI编程,布局管理。
Spring框架的Resource接口,资源访问策略。
javax.servlet.http.HttpServlet#service()。