在現實生活中,某些類具有兩個或多個維度的變化,如圖形既可按形狀分,又可按顏色分。如何設計類似於 Photoshop 這樣的軟件,能畫不同形狀和不同顏色的圖形呢?如果用繼承方式,m 種形狀和 n 種顏色的圖形就有 m×n 種,不但對應的子類很多,而且擴展困難。
當然,這樣的例子還有很多,如不同顏色和字體的文字、不同品牌和功率的汽車、不同性別和職業的男女、支持不同平臺和不同文件格式的媒體播放器等。如果用橋接模式就能很好地解決這些問題。
橋接模式的定義與特點
橋接(Bridge)模式的定義如下:將抽象與實現分離,使它們可以獨立變化。它是用組合關係代替繼承關係來實現,從而降低了抽象和實現這兩個可變維度的耦合度。
通過上面的講解,我們能很好的感覺到橋接模式遵循了里氏替換原則和依賴倒置原則,最終實現了開閉原則,對修改關閉,對擴展開放。這裏將橋接模式的優缺點總結如下。
橋接(Bridge)模式的優點是:
- 抽象與實現分離,擴展能力強
- 符合開閉原則
- 符合合成複用原則
- 其實現細節對客戶透明
缺點是:由於聚合關係建立在抽象層,要求開發者針對抽象化進行設計與編程,能正確地識別出系統中兩個獨立變化的維度,這增加了系統的理解與設計難度。
橋接模式的結構與實現
可以將抽象化部分與實現化部分分開,取消二者的繼承關係,改用組合關係。
1. 模式的結構
橋接(Bridge)模式包含以下主要角色。
- 抽象化(Abstraction)角色:定義抽象類,幷包含一個對實現化對象的引用。
- 擴展抽象化(Refined Abstraction)角色:是抽象化角色的子類,實現父類中的業務方法,並通過組合關係調用實現化角色中的業務方法。
- 實現化(Implementor)角色:定義實現化角色的接口,供擴展抽象化角色調用。
- 具體實現化(Concrete Implementor)角色:給出實現化角色接口的具體實現。
其結構圖如圖 1 所示。
圖1 橋接模式的結構圖
2. 模式的實現
橋接模式的代碼如下:
package bridge;
public class BridgeTest {
public static void main(String[] args) {
Implementor imple = new ConcreteImplementorA();
Abstraction abs = new RefinedAbstraction(imple);
abs.Operation();
}
}
//實現化角色
interface Implementor {
public void OperationImpl();
}
//具體實現化角色
class ConcreteImplementorA implements Implementor {
public void OperationImpl() {
System.out.println("具體實現化(Concrete Implementor)角色被訪問");
}
}
//抽象化角色
abstract class Abstraction {
protected Implementor imple;
protected Abstraction(Implementor imple) {
this.imple = imple;
}
public abstract void Operation();
}
//擴展抽象化角色
class RefinedAbstraction extends Abstraction {
protected RefinedAbstraction(Implementor imple) {
super(imple);
}
public void Operation() {
System.out.println("擴展抽象化(Refined Abstraction)角色被訪問");
imple.OperationImpl();
}
}
運行結果:
擴展抽象化(Refined Abstraction)角色被訪問
具體實現化(Concrete Implementor)角色被訪問
橋接模式的應用實例
【例1】用橋接(Bridge)模式模擬女士皮包的選購。
分析:女士皮包有很多種,可以按用途分、按皮質分、按品牌分、按顏色分、按大小分等,存在多個維度的變化,所以採用橋接模式來實現女士皮包的選購比較合適。
本實例按用途分可選錢包(Wallet)和挎包(HandBag),按顏色分可選黃色(Yellow)和紅色(Red)。可以按兩個維度定義爲顏色類和包類。(點此下載本實例所要顯示的包的圖片)。
顏色類(Color)是一個維度,定義爲實現化角色,它有兩個具體實現化角色:黃色和紅色,通過 getColor() 方法可以選擇顏色;包類(Bag)是另一個維度,定義爲抽象化角色,它有兩個擴展抽象化角色:挎包和錢包,它包含了顏色類對象,通過 getName() 方法可以選擇相關顏色的挎包和錢包。
客戶類通過 ReadXML 類從 XML 配置文件中獲取包信息(點此下載 XML 配置文件),並把選到的產品通過窗體顯示出現,圖 2 所示是其結構圖。
圖2 女士皮包選購的結構圖
程序代碼如下:
package bridge;
import org.w3c.dom.NodeList;
import javax.swing.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.awt.*;
public class BagManage {
public static void main(String[] args) {
Color color;
Bag bag;
color = (Color) ReadXML.getObject("color");
bag = (Bag) ReadXML.getObject("bag");
bag.setColor(color);
String name = bag.getName();
show(name);
}
public static void show(String name) {
JFrame jf = new JFrame("橋接模式測試");
Container contentPane = jf.getContentPane();
JPanel p = new JPanel();
JLabel l = new JLabel(new ImageIcon("src/bridge/" + name + ".jpg"));
p.setLayout(new GridLayout(1, 1));
p.setBorder(BorderFactory.createTitledBorder("女士皮包"));
p.add(l);
contentPane.add(p, BorderLayout.CENTER);
jf.pack();
jf.setVisible(true);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
//實現化角色:顏色
interface Color {
String getColor();
}
//具體實現化角色:黃色
class Yellow implements Color {
public String getColor() {
return "yellow";
}
}
//具體實現化角色:紅色
class Red implements Color {
public String getColor() {
return "red";
}
}
//抽象化角色:包
abstract class Bag {
protected Color color;
public void setColor(Color color) {
this.color = color;
}
public abstract String getName();
}
//擴展抽象化角色:挎包
class HandBag extends Bag {
public String getName() {
return color.getColor() + "HandBag";
}
}
//擴展抽象化角色:錢包
class Wallet extends Bag {
public String getName() {
return color.getColor() + "Wallet";
}
}
package bridge;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import java.io.*;
class ReadXML {
public static Object getObject(String args) {
try {
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dFactory.newDocumentBuilder();
Document doc;
doc = builder.parse(new File("src/bridge/config.xml"));
NodeList nl = doc.getElementsByTagName("className");
Node classNode = null;
if (args.equals("color")) {
classNode = nl.item(0).getFirstChild();
} else if (args.equals("bag")) {
classNode = nl.item(1).getFirstChild();
}
String cName = "bridge." + classNode.getNodeValue();
Class<?> c = Class.forName(cName);
Object obj = c.newInstance();
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
運行結果:
圖3 女士皮包選購的運行結果1
如果將 XML 配置文件按如下修改:
<?xml version="1.0" encoding="UTF-8"?>
<config>
<className>Red</className>
<className>Wallet</className>
</config>
則程序的運行結果如圖 4 所示。
橋接模式的應用場景
當一個類內部具備兩種或多種變化維度時,使用橋接模式可以解耦這些變化的維度,使高層代碼架構穩定。
橋接模式通常適用於以下場景。
- 當一個類存在兩個獨立變化的維度,且這兩個維度都需要進行擴展時。
- 當一個系統不希望使用繼承或因爲多層次繼承導致系統類的個數急劇增加時。
- 當一個系統需要在構件的抽象化角色和具體化角色之間增加更多的靈活性時。
橋接模式的一個常見使用場景就是替換繼承。我們知道,繼承擁有很多優點,比如,抽象、封裝、多態等,父類封裝共性,子類實現特性。繼承可以很好的實現代碼複用(封裝)的功能,但這也是繼承的一大缺點。
因爲父類擁有的方法,子類也會繼承得到,無論子類需不需要,這說明繼承具備強侵入性(父類代碼侵入子類),同時會導致子類臃腫。因此,在設計模式中,有一個原則爲優先使用組合/聚合,而不是繼承。
很多時候,我們分不清該使用繼承還是組合/聚合或其他方式等,其實可以從現實語義進行思考。因爲軟件最終還是提供給現實生活中的人使用的,是服務於人類社會的,軟件是具備現實場景的。當我們從純代碼角度無法看清問題時,現實角度可能會提供更加開闊的思路。
橋接模式模式的擴展
在軟件開發中,有時橋接(Bridge)模式可與適配器模式聯合使用。當橋接(Bridge)模式的實現化角色的接口與現有類的接口不一致時,可以在二者中間定義一個適配器將二者連接起來,其具體結構圖如圖 5 所示。
進階閱讀
如果您想深入瞭解橋接模式,可猛擊閱讀以下文章。
備註:
程序的圖片資源:
擴充:
以上的例子是二維的,如果對一個事物是由多個維度決定,那麼第3個、第4個維度等可以用類似Abstraction、RefinedAbstraction的方式進行擴充。示例如下:
package bridge.example01;
/**************************************************************************************************
@copyright 2003-2022
@package bridge.example01
@file Implementor.java
@date 2022/3/6 12:02
@author qiao wei
@version 1.0
@brief 第一維度接口。
@history
**************************************************************************************************/
public interface Implementor {
void operationImpl();
}
package bridge.example01;
/**************************************************************************************************
@copyright 2003-2022
@package bridge.example01
@file ConcreteImplementorA.java
@date 2022/3/6 12:02
@author qiao wei
@version 1.0
@brief 第一維度實現類。
@history
**************************************************************************************************/
public class ConcreteImplementorA implements Implementor {
@Override
public void operationImpl() {
System.out.println("具體實現化(Concrete Implementor)角色被訪問");
}
}
package bridge.example01;
/**************************************************************************************************
@copyright 2003-2022
@package bridge.example01
@file AbstractA.java
@date 2022/3/6 11:53
@author qiao wei
@version 1.0
@brief 第二維度抽象類。
@history
**************************************************************************************************/
public abstract class AbstractA {
public abstract void operationA();
protected AbstractA(Implementor implementor) {
this.implementor = implementor;
}
protected Implementor implementor;
}
package bridge.example01;
/**************************************************************************************************
@copyright 2003-2022
@package bridge.example01
@file ConcreteA.java
@date 2022/3/6 12:00
@author qiao wei
@version 1.0
@brief 第二維度實現類。
@history
**************************************************************************************************/
public class ConcreteA extends AbstractA{
public ConcreteA(Implementor implementor) {
super(implementor);
}
@Override
public void operationA() {
System.out.println("具體實現化AbstractA角色被訪問");
implementor.operationImpl();
}
}
package bridge.example01;
/**************************************************************************************************
@copyright 2003-2022
@package bridge.example01
@file AbstractB.java
@date 2022/3/6 12:01
@author qiao wei
@version 1.0
@brief 第三維度抽象類。
@history
**************************************************************************************************/
public abstract class AbstractB {
public abstract void operationB();
protected AbstractB(AbstractA abstractA) {
this.abstractA = abstractA;
}
protected AbstractA abstractA;
}
package bridge.example01;
/**************************************************************************************************
@copyright 2003-2022
@package bridge.example01
@file ConcreteB.java
@date 2022/3/6 12:01
@author qiao wei
@version 1.0
@brief 第三維度實現類。
@history
**************************************************************************************************/
public class ConcreteB extends AbstractB {
public ConcreteB(AbstractA abstractA) {
super(abstractA);
}
@Override
public void operationB() {
System.out.println("具體實現化AbstractB角色被訪問");
abstractA.operationA();
}
}
package bridge.example01;
/**************************************************************************************************
@Copyright 2003-2022
@package bridge.example01
@date 2022/3/5 19:05
@author qiao wei
@version 1.0
@brief
@history
*************************************************************************************************/
public class Client {
public static void main(String[] args) {
AbstractB b = new ConcreteB(new ConcreteA(new ConcreteImplementorA()));
b.operationB();
}
}
package bridge.example01;
/**************************************************************************************************
@copyright 2003-2022
@package bridge.example01
@file ConcreteImplementorA.java
@date 2022/3/6 12:02
@author qiao wei
@version 1.0
@brief 第一維度實現類。
@history
**************************************************************************************************/
public class ConcreteImplementorA implements Implementor {
@Override
public void operationImpl() {
System.out.println("具體實現化(Concrete Implementor)角色被訪問");
}
}
運行結果:
具體實現化AbstractB角色被訪問
具體實現化AbstractA角色被訪問
具體實現化(Concrete Implementor)角色被訪問