08-开关与电灯:桥接模式

8.1开关与电灯

  本章背景故事是生活中常用的开关与电灯。

8.2 模式定义

  桥接模式(Bridge Pattern),也称为桥梁模式。在软件系统中,某些类型由于自身的逻辑,具有两个或多个维度的变化,如何应对这种“多维度的变化”?桥接模式使得软件系统能够轻松地沿着多个方向进行变化,而又不引入额外的复杂度。

  桥接模式的用意是“将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化”。这句话有三个关键词,即抽象化、实现化和脱耦。

  1)抽象化

  存在于多个实体中的共同的概念性联系,就是抽象化。作为一个过程,抽象化就是忽略一些信息,从而把不同的实体当作同样的实体对待。

  2)实现化

  抽象化给出的具体实现,就是实现化。

  3)脱耦

  所谓耦合,就是两个实体的行为的某种强关联。而将它们的强关联去掉,就是耦合的解脱,或称为脱耦。在这里,脱耦是指将抽象化和实现化之间的耦合解脱开来,或者说是将它们之间的强关联改为弱关联。

  将两个角色之间的继承关系改为聚合关系,就是将它们之间的强关联改为弱关联。因此,桥接模式中的脱耦,就是指在一个软件系统的抽象化和实现化之间使用组合/聚合关系而不是继承关系,从而使两者可以相对独立地变化。这就是桥接模式的用意所在。

8.3 一般化分析

  本章我们来介绍开关和电灯的例子,看看开关和电灯之间到底如何运作才是最理想的。

  在示例中出现了以下几个角色:

  1)开关

  2)电灯

  3)电线

  我们先来分析一般情况下,该如何实现。我们知道,开关控制电灯,开关接通电源,电灯亮;开关断开,电灯熄灭。这样看来,开关应该是一个基类,类中含有开灯、关灯、照明等抽象方法,还要有一个开灯照明的方法提供给外部应用调用;然后,电灯继承开关基类,具体实现开灯、关灯、照明等抽象方法,告诉外部应用现在使用的是哪盏灯。开关和电灯之间的关系如图所示:

  

8.4 一般化实现

8.4.1 建立抽象开关

  新建抽象开关基类“AbstractSwitch”。该类中存在三个抽象方法:打开开关、照明和关闭开关,还有一个提供给外部应用调用的开灯照明方法。

package com.demo.common;

/**
 * Created by Daniel on 2018/3/18.
 * 开关基类
 */
public abstract class AbstractSwitch {

    //打开开关
    public abstract void turnOn();

    //照明
    public abstract void light();

    //关闭开关
    public abstract void turnOff();

    //开灯照明
    public final void makeLight(){
        this.turnOn();
        this.light();
        this.turnOff();
    }

}

8.4.2 电灯实现

1. 白炽灯实现——IncandescentLight

package com.demo.common.sub;

import com.demo.common.AbstractSwitch;

/**
 * Created by Daniel on 2018/3/18.
 * 白炽灯
 */
public class IncandescentLight extends AbstractSwitch {

    //打开开关方法实现
    @Override
    public void turnOn() {
        System.out.println("白炽灯打开了……");
    }

    //照明方法实现
    @Override
    public void light() {
        System.out.println("白炽灯照明!");
    }

    //关闭开关方法实现
    @Override
    public void turnOff() {
        System.out.println("白炽灯关闭了……");
    }
}

2. 水晶灯实现——CrystalLight

package com.demo.common.sub;

import com.demo.common.AbstractSwitch;

/**
 * Created by Daniel on 2018/3/18.
 * 水晶灯
 */
public class CrystalLight extends AbstractSwitch {

    //打开开关方法实现
    @Override
    public void turnOn() {
        System.out.println("水晶灯打开了……");
    }

    //照明方法实现
    @Override
    public void light() {
        System.out.println("水晶灯照明!");
    }

    //关闭开关方法实现
    @Override
    public void turnOff() {
        System.out.println("水晶灯关闭了……");
    }

