设计模式7 适配器模式与外观模式

适配器模式

定义

适配器模式(Adapter Pattern)将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。 ——《HEAD First设计模式》

适配器模式分为类结构型模式对象结构型模式两种。

主要角色

目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。客户想使用的那个类或接口。可以理解为出国旅行使用的那个充电子插头。
适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。是实际上工作的那个类。可以理解为出国旅行,酒店提供的那个实际上的插口。
适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。可以理解为出国旅行携带的电源转换器。
下图是对象适配器模式:
在这里插入图片描述

下图是类适配器模式:
在这里插入图片描述

对象适配器模式

例子来源于《HEAD First设计模式》,实现火鸡和鸭子的适配器。
客户想使用鸭子对象,拿一些火鸡来冒充鸭子,需要实现一个鸭子到火鸡的适配器。鸭子对象和第一篇[设计模式1 策略模式]是一致的(https://blog.csdn.net/mxb1234567/article/details/120677314)
首先是鸭子接口:

public interface Duck{
	public void quack();
	public void fly();
}

具体的鸭子类:

public MallardDuck implements Duck{
	public void quack()
	{System.out.println("Quack");}
	public void fly()
	{System.out.println("I'm flying");};
}

然后是火鸡类:

public interface Turkey{
	public void gobble();
	public void fly();
}

火鸡飞行是一次飞行一小段距离:

public class WildTurkey implements Turkey{
	public void gobble()
	{System.out.println("Gobble");}
	public void fly()
	{System.out.println("Fly short distance");}
}

那么,适配器就可以是:

public class TurkeyAdapter implements Duck{
	Turkey turkey;
	public TurkeyAdapter(Turkey turkey)
	{
		this.turkey = turkey;
	}
	public void quack()
	{
		turkey.gobble();
	}
	public void fly()
	{
		for(int i = 0;i < 5;i++)
			turkey.fly();
	}
} 

这样在使用火鸡伪装的鸭子时,可以这样:

public class Test
{
	public static void main(String[] args)
	{
		WildTurkey turkey = new WildTurkey();
		Duck turkeyAdapter = new TurkeyAdapter(turkey);
		testDuck(turkeyAdapter);
	}
	static void testDuck(Duck duck)
	{
		duck.quack();
		duck.fly();
	}
}

因此可以看出,客户通过目标接口(Duck),调用适配器(TurkeyAdapter)的方法(quack()、fly())对适配器发出请求,适配器把请求转换为被适配者(Turkey)的一个或多个调用接口。这个过程中对用户是透明的,用户是不知道适配器在起作用的。
这种适配器不光可以适配某个类,还可以适配任何这个类的子类。

类适配器

类适配器模式可采用多重继承方式实现。Java中不允许多重继承,可以继承Target接口和Adaptee类,来实现类适配器。
Target接口,即目标接口(Duck):

public interface Duck{
	public void quack();
	public void fly();
}

再来实现一个Adaptee,即上个例子中的Turkey:

public interface Turkey{
	public void gobble();
	public void fly();
}
public class WildTurkey implements Turkey{
	public void gobble()
	{System.out.println("Gobble");}
	public void fly()
	{System.out.println("Fly short distance");}
}

实现一个类的适配器TurkeyAdapter可以这样:

public class TurkeyAdapter extends WildTurkey implements Duck{
	public TurkeyAdapter(){}
	public void quack()
	{
		super.gobble();
	}
	public void fly()
	{
		for(int i = 0;i < 5;i++)
			super.fly();
	}
} 

类的适配器和对象适配器对比

可以看出,对象适配器是持有Adaptee对象的,是组合关系,是动态的方式;类的适配器是继承adaptee,并且实现target方法,是静态的方式。

这样做可以使得对象适配器可以重定义实现行为,可以override掉父类的功能函数;但是对象适配器重定义适配的行为比较困难,但是添加行为较方便。

优缺点

优点

  1. 可以让任何两个没有关联的类一起运行。
  2. 提高了类的复用。 不需要修改原有代码而重用现有的适配者类。
  3. 增加了类的透明度。 客户端通过适配器可以透明地调用目标接口。
  4. 在很多业务场景中符合开闭原则。

缺点

增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱。例如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现。

已有使用场景

  • JAVA 中的 jdbc
  • AVA JDK 1.1 提供了 Enumeration 接口,而在 1.2 中提供了 Iterator 接口,想要使用 1.2 的 JDK,则要将以前系统的 Enumeration 接口转化为 Iterator 接口

适配器模式和装饰者模式对比

装饰者模式和适配器模式都是起到包装一个类的作用,都有一个别名叫做包装模式(Wrapper Pattern)。但是使用它们的目的很不一一样。
适配器模式的是要将一个接口转变成另一个接口,它的目的是通过改变接口调用方式来达到重复使用的目的。
装饰器模式是不改变原有的接口,但是增强原有对象的功能,当涉及到装饰者就意味着新的行为或责任被加入了设计中。

外观模式

定义

外观模式(Facade Pattern)提供了一个统一的接口,用来访问子系统中的一群接口。外观模式定义了一个高层接口,让子系统更容易使用。 ——《HEAD First设计模式》

主要角色

  • 外观(Facade)角色:为多个子系统对外提供一个共同的接口。
  • 子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。
  • 客户(Client)角色:通过一个外观角色访问各个子系统的功能。
    在这里插入图片描述
    图源:外观模式(Facade模式)详解
    简单来说,可以理解为外观模式可以实现把很多接口进行打包,用户点一下玩一年,一个接口调用其他接口干很多事情。外观模式的特点主要是简化接口,以及减少客户端对外观组件的耦合。

例子

例子来源于《HEAD First设计模式》,实现家庭影院的外观模式。
家庭影院中,有个爆米花机,CD播放器,屏幕,用户使用家庭影院需要一个个的去打开关闭。这时候采用外观模式,一个函数让用户点一下玩一年。
首先是爆米花机:

class PopcornPopper{
    public void on(){
        System.out.println("正在打开爆米花机。。");
    }
    public void off(){
        System.out.println("正在关闭爆米花机。。");
    }
    public  void pop(){
        System.out.println("正在蹦爆米花。。");
    }
}

然后是屏幕:

class Screen{
    public void up(){
        System.out.println("正在生起屏幕。。");
    }
    public void down(){
        System.out.println("正在放下屏幕。。");
    }
}

然后是CD播放器:

class CDPlayer{
    public void on(){
        System.out.println("正在打开CD");
    }
    public void off(){
        System.out.println("正在关闭CD");
    }
    public void eject(){
        System.out.println("弹出CD播放器!");
    }
    public void pause(){    
    }
    public void play(){
    }
    public String toString(){
        return "hello panda";
    }
}

外观模式类:

class HomeTheaterFacade{
    CDPlayer cd;
    Screen screen;
    PopcornPopper pop;
    //构造的时候拿到这些对象
    public HomeTheaterFacade(CDPlayer cd ,Screen screen,PopcornPopper pop){
        this.cd = cd;
        this.screen = screen;
        this.pop = pop;
    }
    //看电影  放一个方法里来执行一系列动作
    public void watchMovie(String movie){
        System.out.println("get ready to watch a movie..");
        pop.on();//首先打开爆米花机
        pop.pop();//然后蹦爆米花
        screen.down();//投影仪放下来
        cd.on();
        cd.play();
    }
    //电影结束
    public void endMovie(String movie){
        System.out.println("shutting movie theater down..");
        pop.off();
        screen.up();
        cd.off();
    }
}

对于客户而言,只需要watchMovie和endMovie:

public class FacadePattern {
    public static void main(String args[]){
        CDPlayer cd = new CDPlayer();
        Screen screen = new Screen();
        PopcornPopper pop = new PopcornPopper();                  
        HomeTheaterFacade facade = new HomeTheaterFacade(cd,screen,pop);
        facade.watchMovie("movie");
        facade.endMovie("movie");
    }
 
}

最少知识原则

每个类对象都要执行一些方法,如果直接new这些类创建对象去调方法会与这些类产生耦合。单独再写一个外观类,构造初始化时拿到这些类对象,在一个方法里去调这些类对象的方法,这样对客户来说只和一个类打交道,与子系统的一堆类解耦了。这个原则被称为:最少知识原则(Least Knowledge)
最少知识原则的指导思想:
就任何对象而言,在该对象的方法内,我们只应该调用属于以下范围的方法:

  1. 该对象本身
  2. 被当做方法的参数而传过来的对象
  3. 该方法所创建或实例化的任何对象
  4. 对象的任何组件

如果调用从另一个调用中返回的内容,即如下所示想另外一个对象的子部分发送请求,需要增加我们知道的对象。

//不采用这个原则
public float getTemp()
{
	Thermometer t = station.getThermometer();
	return t.getTemperture();
}
//采用这个原则
public float getTemp()
{
	return station.getTemperture();
}

这样可以减少我们所依赖的类的数目,在station里面加一个getTemperture方法。
这个法则也被称为德墨忒尔法则(Law of Demeter)。这个原则虽然减少了对象间的依赖减少维护成本,但是采用这个原则也会导致更多包装类被制造,处理和其他组件的沟通成本上升,效率降低。

参考

外观模式(Facade模式)详解.
Carson带你学设计模式:适配器模式(Adapter Pattern)
适配器模式
适配器模式(Adapter模式)详解
学习编程|Head First设计模式:外观模式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值