(一) 桥接模式
桥接模式(Bridge Pattern): 又称之为接口模式, 将 抽象部分 与 实现部分 分离, 使它们都可以独立地变化. 属于对象结构模型的一种
桥接模式基于类的最小设计原则, 通过使用封装、聚合及继承等行为让不同的类承担不同的职责. 它最主要特点是把 抽象 与 行为实现 分离开来, 从而可以保持各部分的独立性以及应对他们的功能扩展
举例: 抽象类Shape中存在draw绘画的抽象方法, 可以画 圆形、正方形、长方形等, 又还需要给这些形状进行上色, 如白色、红色、黑色等.
第一种设计方案: 我们可以设计为每一个形状提供每一种颜色的类, UML类图表示为:
此设计方案存在的问题:
- 扩展性问题(类爆炸): 如果要新添加一个菱形, 就需要创建一个菱形类和二个具体颜色菱形类(白色和红色). 又如果, 此时要添加一种新的颜色: 黑色, 那么需要创建两个黑色的图形类(圆形和正方形)
- 违反了单一职责原则: 新增一种形状时, 却要新增各个颜色的具体形状
第二种设计方案: 影响创建最终图形有两个因素(维度), 形状(主维度) 和 颜色(次维度), 我们希望可以根据实际需求对形状和颜色进行组合. 对于多个变化维度, 可以采用桥接模式, 将 抽象部分 与 实现部分 分离.
第一种方案中的 抽象部分(Share) 与 实现部分(WhiteCircle、WhiteSquare…) 是继承关系(没有分离), 要将 继承关系 改为 聚合关系(分离). 博主理解的是将 两个维度: 形状 和 颜色, 由原来的继承关系 变成 聚合关系
次维度接口(颜色)
interface Color {
void bepaint(String shape); // 为 shape形状 着色
}
次维度接口实现类
class RedColor implements Color {
@Override
public void bepaint(String shape) {
System.out.println("红色的" + shape);
}
}
class WhiteColor implements Color {
@Override
public void bepaint(String shape) {
System.out.println("白色的" + shape);
}
}
主维度抽象类(桥), 拥有次维度接口成员属性. 主维度 和 次维度 组合关系
abstract class Shape {
private Color color;
public Shape(Color color) {
this.color = color;
}
public void setColor(Color color) {
this.color = color;
}
abstract void draw();
}
主维度实现类, 通过主维度抽象类(桥) 调用 次维度接口方法
class Circle extends Shape {
public Circle(Color color) {
super(color);
}
@Override
void draw() {
color.bepaint("圆形");
}
}
class Square extends Shape {
public Square(Color color) {
super(color);
}
@Override
void draw() {
color.bepaint("正方形");
}
}
测试:
public class BridgePattern {
public static void main(String[] args) {
Circle whiteCircle = new Circle(new WhiteColor());
whiteCircle.draw();
Circle redCircle = new Circle(new RedColor());
redCircle.draw();
Square whiteSquare = new Square(new WhiteColor());
whiteSquare.draw();
Square redSquare = new Square(new RedColor());
redSquare.draw();
}
}
桥接模式的注意事项和细节
- 实现了抽象和实现部分的分离, 从而极大的提功力系统的灵活性, 让抽象部分和实现部分独立分开来, 这有助于系统进行分册设计, 从而生产更好的结构化系统
- 桥接模式替代多重继承方案, 可以减少子类的个数, 降低系统的管理和维护成本
- 桥接模式的引入增加了系统理解和设计难度, 由于聚合关联关系建立在抽象层, 要求开发者针对抽象进行设计和编程
- 桥接模式要求正确识别出系统中两个或逗哥独立变化的维度, 适用于 不希望使用继承 或 多层继承导致系统类的个数急剧增加的系统.
(二) 装饰者模式
装饰者模式(Decorator Pattern): 在不改变原有对象的基础之上, 动态的将新功能附加到对象上, 在对象功能扩展方面, 它比继承更有弹性, 装饰者模式也提现了开闭原则(OCP)
举例: 有一家咖啡店, 已出售的咖啡种类有: 摩卡和拿铁咖啡. 现在需要在已有的咖啡种类上添加调味: 牛奶 和 茶. 可自由组合, 如: 一份摩卡咖啡加一份牛奶、一份拿铁咖啡加两份茶… UML类图所示:
咖啡抽象类和具体咖啡实体: 咖啡抽象类是 具体咖啡 和 抽象装饰类 的共同父类
abstract class Coffee {
public abstract String getDes(); // 描述
public abstract Float getPrice(); // 价格
}
class MochaCoffee extends Coffee {
@Override
public String getDes() {
return "摩卡咖啡";
}
@Override
public Float getPrice() {
return 10.5f;
}
}
class LatteCoffer extends Coffee {
@Override
public String getDes() {
return "拿铁咖啡";
}
@Override
public Float getPrice() {
return 20.5f;
}
}
抽象装饰器类: 继承抽象类, 组合抽象类. 用于给具体实体类增加职责
class AbstractDecorator extends Coffee {
protected Coffee coffee;
public AbstractDecorator(Coffee coffee) {
this.coffee = coffee;
}
@Override
public String getDes() {
return coffee.getDes();
}
@Override
public Float getPrice() {
return coffee.getPrice();
}
}
牛奶装饰器: 继承抽象装饰器, 在抽象父类加入牛奶, 且价格加 2 元
茶装饰器: 继承抽象装饰器, 在抽象父类加入茶, 且价格加 2.5 元
class MikeDecorator extends AbstractDecorator {
public MikeDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDes() {
return coffee.getDes() + " 加入一份牛奶";
}
@Override
public Float getPrice() {
return coffee.getPrice() + 2F;
}
}
class TeaDecorator extends AbstractDecorator {
public TeaDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDes() {
return coffee.getDes() + " 加入一份茶";
}
@Override
public Float getPrice() {
return coffee.getPrice() + 2.5F;
}
}
测试:
public class DecoratorPattern {
public static void main(String[] args) {
// 单点一份 摩卡咖啡
Coffee coffee = new MochaCoffee();
System.out.println(coffee.getDes() + ", 价格: " + coffee.getPrice());
// 摩卡咖啡 + 一份牛奶
coffee = new MikeDecorator(coffee);
System.out.println(coffee.getDes() + ", 价格: " + coffee.getPrice());
// 摩卡咖啡 + 两份牛奶
coffee = new MikeDecorator(coffee);
System.out.println(coffee.getDes() + ", 价格: " + coffee.getPrice());
// 摩卡咖啡 + 两份牛奶 + 一份茶
coffee = new TeaDecorator(coffee);
System.out.println(coffee.getDes() + ", 价格: " + coffee.getPrice());
}
}
(三) 组合模式
组合模式(Composite Pattern): 又称之为部分整体模式, 是用于把一组相似的对象当作一个单一的对象. 组合模式依据树形结构来组合对象, 用来表示 “整体-部分” 的层次关系.
组合模式使得用户对单个对象 和 组合对象的访问具有一致性, 即: 能让客户以一致的方式处理单一对象 和 组合对象
举例: 学校院系展示需求, 展示出: 一个学校有多少个学院, 每个学院有多少个系.
采用组合模式分析:
- 抽取 一组相似的对象(学校、学院、系) 中共有的属性和方法 组成一个单一的对象(Component)
- 属性: 名称, 描述, 创建时间 …
- 方法: 打印对象信息的方法, 添加、删除、修改的方法… - 以依据树形结构来组合对象, 用来表示 “整体-部分” 的层次关系
- UML类图展示: 用集合表示是否有下一层
组合模式中的角色:
- Component角色: 将 一组相似的对象 抽取 一个单一的共同对象Component(树结构中的子节点), 共同对象中拥有 这组相似的对象 共有的属性和方法
- Composite角色: 非叶子节点, 继承Component公共类, 覆盖重写所有的公共方法
- Leaf角色: 叶子节点, 继承Component公共类, 没有下一层(集合), 无需实现所有的公共方法
Component公共类: 抽取了 名称公共属性 和 添加、删除、打印信息的公共方法
abstract class Component {
protected String name; // 名称
public Component(String name) {
this.name = name;
}
// 打印Component共同对象信息
public abstract void printInfo();
// 默认实现 一组相似对象 中共有的接口: 添加add、删除remove
public void add(Component component) {
throw new UnsupportedOperationException();
}
public void remove(Component component) {
throw new UnsupportedOperationException();
}
}
大学实体类Composite: 拥有学院层(学院集合)
class University extends Component {
// 学院列表
private List<Component> colleges = new ArrayList<>();
public University(String name) {
super(name);
}
@Override
public void printInfo() {
System.out.println("----------------" + super.getName() + "----------------");
for (Component college : colleges) {
college.printInfo(); // 打印学院信息
}
}
@Override
public void add(Component component) {
colleges.add(component);
}
@Override
public void remove(Component component) {
colleges.remove(component);
}
}
学院实体类Composite: 拥有系列层(系列集合)
class College extends Component {
private List<Component> departments = new ArrayList<>();
public College(String name) {
super(name);
}
@Override
public void printInfo() {
System.out.println("----------------" + super.getName() + "----------------");
for (Component department : departments) {
department.printInfo(); // 打印学院信息
}
}
@Override
public void add(Component component) {
departments.add(component);
}
@Override
public void remove(Component component) {
departments.remove(component);
}
}
系实体类Leaf: 没有下一层, 叶子节点, 不需要实现某些共有的方法 add remove
class Department extends Component {
public Department(String name) {
super(name);
}
@Override
public void printInfo() {
System.out.println(super.getName());
}
}
测试:
public class CompositePattern {
public static void main(String[] args) {
University university = new University("清华大学");
// 添加学院
College computeCollege = new College("计算机学院");
College infoCollege = new College("信息工程学院");
university.add(computeCollege);
university.add(infoCollege);
// 添加系
computeCollege.add(new Department("软件工程"));
computeCollege.add(new Department("网络工程"));
infoCollege.add(new Department("通信工程"));
infoCollege.add(new Department("信息工程"));
university.printInfo();
}
}
组合模式的总结
- 简化客户端操作. 客户端只需要面对一致的对象Component而不用考虑整体部分或者结点叶子
- 具有较强的扩展性. 当需要更改组合对象时, 只需要调整内部的层次关系,
(四) 外观模式
外观模式(Facade Pattern): 又称之为过程模式, 外观模式为子系统类中的一组接口提供一个一致的界面, 此模式定义了一个高层接口, 用以屏蔽内部子系统的细节, 使得调用(客户)端只需要跟这个接口发送调用, 而无需关心这个子系统的内部细节
外观模式中的角色:
- Facade外观角色: 组合子系统类, 外观类对客户端提供统一的调用接口. 外观类知道 各个子系统类中的接口的具体功能, 从而将客户端的请求 代理给适当的 子系统对象
- Subsystem子系统角色: 是具体功能的实际提供者, 处理Facade 对象指派的任务.
举例: 银行ATM机器采用的是外观模式, 向用户提供业务功能(存取款), 用户只需要选择对应的功能, 就能完成对应的业务(存取款), 用户无需知道存取款业务的具体流程
- ATM外观角色: 提供存款、取款业务(接口)
- 银行卡子系统: 记录 账户、密码、余额 的具体功能
- 银行子系统: 校验用户、输入操作金额、校验金额、具体业务操作功能
银行卡子系统: 提供 账户、密码具体功能
class BankCard {
private static BankCard bankCard = new BankCard();
public static BankCard getBankCard() {
return bankCard;
}
public void inputAccount() {
System.out.println("输入账号");
}
public void inputPwd() {
System.out.println("输入密码");
}
}
银行子系统: 提供 输入操作金额、校验金额、存钱、取钱的具体功能
class Bank {
private static Bank bank = new Bank();
public static Bank getBank() {
return bank;
}
public void operationMoney() {
System.out.println("输入操作金额");
}
public void verifyMoney() {
System.out.println("校验金额数量、真假");
}
public void saveMoney() {
System.out.println("存款成功");
}
public void withdrawalMoney() {
System.out.println("提款成功");
}
}
ATM外观: 提供存款、取款业务接口
class ATMFacade {
private BankCard bankCard = BankCard.getBankCard();
private Bank bank = Bank.getBank();
/**
* 存款业务
*/
public void saveBusiness() {
bankCard.inputAccount();
bankCard.inputPwd();
bank.operationMoney();
bank.verifyMoney();
bank.saveMoney();
}
/**
* 取款业务
*/
public void withdrawalBusiness() {
bankCard.inputAccount();
bankCard.inputPwd();
bank.operationMoney();
bank.verifyMoney();
bank.withdrawalMoney();
}
}
测试
public class FacadePattern {
public static void main(String[] args) {
ATMFacade bankFacade = new ATMFacade();
System.out.println("---------存款业务---------");
bankFacade.saveBusiness();
System.out.println("---------取款业务---------");
bankFacade.withdrawalBusiness();
}
}
外观模式的总结
- 外观模式对外屏蔽了子系统的细节, 因此外观模式降低了客户端对子系统使用的复杂性
- 外观模式对 客户端与子系统进行了解耦, 让子系统内部的模块更易维护和扩展
- 通过合理的的使用外观模式, 可以帮我们更好的划分访问层次(MVC)
(五) 享元模式
享元模式(Flyweight Pattern): 又称之为蝇量模式, 运用共享技术有效地支持大量细粒度的对象. 常与工厂模式一同使用. 享元模式经典的应用场景就是池技术: String常量池、数据库连接池、缓冲池等等(使用对象时 , 先从对象池中获取对象 , 如果对象池中没有 , 创建一个 , 放入对象池 , 然后再从对象池中获取)
享元模式能够解决 创建重复对象的内存浪费的问题, 当系统中有大量相似对象, 可以提供缓冲池, 不需要总是创建新的对象, 可以从缓冲池中取出, 从而达到降低系统内存,同时提高效率的目的
细粒度对象: 对象数量多且对象属性(性质)相似, 将细粒度对象中的属性分成两部分
- 内部状态: 指对象共享出来的的信息, 存储在享元对象内部且不会随环境的改变而改变(围棋棋子的颜色:白色 和 黑色)
- 外部状态: 指对象得以依赖的一个标记, 是随环境的变化而改变的, 不可共享的状态(棋子的位置)
享元模式中的角色:
- Flyweight 抽象享元类: 通常是一个接口或者抽象类, 是细粒度对象的抽象类
- ConcreteFlyweight 具体享元类: 实现了Flyweight, 是具体的细粒度对象, 在抽象享元类和具体的享元类中定义内部状态和外部状态
- FlyweightFactory 享元工厂类: 用于构建一个池容器(Map集合), 从这个池容器中获取具体的享元类, 从而达到共享对象的目的
举例: 有一篇文章笔记, 写的非常有技术含量. 此时恰巧有三位童鞋(小明、小红、小刚)想转载这篇笔记, 但他们的转载形式不一样
- 小明和小刚想以博客的形式转载这篇文章
- 而小红想以网站的形式转载这篇文章
可以采用享元模式来思考解决问题
- 确定细粒度对象(对象数量多且对象属性(性质)相似): 笔记转载对象, 由笔记 + 转载形式 + 转载者 构成
- 分析内部状态(不会随环境的改变而改变): 笔记的内容、转载形式(博客、网站), 这些内部状态时可共享的
- 分析外部状态(随环境的变化而改变): 转载者(无法确定转载者的数量), 无法共享的状态
抽象享元类: 提供一个抽象的转载文章的方法: reprint(String 转载者), 定义了外部状态
abstract class NoteFlyweight {
/**
* 笔记转载
* author: 笔记的外部状态, 转载者(随转载者的不同而不同, 不可共享的状态)
*/
abstract void reprint(String publisher);
}
具体享元角色, 继承抽象享元类, 定义了 转载形式 内部状态
class ConcreteNote extends NoteFlyweight {
// 笔记的内部状态, 转载形式(博客、网站, 可共享的状态) 定义在抽象享元类的实现类中
private String type;
public ConcreteNote (String type) {
this.type = type;
}
@Override
void reprint(String publisher) {
System.out.println("笔记转载者: " + publisher + "; 笔记的转载类型: " + type);
}
}
享元工厂类: 用于构建一个池容器(Map集合), 从这个池容器中根据 转载形式(内部状态) 获取具体的享元类
class FlyweightFactory {
private Map<String, ConcreteNote> pool = new HashMap<>();
// 根据 转载形式(内部状态) 获取具体的享元类
public NoteFlyweight getNodeByType(String type) {
if (!pool.containsKey(type)) {
pool.put(type, new ConcreteNote(type));
}
return pool.get(type);
}
// 池容器 存储 享元类的个数
public int getNodeCount() {
return pool.size();
}
}
测试:
public class FlyweightPattern {
public static void main(String[] args) {
FlyweightFactory factory = new FlyweightFactory();
// 小明以博客的形式转载此笔记
NoteFlyweight node1 = factory.getNodeByType("博客"); // 从工厂池中取出可共享的笔记对象
node1.reprint("小明"); // 可共享的笔记对象 + 不可共享的转载者
// 小红以博客的形式转载此笔记
NoteFlyweight node2 = factory.getNodeByType("网站"); // 从工厂池中取出可共享的笔记对象
node2.reprint("小红"); // 可共享的笔记对象 + 不可共享的转载者
// 小刚以博客的形式转载此笔记
NoteFlyweight node3 = factory.getNodeByType("博客"); // 从工厂池中取出可共享的笔记对象
node3.reprint("小刚"); // 可共享的笔记对象 + 不可共享的转载者
System.out.println();
System.out.println("池容器 存储 享元类的个数 = " + factory.getNodeCount());
}
}
(六) 代理模式
代理模式(ProxyPattern): 为一个对象提供一个替身, 以控制对这个对象的访问. 即通过代理对象访问目标对象. 可以在目标对象实现的基础上, 增强额外的功能操作, 即扩展目标对象的功能
代理模式有三种形式:
- 静态代理
- 动态代理(动态代理、接口代理)
- Cglib代理: 可以在内存动态的创建对象, 而不需要实现接口
静态代理: 需要定义接口或者父类, 被代理对象(目标对象) 与 代理对象 需要一同实现相同的接口或者是继承相同的父类
// 教师接口
interface ITeacher {
void teach();
}
// 教师实现类, 被代理对象
class Teacher implements ITeacher {
@Override
public void teach() {
System.out.println("教师正在授课...");
}
}
// 代理对象, 实现 ITeacher接口, 组合 ITeacher类
class TeacherProxy implements ITeacher {
private ITeacher target;
public TeacherProxy(ITeacher target) {
this.target = target;
}
// 通过成员属性target目标对象 来调用teach方法, 在执行teach方法前后 扩展目标对象的功能
@Override
public void teach() {
System.out.println("开始代理...");
target.teach();
System.out.println("代理结束...");
}
}
测试:
public class StaticProxyPattern {
public static void main(String[] args) {
// 目标对象
ITeacher target = new Teacher();
// 代理对象, 将目标对象传递给代理对象
TeacherProxy proxy = new TeacherProxy(target);
// 代理对象通过目标对象执行teach方法, 且在执行方法前可对目标对象的扩展功能
proxy.teach();
}
}
静态代理的优缺点:
- 优点: 在不修改目标对象的功能前提下, 能通过代理对象对目标对象的功能扩展
- 缺点: 代理对象需要和目标对象实现了同一接口, 会存在很多代理类. 且 一旦接口增加方法, 目标对象与代理对象都要维护
动态代理: 代理对象不需要实现接口, 但目标接口一定要实现接口(否则不能使用动态代理), 代理对象由JDK的API(反射), 动态的在内存中创建代理对象. 动态代理又因此称之为: JDK代理、接口代理
JDK中生成代理对象的API: java.lang.reflect.Proxy.newProxyInstance(ClassLoader, Class<?>[], InvocationHandler) 方法, 方法返回一个代理对象(Object), 该方法接受三个参数:
- ClassLoader laoder: 目标对象(被代理对象)的类加载器, 对生成的代理对象进行加载
- Class<?>[] interfaces: 目标对象(被代理对象)实现的接口类型数组, 生成的代理对象会实现这些接口
- InvocationHandler 接口: 调用处理器接口, 只有一个抽象方法 invoke(Object, Method, Object[]), 当调用执行代理对象的方法时, 会转发到的 InvocationHandler.invoke 方法来执行, 且会将目标对象和目标对象的方法作为参数传递, invoke 返回值是 Object (执行方法后返回的对象)
- Object proxy: 目标对象(被代理的对象)
- Method method: 调用目标对象的方法Method
- Object[] args: 调用目标对象方法时接受的参数
public class DynamicProxyPattern {
public static void main(String[] args) {
// 创建目标对象(被代理对象)
Teacher teacher = new Teacher();
// 给目标对象创建代理对象
ProxyFactory proxyFactory = new ProxyFactory(teacher);
// 放回的代理对象是Object类型, 由于指定了目标对象实现的接口, 导致代理对象也实现了此接口, 强转
ITeacher proxy = (ITeacher) proxyFactory.getProxyInstance();
System.out.println("代理对象的类型: " + proxy.getClass());
proxy.teach();
}
}
interface ITeacher {
void teach();
}
class Teacher implements ITeacher {
@Override
public void teach() {
System.out.println("教师正在授课...");
}
}
class ProxyFactory {
private Object target; // 目标对象, 被代理的对象
public ProxyFactory(Object target) {
this.target = target;
}
/**
* 通过 java.lang.reflect.Proxy.newProxyInstance() 方法生成代理对象实例
*
* @return
*/
public Object getProxyInstance() {
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK动态代理, 在目标方法执行之前扩展功能");
/*
method.invoke(Object obj, Object... args): 执行目标对象(被代理对象)的方法, 方法返回Object
obj: 目标对象实例, 由这个实例去执行方法
args: 执行这个方法需要的参数数组
*/
Object invoke = method.invoke(target, args);
System.out.println("JDK动态代理, 在目标方法执行之后扩展功能");
return invoke;
}
});
}
}
测试
Cglib代理: Cglib代理是可以对没有实现接口的类产生代理对象, 是在内存中, 通过继承目标类来产生代理对象, 因此也称之为子类代理.
Cglib是一个强大的高性能的代码生成包(需要引入), 它广泛运用于AOP的框架中, 如 Spring AOP. 底层通过字节码处理框架ASM来转换字节码并生成代理类, final修饰的类, 无法继承; final修饰的方法, 无法覆盖重写
引入jia包: asm-7.0-beta.jar、asm-commons-7.0-beta.jar、asm-util-7.0-beta.jar、cglib-3.1.jar
public class CglibProxyPattern {
public static void main(String[] args) {
// 创建目标对象(被代理对象)
Teacher teacher = new Teacher();
// 给目标对象创建代理对象
ProxyFactory proxyFactory = new ProxyFactory(teacher);
// 放回的代理对象是Object类型, 由于Enhancer设置父类类型, 导致代理对象也继承了此父类, 强转
Teacher proxy = (Teacher) proxyFactory.getProxyInstance();
System.out.println("代理对象的类型: " + proxy.getClass());
System.out.println();
proxy.teach();
}
}
interface ITeacher {
void teach();
}
class Teacher implements ITeacher {
@Override
public void teach() {
System.out.println("教师正在授课...");
}
}
/**
* 代理工厂实现 MethodInterceptor 接口生成方法拦截器intercept方法
*/
class ProxyFactory implements MethodInterceptor {
private Object target; // 目标对象, 被代理的对象
public ProxyFactory(Object target) {
this.target = target;
}
/**
* 通过 Cglib 生成代理对象实例
* @return
*/
public Object getProxyInstance() {
// 1.创建 Enhancer对象, 创建代理类
Enhancer enhancer = new Enhancer();
// 2.设置父类类型, 生成的代理对象继承父类类型
enhancer.setSuperclass(target.getClass());
// 3.设置回调函数
enhancer.setCallback(this);
// 4. 创建代理子对象
return enhancer.create();
}
/**
* 方法拦截器, 类似JDK代理的InvocationHandler接口实现类
* 当调用执行代理对象的方法时, 会转发到的 intercept 方法来执行, 且会将目标对象和目标对象的方法作为参数传递
* intercept 返回值是 Object (执行方法后返回的对象)
*
* @param o 目标对象
* @param method 调用目标对象的方法Method
* @param args 调用目标对象方法时接受的参数
* @param methodProxy 代理对象
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("Cglib动态代理, 在目标方法执行之前扩展功能");
Object ret = method.invoke(target, args);
System.out.println("Cglib动态代理, 在目标方法执行之后扩展功能");
return ret;
}
}
测试:
(七) 适配器模式
可以看博主另一篇文章: 适配器模式之3种实现方式