文章目录
1. 设计模式概述
1.1 什么是设计模式
设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。
1995年,GoF(Gang of Four,四人组/四人帮)合作出版了《设计模式:可复用面向对象软件的基础》一书,共收录了23种设计模式,从此树立了软件设计模式领域的里程碑,人称「GoF设计模式」。
1.2 学习设计模式的意义
-
设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。
-
正确使用设计模式具有以下优点:
- 可以提高程序员的思维能力、编程能力和设计能力。
- 使程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期。
- 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。
1.3 设计模式的基本要素
- 模式名称
- 问题
- 解决方案
- 效果
1.4 GoF23
-
GoF 23
- —种思维,一种态度,一种进步
-
创建型模式:
- 单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式。
-
结构型模式:
- 适配器模式,桥接模式,装饰模式,组合模式,外观模式,享元模式,代理模式
-
行为型模式:
- 模板方法模式,命令模式,迭代器模式,观察者模式,中介者模式,备忘录模式,解释器模式,状态模式,策略模式,职责链模式,访问者模式。
2.OOP七大原则
开闭原则: 对扩展开放,对修改关闭。
里氏替换原则: 继承必须确保超类所拥有的性质在子类中仍然成立。
依赖倒置原则: 要面向接口编程,不要面向实现编程。
单一职责原则: 控制类的粒度大小、将对象解耦、提高其内聚性。
接口隔离原则: 要为各个类建立它们需要的专用接口。
迪米特法则: 只与你的直接朋友交谈,不跟“陌生人”说话。
合成复用原则: 尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
3.创建型模式
3.1 单例模式
饿汉式, DCL懒汉式, 深究!
饿汉式
// 饿汉式单例
public class HungrySingleton {
// 一开始就全部创建,可能会浪费空间
private byte[] data1 = new byte[1024 * 1024];
private byte[] data2 = new byte[1024 * 1024];
private byte[] data3 = new byte[1024 * 1024];
private byte[] data4 = new byte[1024 * 1024];
// 单例模式构造器私有
private HungrySingleton() {
}
private final static HungrySingleton HUNGRY = new HungrySingleton();
public static HungrySingleton getInstance() {
return HUNGRY;
}
}
DCL懒汉式
// 懒汉式单例
// 道高一尺魔高一丈
public class LazySingleton {
// 解决利用反射创建两个对象的带来的破坏
// 利用标志位解决
private static boolean flag = false;
// 单例模式构造器私有
private LazySingleton() {
// 为解决反射带来的破坏,我们需要在这里加锁
synchronized (LazySingleton.class) {
// 标志位判断
if (flag == false) {
flag = true;
} else {
throw new RuntimeException("不要试图使用反射破坏懒汉单例模式");
}
}
System.out.println(Thread.currentThread().getName() + " start!");
}
private volatile static LazySingleton lazySingleton; // 保证原子性
// 双重检测锁模式 懒汉式单例 简称DCL懒汉式
public static LazySingleton getInstance() {
// 加锁(不加锁可能出现多个线程同时执行,加了锁之后只有 Thread-0 start!)
if (lazySingleton == null) {
synchronized (LazySingleton.class) {
if (lazySingleton == null) {
lazySingleton = new LazySingleton(); // 不是一个原子性操作
/**
* lazySingleton = new LazySingleton();代码执行有以下三步过程:
* 1.分配内存空间s
* 2.执行构造方法,初始化对象o
* 3.把这个对象o指向这个空间s
*
* 期望上述步骤执行顺序 123
* 实际可能执行顺序 132
*
* 假设线程0开启,执行步骤顺序为 132
* 先分配内存空间s,再使用一个空对象null把这个内存空间s占用了,之后再初始化一个对象o,把它放入内存空间s
* 此时线程0正常
*
* 假设此时线程1开启,由于步骤3对象o已经指向空间s,线程1会认为lazySingleton != null,然后其直接return lazySingleton
* 此时这个lazySingleton还没有完成构造,其对应的空间是不存在的,出现问题
*/
}
}
}
return lazySingleton; // 此时lazySingleton还没有完成构造
}
// 单线程下正常
// 多线程并发测试——并发的情况下有时会出现多个线程同时执行——加锁控制使得只有一个线程执行
// 在MyTest中测试
// 反射——可以破坏这种单例——怎么解决?
public static void main(String[] args) throws Exception {
LazySingleton instance1 = LazySingleton.getInstance();
Constructor<LazySingleton> declaredConstructor = LazySingleton.class.getDeclaredConstructor(null); // 利用反射获得无参构造器
declaredConstructor.setAccessible(true); // 无视私有化
LazySingleton instance2 = declaredConstructor.newInstance();// 创建对象
System.out.println(instance1==instance2);
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
静态内部类单例
// 静态内部类实现(不安全)
public class HolderSingleton {
// 单例模式构造器私有
private HolderSingleton() {
}
public static HolderSingleton getInstance() {
return InnerClass.HOLDER_SINGLETON;
}
public static class InnerClass {
private static final HolderSingleton HOLDER_SINGLETON = new HolderSingleton();
}
}
反射测试,不断挑战单例的安全性:
import com.GrandNovice.Singleton.LazySingleton;
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class LazySingletonTest {
// 单线程下正常
// 多线程并发测试——并发的情况下有时会出现多个线程同时执行——加锁控制使得只有一个线程执行
@Test
public void test_MultiThread() {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
LazySingleton.getInstance();
}).start();
}
}
// 反射——可以破坏这种单例——怎么解决?
@Test
public void test_Reflection() throws Exception {
LazySingleton instance1 = LazySingleton.getInstance();
Constructor<LazySingleton> declaredConstructor = LazySingleton.class.getDeclaredConstructor(null); // 利用反射获得无参构造器
declaredConstructor.setAccessible(true); // 无视私有化
LazySingleton instance2 = declaredConstructor.newInstance();// 创建对象
System.out.println(instance1==instance2);
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
// 两个对象都是反射创建,单例模式再次被破坏
@Test
public void test_DoubleReflection() throws Exception {
Constructor<LazySingleton> declaredConstructor = LazySingleton.class.getDeclaredConstructor(null); // 利用反射获得无参构造器
declaredConstructor.setAccessible(true); // 无视私有化
LazySingleton instance1 = declaredConstructor.newInstance();// 创建对象
LazySingleton instance2 = declaredConstructor.newInstance();// 创建对象
System.out.println(instance1==instance2);
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
// 进一步破坏标志位带来的保护
@Test
public void test_flag() throws Exception{
Field flag = LazySingleton.class.getDeclaredField("flag"); // 拿到标志位
flag.setAccessible(true); // 破坏权限
Constructor<LazySingleton> declaredConstructor = LazySingleton.class.getDeclaredConstructor(null); // 利用反射获得无参构造器
declaredConstructor.setAccessible(true); // 无视私有化
LazySingleton instance1 = declaredConstructor.newInstance();// 创建对象
flag.set(instance1, false); // 把第一个对象的flag改为false
LazySingleton instance2 = declaredConstructor.newInstance();// 创建对象
System.out.println(instance1==instance2);
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
}
枚举单例
// enum 是一个什么? enum本身也是一个Class类
public enum EnumSingleton {
INSTANCE;
public EnumSingleton getInstance() {
return INSTANCE;
}
}
枚举单例测试:
import com.GrandNovice.Singleton.EnumSingleton;
import org.junit.Test;
import java.lang.reflect.Constructor;
public class EnumSingletonTest {
@Test
public void test_enum() throws Exception{
EnumSingleton instance1 = EnumSingleton.INSTANCE;
Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor(String.class, int.class); // 虚假的无参构造,通过jad反编译得知实际为有参构造
declaredConstructor.setAccessible(true); // 破坏权限
EnumSingleton instance2 = declaredConstructor.newInstance();
System.out.println(instance1==instance2);
System.out.println(instance1);
System.out.println(instance2);
}
}
EnumSingleton.class反编译后的代码:
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: EnumSingleton.java
package com.GrandNovice.Singleton;
public final class EnumSingleton extends Enum
{
public static EnumSingleton[] values()
{
return (EnumSingleton[])$VALUES.clone();
}
public static EnumSingleton valueOf(String name)
{
return (EnumSingleton)Enum.valueOf(com/GrandNovice/Singleton/EnumSingleton, name);
}
private EnumSingleton(String s, int i)
{
super(s, i);
}
public EnumSingleton getInstance()
{
return INSTANCE;
}
public static final EnumSingleton INSTANCE;
private static final EnumSingleton $VALUES[];
static
{
INSTANCE = new EnumSingleton("INSTANCE", 0);
$VALUES = (new EnumSingleton[] {
INSTANCE
});
}
}
进入类所在的目录,然后运行反编译命令javap -p EnumSingleton.class
把jad.exe文件复制到该目录下,然后运行命令jad -s java EnumSingleton.class
狗东西,还是被我用jad反编译发现了
回到测试代码修改获取的构造器,发现反射确实不能破坏枚举单例
3.2 工厂模式
-
作用:
- 实现了创建者和调用者的分离
- 详细分类:
- 简单工厂模式
- 工厂方法模式
- 抽象工厂模式
-
OOP七大原则
- 开闭原则:一个软件的实体应当对扩展开放,对修改关闭
- 依赖倒转原则:要针对接口编程,不要针对实现编程
- 迪米特法则:只与你直接的朋友通信,而避免和陌生人通信
-
核心本质:
- 实例化对象不使用new,用工厂方法代替
- 将选择实现类,创建对象统一管理和控制。从而将调用者跟我们的实现类解耦。
-
三种模式:
- 简单工厂模式
- 用来生产同一等级结构中的任意产品(对于增加新的产品,需要扩展已有代码)
- 用来生产同一等级结构中的任意产品(对于增加新的产品,需要扩展已有代码)
- 简单工厂模式
代码:
- 工厂方法模式
- 用来生产同一等级结构中的固定产品(支持增加任意产品)
代码:
在这里插入图片描述
- 用来生产同一等级结构中的固定产品(支持增加任意产品)
简单工厂和工厂方法模式的对比:
结构复杂度:简单工厂模式更优
代码复杂度:简单工厂模式更优
编程复杂度:简单工厂模式更优
管理复杂度:简单工厂模式更优
根据设计原则:选择工厂方法模式
根据实际业务:选择简单工厂模式
小结:
- 简单工厂模式(静态工厂模式)
- 虽然某种程度上不符合设计原则,但实际使用最多!
- 工厂方法模式
- 不修改已有类的前提下,通过增加新的工厂类实现扩展。
- 抽象工厂模式
- 不可以增加产品,可以增加产品族!
应用场景:
-
JDK中Calendar的getInstance方法
-
JDBC中的Connection对象的获取
-
Spring中IOC容器创建管理bean对象
-
反射中Class对象的newInstance方法
-
抽象工厂模式
- 围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。
3.3 抽象工厂模式
-
定义︰抽象工厂模式提供了一个创建一系列相关或者相互依赖对象的接口,无需指定它们具体的类
-
适用场景:
- 客户端(应用层)不依赖于产品类实例如何被创建、实现等细节
- 强调一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量的重复代码
- 提供一个产品类的库,所有的产品以同样的接口出现,从而使得客户端不依赖于具体的实现
-
优点:
- 具体产品在应用层的代码隔离,无需关心创建的细节
- 将一个系列的产品统一到一起创建
-
缺点:
- 规定了所有可能被创建的产品集合,产品簇中扩展新的产品困难;
- 增加了系统的抽象性和理解难度
UML类图
选中全部类,ctrl + shift + alt + u
然后
3.4 建造者模式
-
建造者模式也属于创建型模式,它提供了一种创建对象的最佳方式。
-
定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示
-
主要作用:在用户不知道对象的建造过程和细节的情况下就可以直接创建复杂的对象。
-
用户只需要给出指定复杂对象的类型和内容,建造者模式负责按顺序创建复杂对象(把内部的建造过程和细节隐藏起来)
-
例子:
- 工厂(建造者模式)∶负责制造汽车(组装过程和细节在工厂内)
- 汽车购买者(用户)︰你只需要说出你需要的型号(对象的类型和内容),然后直接购买就可以使用了(不需要知道汽车是怎么组装的(车轮、车门、发动机、方向盘等等))
-
角色分析
-
既然是建造者模式,那么我们还是继续造房吧,其实我也想不到更简单的例子。假设造房简化为如下步骤:(1)地基(2)钢筋工程(3)铺电线(4)粉刷;“如果”要盖一座房子,首先要找一个建筑公司或工程承包商(指挥者)。承包商指挥工人(具体建造者)过来造房子(产品),最后验收。
-
上面示例是Builder模式的常规用法,导演类Director在Builder模式中具有很重要的作用,它用于指导具体构建者如何构建产品,控制调用先后次序,并向调用者返回完整的产品类,但是有些情况下需要简化系统结构,可以把Director和抽象建造者进行结合。
-
通过静态内部类方式实现零件无序装配构造,这种方式使用更加灵活,更符合定义。内部有复杂对象的默认实现,使用时可以根据用户需求自由定义更改内容,并且无需改变具体的构造方式。就可以生产出不同复杂产品
-
比如:比如麦当劳的套餐,服务员(具体建造者)可以随意搭配任意几种产品(零件)组成一款套餐(产品),然后出售给客户。比第一种方式少了指挥者,主要是因为第二种方式把指挥者交给用户来操作,使得产品的创建更加简单灵活。
-
优点:
- 产品的建造和表示分离,实现了解耦。使用建造者模式可以使客户端不必知道产品内部组成的细节。
- 将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰
- 具体的建造者类之间是相互独立的,这有利于系统的扩展。增加新的具体建造者无需修改原有类库的代码,符合“开闭原则“。
-
缺点:
- 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似;如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
庞大。 - 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很
- 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似;如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
-
应用场景:
- 需要生成的产品对象有复杂的内部结构,这些产品对象具备共性;
- 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。
- 适合于一个具有较多的零件(属性)的产品(对象)的创建过程。
-
建造者与抽象工厂模式的比较:
- 与抽象工厂模式相比,建造者模式返回一个组装好的完整产品,而抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族。
- 在抽象工厂模式中,客户端实例化工厂类,然后调用工厂方法获取所需产品对象,而在建造者模式中,客户端可以不直接调用建造者的相关方法,而是通过指挥者类来指导如何生成对象,包括对象的组装过程和建造步骤,它侧重于一步步构造一个复杂对象,返回一个完整的对象。
- 如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车!
3.5 原型模式
- 克隆
- Prototype
- Cloneable接口
- clone()方法
demo01浅克隆
demo02 深克隆
重写clone方法实现深克隆
@Override
protected Object clone() throws CloneNotSupportedException {
Object obj = super.clone();
// 实现深克隆 序列化/反序列化
Video v = (Video) obj;
//将这个对象的属性也进行克隆
v.createDate = (Date) this.createDate.clone();
return obj;
}
4. 结构型模式
-
作用
- 从程序的结构上实现松耦合,从而可以扩大整体的类结构,用来解决更大的问题。
-
分类
- 适配器模式
- 代理模式
- 桥接模式
- 装饰模式
- 组合模式
- 外观模式
- 享元模式
4.1 适配器模式
-
USB网线转换器(显示器转接器)
-
将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作!
角色分析
- 目标接口:客户所期待的接口,目标可以是具体的或抽象的类,也可以是接口。
- 需要适配的类:需要适配的类或适配者类。
- 适配器:通过包装一个需要适配的对象,把原接口转换成目标对象!
-
对象适配器优点
- 一个对象适配器可以把多个不同的适配者适配到同一个目标
- 可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据“里氏代换原则”,适配者
的子类也可通过该适配器进行适配。
-
类适配器缺点
- 对于Java、C#等不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者;
- 在Java、C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性。
-
适用场景
- 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码。
- 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
4.2 代理模式
为什么要学习代理模式?因为这是SpringAOC的底层实现 【SpringAOP 和 SpringMVC】
代理模式的分类:
- 静态代理
- 动态代理
4.2.1 静态代理
角色分析:
- 抽象角色:一般会使用接口或抽象类来解决
- 真实角色:被代理的角色
- 代理角色:代理真实角色的角色,其代理真实角色后,我们一般会做一些附属操作
- 客户:访问代理角色的角色
代码步骤:
- 接口
// 租房的接口
public interface Rent {
public void rent();
}
- 真实角色
// 房东,真实角色,往外租房
public class Host implements Rent{
@Override
public void rent() {
System.out.println("房东要外租房子");
}
}
- 代理角色
public class Proxy implements Rent{
private Host host;
public Proxy() {
}
public Proxy(Host host) {
this.host = host;
}
@Override
public void rent() {
seeHouse();
host.rent();
fee();
contract();
}
// 看房
public void seeHouse() {
System.out.println("中介带你看房");
}
// 收中介费
public void fee() {
System.out.println("收中介费");
}
// 签租房合同
public void contract() {
System.out.println("签租房合同");
}
}
- 客户端访问代理角色
public class Client {
// 直接找房东租房
@Test
public void test01(){
Host host = new Host();
host.rent();
}
// 通过中介租房子
@Test
public void test02(){
// 房东要租房子
Host host = new Host();
// 中介帮房东租房子,代理角色(中介)还有一些附属操作
Proxy proxy = new Proxy(host);
proxy.seeHouse();
proxy.fee();
proxy.rent();
proxy.contract();
}
}
代理模式的好处:
- 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务
- 公共业务交给代理角色,实现了业务的分工
- 公共业务发生扩展的时候,方便集中管理
缺点:
- 一个真实角色就会产生一个代理角色,代码量翻倍,开发效率变低
4.2.2 加深理解
AOP
代码步骤:
- service层接口
public interface UserService {
// 增删改查
public void add();
public void delete();
public void update();
public void query();
}
- service层实现类
// 真实角色
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("增加用户");
}
@Override
public void delete() {
System.out.println("删除用户");
}
@Override
public void update() {
System.out.println("更新用户");
}
@Override
public void query() {
System.out.println("查询用户");
}
}
- service层实现类的代理
public class UserServiceProxy implements UserService {
private UserServiceImpl userService;
public void setUserService(UserServiceImpl userService) {
this.userService = userService;
}
@Override
public void add() {
log("add");
userService.add();
}
@Override
public void delete() {
log("delete");
userService.delete();
}
@Override
public void update() {
log("update");
userService.update();
}
@Override
public void query() {
log("query");
userService.query();
}
// 日志方法
public void log(String msg) {
System.out.println("[DEBUG:]使用了" + msg + "方法!");
}
/**
* 直接多出来一个类,相比于在原来代码上直接添加有什么必要?
* 改动原有的业务代码,在公司中是大忌
*/
}
- 客户端访问代理角色
public class Client {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
// userService.add();
UserServiceProxy proxy = new UserServiceProxy();
proxy.setUserService(userService);
proxy.add();
proxy.delete();
proxy.update();
proxy.query();
}
}
4.2.3 动态代理
-
动态代理和静态代理角色一样
-
动态代理的代理类是动态生成的,不是我们直接写好的
-
动态代理分为两大类:
- 基于接口的动态代理——JDK动态代理(学习这个)
- 基于类的动态代理——cglib
- java字节码实现——Javassist
需要了解两个类:Proxy, InvocationHandler
- 动态代理的好处:
- 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
- 公共也就就交给代理角色!实现了业务的分工!
- 公共业务发生扩展的时候,方便集中管理!
- 一个动态代理类代理的是一个接口,一般就是对应的一类业务
- 一个动态代理类可以代理多个类,只要是实现了同一个接口即可!
代码:
可以生成动态代理工具类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 使用这个类动态生成代理类
public class ProxyInvocationHandler implements InvocationHandler {
// 1.被代理的接口
private Object target;
// 2.set方法
public void setTarget(Object target) {
this.target = target;
}
// 3.生成代理类
public Object getProxy() {
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
}
// 4.处理代理实例,并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method.getName());
Object result = method.invoke(target, args);
return result;
}
// 增加日志功能
public void log(String msg) {
System.out.println("[DEBUG:]执行了" + msg + "方法");
}
/**
* Proxy:生成动态代理实例
* InvocationHandler:调用处理程序并返回一个结果
*/
}
Client测试:
import com.GrandNovice.Proxy.demo02.UserService;
import com.GrandNovice.Proxy.demo02.UserServiceImpl;
public class Client {
public static void main(String[] args) {
// 真实角色
UserService userService = new UserServiceImpl();
// 代理角色,不存在
ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler();
// 设置要代理的对象
proxyInvocationHandler.setTarget(userService);
// 动态生成代理类并强转
UserService proxy = (UserService) proxyInvocationHandler.getProxy();
proxy.add();
proxy.delete();
proxy.update();
proxy.query();
}
}
4.3 桥接模式
桥接模式 bridge
桥接模式是将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interfce)模式。
-
好处分析:
- 桥接模式偶尔类似于多继承方案,但是多继承方案违背了类的单一职责原则,复用性比较差,类的个数也非常多,桥接模式是比多继承方案更好的解决方法。极大的减少了子类的个数,从而降低管理和维护的成本
- 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。符合开闭原则,就像一座桥,可以把两个变化的维度连接起来!
-
劣势分析:
- 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
- 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。
-
最佳实践:
-
如果一个系统需要在构建的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
-
一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
-
虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
-
场景:
- Java语言通过Java虚拟机实现了平台的无关性。
- AWT中的Peer架构
- JDBC驱动程序也是桥接模式的应用之一。