一、适配器模式三种情况
适配器模式,顾名思义,就是把原本不兼容的接口,通过适配,使之兼容。
举个生活中简单的例子,以前的手机内存卡可以取出来,但是想和电脑之间传输音乐、视频等资料不能直接传输,需要通过USB读卡器,然后插入USB接口就可以传输了,这个USB读卡器就相当于适配器。
你经常使用的手机或电脑充电器,也属于适配器,它将220V的交流电转换为手机可用的直流电。下面,以手机充电器为例讲解适配器模式。
适配器模式一般分为三类:类适配器模式、对象适配器模式、接口适配器模式(缺省适配器模式)
1.类适配器模式
一般手机充电器输出的直流电压为5V,我们把交流电220V称为源,希望得到的直流电5V称为目标,而充电器即为适配器。
//源,交流电
public class AC {
public int outputAC(){
return 220;
}
}
//目标接口,直流电
public interface IDC {
public int outputDC();
}
//适配器
public class ClsAdapter extends AC implements IDC{
@Override
public int outputDC() {
return outputAC()/44; //直流电为交流电的电压值除以44
}
public static void main(String[] args) {
ClsAdapter adapter = new ClsAdapter();
System.out.println("交流电电压:" + adapter.outputAC());
System.out.println("直流电电压:" + adapter.outputDC());
}
}
/**
输出结果为:
交流电电压:220
直流电电压:5
*/
2.适配器模式
对象适配器,不是继承源类,而是依据关联关系,持有源类的对象,这也隐藏了源类的方法。在这里,适配器和源类的关系不是继承关系,而是组合关系。
public class ObjAdapter implements IDC {
//持有源类的对象
private AC ac;
public ObjAdapter(AC ac){
this.ac = ac;
}
public int outputAC(){
return ac.outputAC();
}
@Override
public int outputDC() {
return ac.outputAC()/44;
}
public static void main(String[] args) {
ObjAdapter adapter = new ObjAdapter(new AC());
System.out.println("交流电电压:" + adapter.outputAC());
System.out.println("直流电电压:" + adapter.outputDC());
}
}
//输出结果同上
3、接口适配器模式
设想,我现在的目标接口有多个方法,可以输出5V,12V,20V的电压。按照正常逻辑,设计一个适配器去实现这个接口,很显然需要实现所有的方法。但是,实际使用中,其实只需要使用其中一个方法就可以了,比如我mac电脑直流电压20V,只需要实现20V的方法就可以了。
因此,设计一个中间类去把目标接口的所有方法空实现,然后适配器类再去继承这个中间类,选择性重写我所需要的方法,岂不是更好。代码如下,
//目标接口,有多个方法
public interface IDCOutput {
public int output5V();
public int output12V();
public int output20V();
}
//中间类,空实现所有方法,这是一个抽象类
public abstract class DefaultAdapter implements IDCOutput {
@Override
public int output5V() {
return 0;
}
@Override
public int output12V() {
return 0;
}
@Override
public int output20V() {
return 0;
}
}
//我的mac电源适配器只需要实现20V的方法即可
public class MacAdatper extends DefaultAdapter {
private AC ac;
public MacAdatper(AC ac){
this.ac = ac;
}
@Override
public int output20V() {
return ac.outputAC()/11;
}
public static void main(String[] args) {
MacAdatper adatper = new MacAdatper(new AC());
System.out.println("mac电脑电压:" + adatper.output20V());
}
}
//输出结果:
//mac电脑电压:20
至于为什么中间类使用抽象类,相信你看过我介绍的软件六大设计原则,就明白了。它需要符合里氏替换原则(尽量基于抽象类和接口的继承)。
不太明白接口适配模式的童鞋,建议看一下JDK里边提供的一个键盘监听适配器KeyAdapter,它就是一个抽象类,去空实现了KeyListener接口的所有方法。你就会感受到这种模式的奥妙。
总结:
- 类适配器模式,继承源类,实现目标接口。
- 对象适配器模式,持有源类的对象,把继承关系改变为组合关系。
- 接口适配器模式,借助中间抽象类空实现目标接口所有方法,适配器选择性重写。
三种模式,各有优缺点,可根据实际情况选择使用。
二、桥接模式
抽象化(Abstraction)角色:定义抽象类,并包含一个对实现化对象的引用。
扩展抽象化(Refined Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用。
具体实现化(Concrete Implementor)角色:给出实现化角色接口的具体实现。
桥接模式示例
下面我们以发消息为例来进行说明,首先消息类型是一个维度,比如可以发邮件,发短信。然后消息可以有紧急消息,普通消息,这又是一个具体的维度。
1、首先新建一个消息接口类:
package com.zwx.design.pattern.bridge;
public interface IMessage {
void send(String content,String toUser);
}
2、新建两个实现类,分别是邮件消息和短信消息:
package com.zwx.design.pattern.bridge;
public class EmailMessage implements IMessage {
@Override
public void send(String content, String toUser) {
System.out.println(String.format("邮件消息->%s:%s",toUser,content));
}
}
package com.zwx.design.pattern.bridge;
public class SmsMessage implements IMessage {
@Override
public void send(String content, String toUser) {
System.out.println(String.format("SMS消息->%s:%s",toUser,content));
}
}
假如这时候我们需要按照普通消息和紧急消息来做一些不同的事情,那么这时候普通写法会怎么写,我想可能会是如下写法,直接改写消息类,在分别去实现紧急消息类和普通消息类:
public class SmsMessage extends CommonMsg implements IMessage {
但是Java是单继承,所以一次只能继承一个,要么就把普通消息和紧急消息设置为接口,要么就作为组合的形式,将紧急消息和普通消息分别作为属性存到对应的消息类型里面去。但是不论是哪种形式,都需要修改原先的SmsMessage 类。
所以这时候就需要使用桥接模式,将抽象(消息类型)与实现(消息紧急程度)进行分离。
3、新建一个抽象类,将IMessage集成进去:
package com.zwx.design.pattern.bridge;
public abstract class AbstractMessage {
private IMessage iMessage;
public AbstractMessage(IMessage iMessage) {
this.iMessage = iMessage;
}
public void sendMessage(String content,String toUser){
this.iMessage.send(content,toUser);
}
}
4、新建一个紧急程度为普通的消息类:
package com.zwx.design.pattern.bridge;
public class CommonMsg extends AbstractMessage {
public CommonMsg(IMessage iMessage) {
super(iMessage);
}
@Override
public void sendMessage(String content, String toUser) {
this.doSomething();
super.sendMessage(content, toUser);
}
private void doSomething() {
System.out.println("这只是一个普通消息");
}
}
5、新建一个紧急程度为紧急的消息类:
package com.zwx.design.pattern.bridge;
public class UrgentMessage extends AbstractMessage{
public UrgentMessage(IMessage iMessage) {
super(iMessage);
}
@Override
public void sendMessage(String content, String toUser) {
doSomething();
super.sendMessage(content, toUser);
}
private void doSomething() {
System.out.println("这是紧急消息,请优先发送");
}
}
这时候假如要新增其他紧急程度那直接再建一个类就好了,非常方便。
6、最后新建一个测试类来测试一下:
package com.zwx.design.pattern.bridge;
import java.io.IOException;
public class TestBridge {
public static void main(String[] args) throws IOException {
IMessage iMessage = new EmailMessage();
AbstractMessage abstractMessage = new UrgentMessage(iMessage);//紧急邮件消息
abstractMessage.sendMessage("您好","张三丰");
//再来一个普通邮件消息
System.out.println("------------分割线---------------");
abstractMessage = new CommonMsg(iMessage);
abstractMessage.sendMessage("您好","郭靖");
}
}
输出结果如下:
这是紧急消息,请优先发送
邮件消息->张三丰:您好
------------分割线---------------
这只是一个普通消息
邮件消息->郭靖:您好
桥接模式优缺点
优点:
1、分离了抽象部分及其实现部分两个维度,实现了代码的解耦,提高了系统的扩展性。
3、扩展功能时只需要新增类,无需修改源代码,符合开闭原则。
4、通过组合而不是继承来实现耦合,符合合成复用原则。
缺点:
1、增加了系统的理解难度和设计难度(这也算是大部分设计模式的共性)
2、需要正确识别系统中各个独立变化的维度
总结
本文主要介绍桥接模式的原理,并结合了示例对其进行了分析。桥接模式也是通过组合来实现的。我们在开发中大家都知道要解耦,解耦的实质就是减少对象之间的关联,而继承是一种强关联,因为一旦通过继承,那么子类就会拥有父类所有公开的方法和属性,有些可能并不是子类需要的,而组合就不一样,组合是一种弱关联,我只是持有一个对象,但是我持有对象所拥有的功能并不是我的,和我并没有很强烈的关系。所以实质上在很多场景我们都可以通过组合来解耦继承对象之间的强关联关系。
最后还是希望大家记住编程中的一条原则,那就是:多用组合,少用继承。