1. 结构型模式概述
结构型模式关注点“怎样组合对象/类?”所以我们关注下类的组合关系
类结构型模式关心类的组合,由多个类可以组合成一个更大的(继承)
对象结构型模式关心类与对象的组合,通过关联关系在一个类中定义另一个类的实例对象(组合)
根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系,因此大部分结构型模式都是对象结构型模式
模式分类
- 适配器模式(Adapter Pattern):两个不兼容接口之间适配的桥梁
- 桥接模式(Bridge Pattern):相同功能抽象化与实现化解耦,抽象与实现可以独立升级。
- 过滤器模式(Filter、Criteria Pattern):使用不同的标准来过滤一组对象
- 组合模式(Composite Pattern):相似对象进行组合,形成树形结构
- 装饰器模式(Decorator Pattern):向一个现有的对象添加新的功能,同时又不改变其结构
- 外观模式(Facade Pattern):向现有的系统添加一个接口,客户端访问此接口来隐藏系统的复杂性
- 享元模式(Flyweight Pattern):尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象
代理模式(Proxy Pattern):一个类代表另一个类的功能
2. 适配器模式
2.1 特点和应用场景
模式特点
将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,适配器模式分为类结构型模式(继承)和对象结构型模式(组合)两种,前者(继承)类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些
- 别名也可以是 Wrapper,包装器
包含角色
- 目标(Target)接口:可以是抽象类或接口。客户希望直接用的接口
- 适配者(Adaptee)类:隐藏的转换接口
- 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口
应用场景
- Tomcat 如何将 Request 流转为标准 Request
- Spring AOP 中的 AdvisorAdapter
Spring MVC 中经典的 HandlerAdapter
2.2 案例
```java public interface MoviePlayer {
// 播放电影并返回字幕 String play(String movieName);
class DefaultMoviePlayer implements MoviePlayer {
@Override
public String play(String movieName) {
System.out.println(movieName + "开始播放");
return "混蛋";
}
}
class MoviePlayerAdapter implements MoviePlayer {
private Translator translator;
private MoviePlayer target;
/**
* 目标(Target)接口:可以是抽象类或接口。客户希望直接用的接口
* 适配者(Adaptee)类:隐藏的转换接口
* 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口
*/
public MoviePlayerAdapter(Translator translator, MoviePlayer moviePlayer) {
this.translator = translator;
this.target = moviePlayer;
}
@Override
public String play(String movieName) {
String subtitle = target.play(movieName);
return translator.translate(subtitle);
}
}
}
```java
public interface Translator {
String translate(String subtitle);
class ZH2JapanTranslator implements Translator {
@Override
public String translate(String subtitle) {
return "原字幕" + "[" + subtitle + "]" +
"翻译后字幕" + "[" + "八嘎" + "]";
}
}
}
3. 桥接模式
3.1 特点和应用场景
模式特点
- 将抽象与实现解耦,使两者都可以独立变化
- 在现实生活中,某些类具有两个或多个维度的变化,如图形既可按形状分,又可按颜色分。如何设计类似于 Photoshop 这样的软件,能画不同形状和不同颜色的图形呢?如果用继承方式,m 种形状和 n 种颜色的图形就有 m×n 种,不但对应的子类很多,而且扩展困难。不同颜色和字体的文字、不同品牌和功率的汽车
- 桥接将继承转为关联,降低类之间的耦合度,减少代码量
包含角色
- 系统设计期间,如果这个类里面的一些东西,会扩展很多,这个东西就应该分离出来
- 抽象化(Abstraction)角色:定义抽象类,并包含一个对实现化对象的引用
- 扩展抽象化(Refined Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法
- 实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用
应用场景
- 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时
- 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时
- 当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时
-
3.2 案例
```java public abstract class Phone {
private String name;
// 桥接层, 一部分实现分离出去, 给外部独立扩展渠道 private Sale sale;
public Phone(String name, Sale sale) {
this.name = name;
this.sale = sale;
}
void printInfo() {
System.out.println(this.name + "售卖信息:" + sale);
}
public static class IPhone extends Phone {
public IPhone(Sale sale) {
super("苹果手机", sale);
}
}
}
```java
@ToString
public abstract class Sale {
private String type;
private String price;
public Sale(String type, String price) {
this.type = type;
this.price = price;
}
public static class OnlineSale extends Sale {
public OnlineSale(String type, String price) {
super(type, price);
}
}
public static class OfflineSale extends Sale {
public OfflineSale(String type, String price) {
super(type, price);
}
}
}
4. 装饰器模式
4.1 特点和应用场景
模式特点
- 适配器是连接两个类,可以增强一个类,装饰器是增强一个类
- 向一个现有的对象添加新的功能,同时又不改变其结构。属于对象结构型模式
- 创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能
包含角色
- 抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象
- 具体构件(ConcreteComponent)角色:实现抽象构件,通过装饰角色为其添加一些职责
- 抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能
- 具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任
应用场景
- Spring Session 中如何进行 session 与 redis 关联
- MyBatisPlus 中的 QueryWrapper
- Spring 中的 BeanWrapper
Spring Webflux 中的 WebHandlerDecorator
4.2 案例
```java public interface TikTok {
void broadcast();
class GirlAnchor implements TikTok {
@Override
public void broadcast() {
System.out.println("美女开始直播");
}
}
}
```java
public interface TikTokDecorator extends TikTok {
void enableBeauty();
class BeautyTikTokDecorator implements TikTokDecorator {
private TikTok tikTok;
public BeautyTikTokDecorator(TikTok tikTok) {
this.tikTok = tikTok;
}
@Override
public void broadcast() {
enableBeauty();
this.tikTok.broadcast();
}
@Override
public void enableBeauty() {
System.out.println("开启美颜功能");
}
}
}
5. 代理模式
5.1 特点和应用场景
模式特点
- 代理模式(Proxy Pattern) ,给某一个对象提供一个代理,并由代理对象控制对原对象的引用,对象结构型模式
- 这种也是静态代理
包含角色
- Subject: 抽象主体角色(抽象类或接口)
- Proxy: 代理主体角色(代理对象类)
- RealSubject: 真实主体角色(被代理对象类)
应用场景
- MyBatis 中的 MapperProxy
- Alibaba Seata 的 DataSourceProxy
-
5.2 静态代理
装饰器和代理之间的区别很细微,可以认为装饰器是代理的一个子集
静态代理就是装饰器模式public interface UserService {
void save();
class UserServiceImpl implements UserService {
@Override
public void save() {
System.out.println("保存用户");
}
}
}
public class UserServiceProxy implements UserService {
private UserService target;
public UserServiceProxy(UserService target) {
this.target = target;
}
@Override
public void save() {
System.out.println("执行方法前日志");
this.target.save();
System.out.println("执行方法后日志");
}
}
5.3 JDK 动态代理
只能代理接口,不能代理实现类
- 代理的类可以实现多个接口
代理对象强转时只能转成接口类型,不能转成实例类型,因为实例类型是 Proxy
public interface UserService {
void save();
class UserServiceImpl implements UserService {
@Override
public void save() {
System.out.println("保存用户");
}
}
}
```java public class JdkProxy {
public static
Object getProxy(T t) { return Proxy.newProxyInstance(
t.getClass().getClassLoader(),
t.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("执行方法前日志");
Object res = method.invoke(t, args);
System.out.println("执行方法前日志");
return res;
}
});
}
}
```java
public class JdkProxyTest {
/**
* 说明 jdk 的类是继承了 Proxy 并实现了 UserService 接口
* 所以 jdk 动态代理只支持转成接口类型, 不支持转成实例 (UserServiceImpl) 类型
* $Proxy0 extends java.lang.reflect.Proxy implements UserService
*/
public static void main(String[] args) {
Object proxy = JdkProxy.getProxy(new UserService.UserServiceImpl());
((UserService) proxy).save();
// class com.sun.proxy.$Proxy0
System.out.println(proxy.getClass());
// [interface org.masteryourself.tutorial.designpattern.structual.proxy.jdk.UserService]
System.out.println(Arrays.toString(proxy.getClass().getInterfaces()));
// class java.lang.reflect.Proxy
System.out.println(proxy.getClass().getSuperclass());
// true
System.out.println(proxy instanceof UserService);
// false
System.out.println(proxy instanceof UserService.UserServiceImpl);
// true
System.out.println(proxy instanceof Proxy);
}
}
5.4 CGLIB 动态代理
- 使用 cglib 实现动态代理,并不要求委托类必须实现接口
- CGLIB 是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法
因为是继承,所以该类或方法最好不要声明成 final,final 可以阻止继承和多态
public interface UserService {
void save();
class UserServiceImpl implements UserService {
@Override
public void save() {
System.out.println("保存用户");
}
}
}
```java public class CglibProxy {
public static
T getProxy(T t) { Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(t.getClass());
enhancer.setCallback(new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] args) throws Throwable {
System.out.println("执行方法前日志");
Object res = method.invoke(t, args);
System.out.println("执行方法后日志");
return res;
}
});
return (T) enhancer.create();
}
}
```java
public class CglibProxyTest {
public static void main(String[] args) {
UserService proxy = CglibProxy.getProxy(new UserService.UserServiceImpl());
proxy.save();
// class org.masteryourself.tutorial.designpattern.structual.proxy.jdk.UserService$UserServiceImpl$$EnhancerByCGLIB$$9ee3c719
System.out.println(proxy.getClass());
// [interface net.sf.cglib.proxy.Factory]
System.out.println(Arrays.toString(proxy.getClass().getInterfaces()));
// class org.masteryourself.tutorial.designpattern.structual.proxy.jdk.UserService$UserServiceImpl
System.out.println(proxy.getClass().getSuperclass());
// true
System.out.println(proxy instanceof UserService);
// true
System.out.println(proxy instanceof UserService.UserServiceImpl);
// false
System.out.println(proxy instanceof Proxy);
}
}
6. 外观模式
6.1 特点和应用场景
模式特点
- 外观模式(Facade Pattern)门面模式,隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口
- 这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性
- 这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用
应用场景
- JAVA 的三层开发模式
-
6.2 案例
```java public class Police {
public void register(String name) {
System.out.println(name + "登记");
}
}
```java
public class Edu {
public void assign(String name) {
System.out.println(name + "分配学校");
}
}
public class Social {
public void handleSocial(String name) {
System.out.println(name + "社保处理");
}
}
public class WechatFacade {
public void handle(String name) {
System.out.println("微信统一办事处(门面模式)");
new Police().register(name);
new Edu().assign(name);
new Social().handleSocial(name);
}
}
7. 组合模式
- 把一组相似的对象当作一个单一的对象
-
8. 享元模式
8.1 特点和应用场景
模式特点
享元模式(Flyweight Pattern),运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。对象结构型
- 在享元模式中可以共享的相同内容称为内部状态(IntrinsicState),而那些需要外部环境来设置的不能共享的内容称为外部状态(Extrinsic State),由于区分了内部状态和外部状态,因此可以通过设置不同的外部状态使得相同的对象可以具有一些不同的特征,而相同的内部状态是可以共享的
- 在享元模式中通常会出现工厂模式,需要创建一个享元工厂来负责维护一个享元池(Flyweight Pool)用于存储具有相同内部状态的享元对象
包含角色
- Flyweight: 抽象享元类(例如数据库连接池中的连接 Connection)
- ConcreteFlyweight: 具体享元类(如 Connection 的 url、passwd 等信息)
- UnsharedConcreteFlyweight: 非共享具体享元类(如 Connection 的 state 状态)
- FlyweightFactory: 享元工厂类;简单工厂(产品就是 Connection)
应用场景
- 数据库连接池
-
8.2 案例
```java public interface Waitress {
// 享元的不可共享属性留给外部进行实现 void start(String customer);
// 享元的不可共享属性留给外部进行实现 void stop(String customer);
boolean state();
@AllArgsConstructor class WaitressImpl implements Waitress {
// 共享属性
private String id;
private String name;
private Integer age;
// 不可共享属性
private boolean state;
@Override
public void start(String customer) {
System.out.println(this.name + "为顾客[" + customer + "]开始工作");
this.state = false;
}
@Override
public void stop(String customer) {
System.out.println(this.name + "为顾客[" + customer + "]结束工作");
this.state = true;
}
@Override
public boolean state() {
return this.state;
}
}
}
```java
public class Spa {
private static final Map<String, Waitress> waitressMap = new HashMap<>();
static {
waitressMap.put("4396", new Waitress.WaitressImpl("4396", "小花", 19, true));
waitressMap.put("9527", new Waitress.WaitressImpl("9527", "小翠", 23, true));
}
public Waitress getWaitress() {
Waitress waitress = waitressMap.values().stream().filter(Waitress::state).findFirst().orElse(null);
if (waitress != null) {
return waitress;
}
waitress = new Waitress.WaitressImpl("8888", "8888", 23, true);
waitressMap.put("8888", waitress);
return waitress;
}
}