    /**
     * 使用遥控开关控制开灯
     * @param operColor 灯颜色
     */
    public final void makeRemoteLight(int operColor){
        //打开开关,接通电流
        this.turnOn();
        //照明
        this.light();
        String color = "";
        switch (operColor){
            case 1 :
                color = "暖色";
                break;
            case 2 :
                color = "蓝色";
                break;
            case 3 :
                color = "红色";
                break;
            default:
                color = "白色";
                break;
        }
        System.out.println("---现在是"+color+"!");

        //关闭开关,断开电流
        this.turnOff();
    }
}

8.4.3 客户端测试

import com.demo.common.AbstractSwitch;
import com.demo.common.sub.CrystalLight;
import com.demo.common.sub.IncandescentLight;

/**
 * Created by Daniel on 2018/3/18.
 * 客户端应用程序
 */
public class Client {

    public static void main(String[] args) {
        //白炽灯实例
        AbstractSwitch light = new IncandescentLight();

        //水晶灯实例
        CrystalLight light2 = new CrystalLight();

        //一般开关
        System.out.println("---一般开关---");
        light.makeLight();

        //遥控开关
        System.out.println("\n---遥控开关---");
        light2.makeRemoteLight(1);
    }

}

  注意:要想使用水晶灯的遥控功能就必须创建水晶灯实例对象,不能使用超类类型,因为超类中没有遥控的功能。

运行客户端程序,结果如下图所示:

 

8.4.4 对于扩展功能的思考

  已经实现了开灯、照明和关灯,是不是已经解决了所有的问题呢?答案是否定的。试想一下,我现在想用遥控开关遥控白炽灯,该怎么办呢?再创建一个子类?这样开销太大了。白炽灯已经存在,遥控开关也已经存在,为什么还要新建一个类呢?问题是,我们一开始的设计思路就是错误的。不应该使用继承,因为继承使开关和电灯关联得太强了,也就是一个开关控制一种灯,要想更换其他种类的电灯就必须配备相应的开关才行。更换电灯的同时还要更换开关,这是不应该出现的。

8.5 桥接模式分析方法

  我们重新分析一下,其实,开关和电灯是完全分开的两个部分,是彼此独立的工作,不应该因为对方的变换而受到影响。所以,要使用组合,不能使用继承。重新建立关系图如下:

  

8.6 开关与电灯的桥接模式实现

8.6.1 创建电灯接口

package com.demo.bridge.lights;

/**
 * Created by Daniel on 2018/3/18.
 * 电灯接口
 */
public interface ILight {

    //接通电流
    public void electricConnected();

    //照明
    public void light();

    //断开电流
    public void electricClosed();

}

8.6.2 创建开关

1. 创建一般开关——BaseSwitch

package com.demo.bridge.switchs;

import com.demo.bridge.lights.ILight;

/**
 * Created by Daniel on 2018/3/18.
 * 开关顶层类
 */
public class BaseSwitch {

    //使用组合,设置ILight为内部私有属性,此为桥梁
    protected ILight light;

    //构造方法将外部的light类型注入进来
    public BaseSwitch(ILight light){
        this.light = light;
    }

    /**
     * 开灯方法
     */
    public final void makeLight(){
        //打开开关,接通电流
        this.light.electricConnected();
        //照明
        this.light.light();
        //关闭开关,断开电流
        this.light.electricClosed();
    }

}

  注意:将ILight电灯接口作为开关基类的属性使用,这就是聚合的方式。而开关基类的开灯照明方法,是委托给ILight电灯接口完成的。这是一般开关实现,下面看看遥控开关。

2. 创建遥控开关——RemoteControlSwitch

package com.demo.bridge.switchs;

import com.demo.bridge.lights.ILight;

/**
 * Created by Daniel on 2018/3/18.
 * 遥控开关,继承BaseSwitch扩展功能
 */
