Java中的设计模式(十一):桥接模式

311 阅读5分钟

image.png

一、基本概念

桥接模式 主要解决在有多种可能会变化的情况下,用继承会造成类爆炸问题,扩展起来不灵活的问题。

桥接模式 (Bridge Pattern)是 结构型设计模式 的一种,桥接模式的定义是:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。

二、外观模式结构和实现

我们有一个作为桥接实现的 DrawAPI 接口和实现了 DrawAPI 接口的实体类 RedCircleGreenCircleShape 是一个抽象类,将使用 DrawAPI 的对象。BridgePatternDemo 类使用 Shape 类来画出不同颜色的圆。

image.png

2.1 创建桥接实现接口

## DrawAPI.java

public interface DrawAPI {
    public void drawCircle(int radius, int x, int y);
}

2.2 创建实现了 DrawAPI 接口的实体桥接实现类

## RedCircle.java

public class RedCircle implements DrawAPI {
    @Override 
    public void drawCircle(int radius, int x, int y) { 
        System.out.println("Drawing Circle[ color: red, radius: " + radius +", x: " +x+", "+ y +"]"); 
    } 
}
## GreenCircle.java

public class GreenCircle implements DrawAPI {
    @Override
    public void drawCircle(int radius, int x, int y) { 
        System.out.println("Drawing Circle[ color: green, radius: " + radius +", x: " +x+", "+ y +"]");
    }
}

2.3 使用 DrawAPI 接口创建抽象类 Shape

## Shape.java

public abstract class Shape { 
    protected DrawAPI drawAPI;
    protected Shape(DrawAPI drawAPI){ 
        this.drawAPI = drawAPI; 
    } 
    public abstract void draw();
}

2.4 创建实现了 Shape 抽象类的实体类

## Circle.java

public class Circle extends Shape {
    private int x, y, radius;
    
    public Circle(int x, int y, int radius, DrawAPI drawAPI) { 
        super(drawAPI); this.x = x; this.y = y; this.radius = radius;
    } 
    
    public void draw() {
        drawAPI.drawCircle(radius,x,y); 
    }
}

2.5 使用 Shape 和 DrawAPI 类画出不同颜色的圆

## BridgePatternDemo.java

public class BridgePatternDemo { 
    public static void main(String[] args) {
        Shape redCircle = new Circle(100,100, 10, new RedCircle());
        Shape greenCircle = new Circle(100,100, 10, new GreenCircle()); 
       
        redCircle.draw();
        greenCircle.draw();
    } 
}

2.6 执行程序,输出结果

Drawing Circle[ color: red, radius: 10, x: 100, 100]
Drawing Circle[  color: green, radius: 10, x: 100, 100]

三、桥接模式的应用实例

【例1】用桥接(Bridge)模式模拟女士皮包的选购。

分析:女士皮包有很多种,可以按用途分、按皮质分、按品牌分、按颜色分、按大小分等,存在多个维度的变化,所以采用桥接模式来实现女士皮包的选购比较合适。

本实例按用途分可选钱包(Wallet)和挎包(HandBag),按颜色分可选黄色(Yellow)和红色(Red)。可以按两个维度定义为颜色类和包类。(点此下载本实例所要显示的包的图片)。

颜色类(Color)是一个维度,定义为实现化角色,它有两个具体实现化角色:黄色和红色,通过 getColor() 方法可以选择颜色;包类(Bag)是另一个维度,定义为抽象化角色,它有两个扩展抽象化角色:挎包和钱包,它包含了颜色类对象,通过 getName() 方法可以选择相关颜色的挎包和钱包。

客户类通过 ReadXML 类从 XML 配置文件中获取包信息(点此下载 XML 配置文件),并把选到的产品通过窗体显示出现,下图所示是其结构图:

image.png

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;
        }
    }
}

程序的运行结果如下图所示:

image.png

如果将 XML 配置文件按如下修改:

<?xml version="1.0" encoding="UTF-8"?>
<config>
    <className>Red</className>
    <className>Wallet</className>
</config>

则程序的运行结果如下图所示:

image.png

四、桥接模式的应用场景

当一个类内部具备两种或多种变化维度时,使用桥接模式可以解耦这些变化的维度,使高层代码架构稳定。

桥接模式通常适用于以下场景。

  • 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
  • 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
  • 当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。

桥接模式的一个常见使用场景就是替换继承。我们知道,继承拥有很多优点,比如,抽象、封装、多态等,父类封装共性,子类实现特性。继承可以很好的实现代码复用(封装)的功能,但这也是继承的一大缺点。

因为父类拥有的方法,子类也会继承得到,无论子类需不需要,这说明继承具备强侵入性(父类代码侵入子类),同时会导致子类臃肿。因此,在设计模式中,有一个原则为优先使用组合/聚合,而不是继承。

很多时候,我们分不清该使用继承还是组合/聚合或其他方式等,其实可以从现实语义进行思考。因为软件最终还是提供给现实生活中的人使用的,是服务于人类社会的,软件是具备现实场景的。当我们从纯代码角度无法看清问题时,现实角度可能会提供更加开阔的思路。

五、桥接模式模式的扩展

在软件开发中,有时桥接(Bridge)模式可与适配器模式联合使用。当桥接(Bridge)模式的实现化角色的接口与现有类的接口不一致时,可以在二者中间定义一个适配器将二者连接起来,其具体结构图如下图所示:

image.png

本文参考和转载自: www.runoob.com/design-patt… c.biancheng.net/view/1364.h…