一、基本概念
桥接模式动机和定义
在软件系统中,有些类由于自身固有特性,是的会发展为多个维度,这种变化维度又称为变化原因。如一个跨平台日志系统,支持输出不同类型的文件(TextFile、VideoFile、ImageFile等),也支持多重系统系统(Linux、Windows、IOS等),也支持多种语言编译(Java、C语言、Python等)。对于这种多维度变化的系统,桥接模式提供一套完整的解决方案。
定义 :桥接模式将抽象部分与实现部分分离,使他们都可以独立变化,是一种对象结构型模式,又称柄体模式或接口模式。
模式结构
- Abstraction 抽象类:一般不是接口
- RefinedAbstraction 扩充抽象类,一般是接口
- Implementor 实现类接口
- ConcreteImplementor 具体实现类
模式分析
理解桥接模式,重点需要理解如何将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化。
- 抽象化:抽象化就是忽略一些信息,把不同的实体当作同样的实体对待。
- 实现化:针对抽象化给出的具体实现
- 脱耦:脱耦就是将抽象化和实现化之间的耦合解脱开,或者说是将它们之间的强关联改换成弱关联,将两个角色之间的继承关系改为关联关系。桥接模式中的所谓脱耦,就是指在一个软件系统的抽象化和实现化之间使用关联关系(组合或者聚合关系)而不是继承关系。
实现类接口:
public interface Implementor{
public void aperationImpl();
}
抽象类:
public abstract class Abstraction
{
protected Implementor impl;
public void setImpl(Implementor impl)
{
this.impl=impl;
}
public abstract void operation();
}
扩充抽象类
public class RefinedAbstraction extends Abstraction
{
public void operation()
{
//代码
impl.operationImpl();
//代码
}
}
二、实例与解析
实例一:模拟毛笔(二维)
要绘制9种不同的笔刷
毛笔:三种大小,三种颜色(3 + 3 = 6 )
扫描二维码关注公众号,回复:
11298277 查看本文章
蜡笔:九种不同的蜡笔(3 * 3 = 9 )
- 实现类接口 Color.java
package com.pen;
/**
* @author:xiaofa
* @date2020/4/2320:50
* @Description:笔类型接口
*/
public interface Color {
void bepaint(String penType, String name);
}
- 抽象类 Pen.java
package com.pen;
/**
* 里面有draw抽象方法
*/
public abstract class Pen {
protected Color color;
public void setColor(Color color) {
this.color = color;
}
public abstract void draw(String name);
}
- 扩充抽象类 SmallPen.java(BigPen、Middle这里省略)
package com.pen;
public class SmallPen extends Pen {
@Override
public void draw(String name) {
String penType = "小号毛笔绘制";
this.color.bepaint(penType,name);
}
}
- 具体实现类Blue.java(Red、Green类省略)
package com.pen;
/**
* @author:xiaofa
* @date2020/4/2320:51
* @Description:具体实现类:蓝色类
*/
public class Blue implements Color {
@Override
public void bepaint(String penType, String name) {
System.out.println(penType+"蓝色的"+name+".");
}
}
- 配置类 UtilXML.java
package com.pen;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
/**
* @Description:配置类
*/
public class XMLUtil {
//该方法用于从XML配置文件中提取品牌名称,并返回该品牌名称
public static Object getBean(String args){
try {
//创建文档对象
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dFactory.newDocumentBuilder();
Document doc;
doc = builder.parse(new File("src\\com\\pen\\config.xml"));
NodeList n1 = null;
Node classNode = null;
// classNode = n1.item(0).getFirstChild();
String cName = null;
// cName = classNode.getNodeValue();
n1 = doc.getElementsByTagName("className");
if (args.equals("color")){
//获取包含类名的文本节点
classNode = n1.item(0).getFirstChild();
}else if (args.equals("pen")){
classNode = n1.item(1).getFirstChild();
}
cName = classNode.getNodeValue();
Class c = Class.forName("com.pen."+cName);
Object obj = c.newInstance();
return obj;
}
catch (Exception e)
{
e.printStackTrace();
return null;
}
}
}
- 配置文件config.xml
<?xml version='1.0' encoding='UTF-8'?>
<config>
<className>Red</className>
<className>BigPen</className>
</config>
- 客户端:Client.java
package com.pen;
/**
* @author:xiaofa
* @date2020/4/2321:11
* @Description:
*/
public class Client {
public static void main(String[] args) {
Color color;
Pen pen;
color = (Color)XMLUtil.getBean("color");
pen =(Pen)XMLUtil.getBean("pen");
pen.setColor(color);
pen.draw("牛粪");
}
}
输出结果:
实例二:日志记录器(三维)
如一个跨平台日志系统,支持输出不同类型的文件(TextFile、VideoFile、ImageFile等),也支持多重系统系统(Linux、Windows、IOS等),也支持多种语言编译(Java、C语言、Python等)。
类图:
- System.java
package logger;
/**
* @author:xiaofa
* @date2020/4/2410:26
* @Description:操作系统
* 继承本类的有两个维度
* - 一个是继续向下发展维度
* - 另一个是自己的所属类型维度:windows、linux、IOS
*/
//抽象类:系统类
public abstract class System {
//定义Language属性,为了让下一维度的属性注入本类
Language language;
//下一维度属性注入本类
public void setLanguage(Language language) {
this.language = language;
}
//本类的属性:windows、linux
public abstract void type();
}
- Language.java
package logger;
/**
* @author:xiaofa
* @date2020/4/2411:38
* @Description:Language类
* 继承本类的有两个维度
* - 一个是继续向下发展维度
* - 另一个维度是本类的属性维度:Java、C_yuyan、Python
*
*/
public abstract class Language{
//定义OutPutl类的属性,为了让下一维度的属性注入本类
OutPut outPut;
//为下一维度的属性注入本类
public void setOutPut(OutPut outPut) {
this.outPut = outPut;
}
public abstract void languageType(String system);
}
- OutPut.java
package logger;
/**
* @author:xiaofa
* @date2020/4/2411:49
* @Description:抽象类:输出类
* 继承本类的有三个实现类:FileOutPut、ImageOutPut、ControllerOutPut
*/
public abstract class OutPut {
public abstract void output(String system,String language);
}
- Windows.java(Linux、IOS类省略)
package logger;
/**
* @author:xiaofa
* @date2020/4/2411:34
* @Description:Windows类:为操作系统的子类
* 为System的类型的子类
*/
public class Windows extends System {
@Override
public void type() {
String system = "Windows";
this.language.languageType(system);
}
}
- Java.java(C_yuyan、Python类省略)
package logger;
/**
* @Description:Java类:为Language的子类
*/
public class Java extends Language {
@Override
public void languageType(String system) {
String language = "JAVA";
this.outPut.output(system,language);
}
}
- XMLUtil.java
package logger;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
/**
* @Description:配置类
*/
public class XMLUtil {
//该方法用于从XML配置文件中提取品牌名称,并返回该品牌名称
public static Object getBean(String args){
try {
//创建文档对象
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dFactory.newDocumentBuilder();
Document doc;
doc = builder.parse(new File("src\\logger\\config.xml"));
NodeList n1 = null;
Node classNode = null;
// classNode = n1.item(0).getFirstChild();
String cName = null;
// cName = classNode.getNodeValue();
n1 = doc.getElementsByTagName("className");
if (args.equals("system")){
//获取包含类名的文本节点
classNode = n1.item(0).getFirstChild();
}else if (args.equals("language")){
classNode = n1.item(1).getFirstChild();
}else if (args.equals("outPut")){
classNode = n1.item(2).getFirstChild();
}
cName = classNode.getNodeValue();
Class c = Class.forName("logger."+cName);
Object obj = c.newInstance();
return obj;
}
catch (Exception e)
{
e.printStackTrace();
return null;
}
}
}
- config.xml
<?xml version='1.0' encoding='UTF-8'?>
<!--当前文件可输入日志要求-->
<!--可选项:-->
<!--操作系统System:Windows、Linux、IOS
编译语言:Java、Python、C_语言
输出方式:FileOutPut、ImageOutPut、ControllerOutPut
-->
<config>
<className>Linux</className>
<className>JAVA</className>
<className>FileOutPut</className>
</config>
输出结果:
三、小结
优缺点
优点:
- 分离抽象接口及实现部分;桥接模式使用“对象间的关联关系”解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化
- 很多情况下,可以却带多层继承方案,极大减少子类的个数。
- 提高系统的可扩展性
缺点:
- 增加系统的理解和设计难度,开发者一开始就针对抽象层进行设计与编程。
- 要求正确的辨识系统中多个独立变化的维度。
适用场景
- 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
- 抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
- 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
- 虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。
- 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
- 在软件开发中,适配器模式通常与桥接模式联合使用。
参考书籍《Java设计模式——刘伟第二版》《设计模式之禅》