public class RemoteControlSwitch extends BaseSwitch{

    //构造方法
    public RemoteControlSwitch(ILight light) {
        super(light);
    }

    /**
     * 使用遥控开关控制开灯
     * @param operColor 灯颜色
     */
    public final void makeRemoteLight(int operColor){
        //打开开关,接通电流
        this.light.electricConnected();
        //照明
        this.light.light();
        String color = "";
        switch (operColor){
            case 1:
                color = "暖色";
                break;
            case 2:
                color = "蓝色";
                break;
            case 3:
                color = "红色";
                break;
            default:
                color = "白色";
                break;
        }
        System.out.println("--现在是"+color+"!");

        //关闭开关,断开电流
        this.light.electricClosed();
    }
}

  注意:遥控开关的方法中,只做了关于电灯颜色的控制,电灯开关控制仍是委托给ILight电灯接口的。

8.6.3 电灯实现

1. 白炽灯实现——IncandescentLight

package com.demo.bridge.lights.impl;

import com.demo.bridge.lights.ILight;

/**
 * Created by Daniel on 2018/3/18.
 * 白炽灯实现
 */
public class IncandescentLight implements ILight{

    //接通电流
    public void electricConnected() {
        System.out.println("白炽灯被打开了……");
    }

    //照明
    public void light() {
        System.out.println("白炽灯照明!");
    }

    //断开电流
    public void electricClosed() {
        System.out.println("白炽灯被关闭了……");
    }
}

2. 水晶灯实现——CrystalLight

package com.demo.bridge.lights.impl;

import com.demo.bridge.lights.ILight;

/**
 * Created by Daniel on 2018/3/18.
 * 水晶灯实现
 */
public class CrystalLight implements ILight {

    //接通电流
    public void electricConnected() {
        System.out.println("水晶灯被打开了……");
    }

    //照明
    public void light() {
        System.out.println("水晶灯照明!");
    }

    //断开电流
    public void electricClosed() {
        System.out.println("水晶灯被关闭了……");
    }
}

8.6.4 客户端测试

1. 一般化测试

import com.demo.bridge.lights.ILight;
import com.demo.bridge.lights.impl.CrystalLight;
import com.demo.bridge.lights.impl.IncandescentLight;
import com.demo.bridge.switchs.BaseSwitch;
import com.demo.bridge.switchs.RemoteControlSwitch;

/**
 * Created by Daniel on 2018/3/18.
 * 客户端应用程序
 */
public class ClientForBridge {

    public static void main(String[] args) {
        //白炽灯实例
        ILight incandescentLight = new IncandescentLight();
        //水晶灯实例
        ILight crystalLight = new CrystalLight();

        //一般开关
        System.out.println("---一般开关---");
        BaseSwitch switch1 = new BaseSwitch(incandescentLight);
        switch1.makeLight();
        System.out.println("\n---遥控开关---");
        RemoteControlSwitch remoteControlSwitch = new RemoteControlSwitch(crystalLight);
        remoteControlSwitch.makeRemoteLight(1);

    }

}

运行结果:

2. 让遥控开关遥控白炽灯

  如果我们想要用遥控开关控制白炽灯,我们只需要将白炽灯传入遥控开关的构造方法中即可实现。

  将遥控开关部分:

RemoteControlSwitch remoteControlSwitch = new RemoteControlSwitch(crystalLight);

  修改为:

RemoteControlSwitch remoteControlSwitch = new RemoteControlSwitch(incandescentLight);

运行结果:

  正如运行结果,遥控开关可以控制白炽灯了!

8.7 使用场合

  1)不希望在抽象类和它的实现部分之间有一个固定的绑定关系;

  2)类的抽象及实现都应该可以通过生成子类的方法加以扩充;

  3)对一个抽象的实现部分的修改对客户不产生影响,即客户的代码不必重新编码。

posted @ 2018-03-18 15:15  shanquan  阅读(482)  评论(0编辑  收藏  举报