前言

这一章主要讲2个模式,一个是,适配器模式(负责将一个类的接口适配成用户所期待的),另外一个是外观模式(为子系统提供一个共同的对外接口),看完的第一反应是,为什么要把它们两放在同一章,难道它们有什么不可告人的秘密?

难道是因为他们俩都很简单吗?不会不会,毕竟是大名鼎鼎的headfirst,怎么可能这么草率,这我是万万不相信的!

细想了一下,我和工作的点点滴滴,我发现,一般到项目的后期,好像都比较容易用上这两个东西...

当然,项目的后期并不是说一个项目自己从头发开到尾的项目,而是在它生命周期的后半段,比如适配器,用来适配老的接口,比如外观模式,用来隐藏各个子系统,各个模块的协作细节。

不过外观模式却不一定都是在后期才发力的:

1,前期如果系统比较复杂,在系统规划的时候,就会有意识的对系统分层,为上层模块提供一些高级的api。

2,在系统的中期呢,开发过程中,发现子系统越来越复杂,也可以提供类似的操作。

3,在系统后期,模块越来越多,功能越来越复杂,还有历史原因,外观模式就更加有用了,毕竟,有一个简单易用的API,比手动调用各个系统的逻辑那简直是不要太舒服!

为什么后期不重构?而是要做这些修修补补的工作呢?

举个例子,房子上有棵树,你觉得这棵树很碍事,就把树给干掉了,因为你以为,是房子上面长的,结果呢?特么是树把房子吊着的!类似的坑实在是太多了,所以,重构需谨慎,且构且珍惜。

当然不是说重构不好,而是要综合考量各方面的因素,而且,重构也用得上这些啊,毕竟,重构不是重写...(诶,重写好像也要用)

适配器模式

先说说它是干嘛的,用通俗一点的话来讲就是,VGA转DVI,2线插头转3线插头...废话不多说,上个图就知道了

什么?大家很想看个例子?那么我就来一个例子吧,就举一个小火鸡变成小鸭子的故事吧

先看看鸭子接口(对应Target)

/**
* 鸭子接口
*/
public interface Duck {
/**
* 鸭叫
*/
void quack(); /**
* 飞行
*/
void fly();
}

然后看一下火鸡的接口和实现类(对应Adaptee)

/**
* 火鸡接口
*/
public interface Turkey {
/**
* 火鸡叫
*/
void gobble(); /**
* 飞行
*/
void fly();
} /**
* 野火鸡
*/
public class WildTurkey implements Turkey {
public void gobble() {
System.out.println("咯咯");
} public void fly() {
System.out.println("我在飞,虽然我飞的很近");
}
}

首先可以看出,它们的之间有一些共同之处,都有叫声,都可以飞行,这个也是适配的前提,有共同点!

如果没有共同点,是不是去隔壁的其他设计模式看看?

OK,接下来开始适配操作

火鸡适配器(Adapter)

/**
* 火鸡适配器
*/
public class TurkeyAdapter implements Duck {
Turkey turkey;//持有一个火鸡对象 public TurkeyAdapter(Turkey turkey) {
this.turkey = turkey;
} /**
* 鸭叫
*/
public void quack() {
turkey.gobble();
} /**
* 飞行
*/
public void fly() {
//适配的时候,这里模拟飞行5次
for(int i= 0; i < 5; i++) {
turkey.fly();
}
}
}

适配器的逻辑也很简单

首先,实现Duck接口,要让Client能够调用,那么首先得长得和别人一样啊

其次,持有一个真正的处理对象,然后再根据Duck接口来进行适配,比如这里,quack接口,就直接调用Turkey#gobble(),而fly()可能是因为某种神秘力量,需要火鸡飞行的距离和鸭子一样远,所以需要手动去适配,在这里添加了适配的代码

最后,适配器的作用就是把一个类转换成另外一个类,转换的时候可能需要一些逻辑上的处理,让它能符合用户的期待

测试下是不是成功的伪装了呢

public class DuckClient {
public static void main(String[] args) { //初始化一只火鸡
WildTurkey turkey = new WildTurkey();
//伪装成一只鸭子
Duck duck = new TurkeyAdapter(turkey); System.out.println("鸣叫:");
duck.quack(); System.out.println("------------------"); System.out.println("飞行:");
duck.fly();
}
}

结果:

适配器模式模式确实很简单,但是确实也很实用,优点很明显,可以将目标类和适配者解耦,不需要改动原来的结构(新增了Adapter来封装了适配的逻辑),但是建议不要在系统设计阶段就盲目的使用它,增加系统的复杂度

外观模式

这个就更简单了,例子我可以举一堆,比如说,酒店前台的小姐姐,餐厅前台的小姐姐,医院的小姐姐...

核心思想:为子系统们提供一套通用的对外接口(高级API)

为什么会有这样的需求呢?

各个子系统在设计过程中,或者在实际使用的过程中会发现,有一些通用的步骤,对于更加高的调用层来说,它们其实不需要知道底层是通过哪些步骤来实现的,更多的是,以一个统一的接口来调用。

比如,在想在家里搞一个家庭影院,需要以下步骤:

1,灯光不能太亮,亮度需要调低到10

2,需要打开投影机,并且要调整到宽屏模式

3,音响需要调整成环绕立体音,音量设置成5

4,打开DVD开始播放

代码如下:

灯光:

/**
* 影院灯光
*/
public class TheaterLights {
String description; public TheaterLights(String description) {
this.description = description;
} public void on() {
System.out.println(description + " 打开");
} public void off() {
System.out.println(description + " 关闭");
} public void dim(int level) {
System.out.println(description + " 亮度调节到:" + level + "%");
} public String toString() {
return description;
}
}

投影仪:

/**
* 投影仪屏幕
*/
public class Screen {
String description; public Screen(String description) {
this.description = description;
} public void up() {
System.out.println(description + " 上升");
} public void down() {
System.out.println(description + " 下降");
} public String toString() {
return description;
}
}
/**
* 投影仪
*/
public class Projector {
String description;
DvdPlayer dvdPlayer; public Projector(String description, DvdPlayer dvdPlayer) {
this.description = description;
this.dvdPlayer = dvdPlayer;
} public void on() {
System.out.println(description + " 打开");
} public void off() {
System.out.println(description + " 关闭");
} public void wideScreenMode() {
System.out.println(description + " 调整到宽屏模式");
} public void tvMode() {
System.out.println(description + " 调整到tv模式");
} public String toString() {
return description;
}
}

音响:

/**
* 音响
*/
public class Amplifier {
String description; public Amplifier(String description) {
this.description = description;
} public void on() {
System.out.println(description + " 打开");
} public void off() {
System.out.println(description + " 关闭");
} //立体声
public void setStereoSound() {
System.out.println(description + " 立体声模式");
} //环绕声
public void setSurroundSound() {
System.out.println(description + " 环绕声模式");
} public void setVolume(int level) {
System.out.println(description + " 调整音量到: " + level);
} public String toString() {
return description;
}
}

DVD播放器:

/**
* DVD播放器
*/
public class DvdPlayer {
String description;
int currentTrack;
Amplifier amplifier;
String movie; public DvdPlayer(String description, Amplifier amplifier) {
this.description = description;
this.amplifier = amplifier;
} public void on() {
System.out.println(description + " 播放");
} public void off() {
System.out.println(description + " 关闭");
} public void play(String movie) {
this.movie = movie;
currentTrack = 0;
System.out.println(description + " 播放 \"" + movie + "\"");
} public String toString() {
return description;
}
}

不重要的代码就折叠了,免得难得看,不使用外观模式,需要调用一堆代码:

/**
* 不使用外观模式
*/
public class Client {
public static void main(String[] args) {
Amplifier amp = new Amplifier("Top-O-Line 扬声器");
DvdPlayer dvd = new DvdPlayer("Top-O-Line DVD播放器", amp);
Projector projector = new Projector("Top-O-Line 投影仪", dvd);
TheaterLights lights = new TheaterLights("客厅灯");
Screen screen = new Screen("投影仪银幕"); System.out.println("准备看电影...");
lights.dim(10);
screen.down();
projector.on();
projector.wideScreenMode();
amp.on();
amp.setSurroundSound();
amp.setVolume(5);
dvd.on();
dvd.play("夺宝奇兵");
}
}

使用外观模式,一行解决:

/**
* 使用外观模式后的测试类
*/
public class FacadeClient { private static HomeTheaterFacade HOME_THEATER;
static{
Amplifier amp = new Amplifier("Top-O-Line 扬声器");
DvdPlayer dvd = new DvdPlayer("Top-O-Line DVD播放器", amp);
Projector projector = new Projector("Top-O-Line 投影仪", dvd);
TheaterLights lights = new TheaterLights("客厅灯");
Screen screen = new Screen("投影仪银幕"); HOME_THEATER = new HomeTheaterFacade(amp, dvd, projector, screen, lights);
} public static void main(String[] args) {
//看电影
HOME_THEATER.watchMovie("夺宝奇兵");
}
}

我擦?咋还是这么多行?

static块里面的代码是初始化代码,一般使用spring,都是依赖注入的东西,其实调用就一行:

HOME_THEATER.watchMovie("夺宝奇兵");

一键解决就是爽啊,如果说对比的话,相当于,去网上买了个床,小哥送来的是一堆零件让你组装,和小哥送来就是一张组装好了的床啊!

但是能够一键解决的,更多的是一些通用的操作,比如说,例子中,灯光不能太亮,你想把它调到5,不想用默认的10,,那么可能就只能自己写一遍外观模式封装的逻辑了。

那么这里就有个问题了,能不能重载方法,让它支持可以自定义灯光亮度这个参数呢?对于这个我只能说,要看业务需求了,如果100个人里面只有1个人用,那么对于系统产生的复杂度可能比 产生的价值高,反过来,可能就需要去实现。

但是,如果这种需求越来越多,系统变得越来越复杂,那外观模式还是一个简单可爱的小姐姐吗?如果不实现,就无法达到隐藏子系统复杂度的痛点,如果实现,就会产生新的API调用的复杂度,我终于知道为啥我特么还在学习设计模式了...

说了这么多,说说它的优缺点吧

优点:

1,对客户屏蔽了子系统组件使用起来门槛更低。

2,实现了子系统与客户之间的松耦合关系。

3,虽然提供了访问子系统的统一入口,但是并不影响用户直接使用子系统类。

缺点:

1,通过外观类访问子系统时,减少了可变性和灵活性。

2,在新的子系统加入,或者子系统接口变更时,可能需要修改外观类或客户端的源代码,违背了“开闭原则”。

headfirst设计模式(8)—适配器模式与外观模式的更多相关文章

  1. Head First 设计模式之适配器模式与外观模式

    Head First设计模式之适配器模式与外观模式 前言: 之前讲过装饰者模式,将对象包装起来并赋予新的职责,这一章我们也会将对象进行包装,只不过是让它们看起来不像自己而像是别的东西.这样就可以在设计 ...

  2. Headfirst设计模式的C++实现——外观模式(Facade)

    light.h #ifndef _LIGHT_H_ #define _LIGHT_H_ #include <iostream> class LIGHT { public: void dim ...

  3. 适配器模式和外观模式(head first设计模式——6)

    为什么要把适配器模式和外观模式放在同一篇文章中,主要是其相对前面的几个模式来讲会简单些并且具有相似之处.下面就分别通过例子来看理解一下两种模式,然后再进行对其进行比较. 一.适配器模式 1.1适配器模 ...

  4. 设计模式(九)外观模式Facade(结构型)

    设计模式(九)外观模式Facade(结构型) 1. 概述 外观模式,我们通过外观的包装,使应用程序只能看到外观对象,而不会看到具体的细节对象,这样无疑会降低应用程序的复杂度,并且提高了程序的可维护性. ...

  5. Java设计模式(11)外观模式(Facade模式)

    外观模式(Facade)的定义:为子系统中的一组接口提供一个一致的界面. Facade一个典型应用就是数据库JDBC的应用,如下例对数据库的操作: public class DBCompare { C ...

  6. 设计模式(二)-- 外观模式(Facade)

    设计模式(二) 外观模式(Facade) 为了解决子系统外部的客户端在使用子系统的时候,既能简单地使用这些子系统内部的模块功能,而又不用客户端去与子系统内部的多个模块交互的问题. 为子系统中的一组接口 ...

  7. 【HeadFirst设计模式】7.适配器模式与外观模式

    今晚学习完第七章,顺便做一下知识备忘. 适配器模模式: 定义:将一个类的接口,转换成客户期望的另一个接口.适配器让原本接口不兼容的类可以合作无间. 对象适配器: 类适配器: 外观模式: 提供了一个统一 ...

  8. 《Head First 设计模式》之适配器模式与外观模式

    适配器模式(Adapter) 适配器(adapter-pattern):将一个类的接口,转换成客户期望的另一个接口.适配器让原来接口不兼容的类可以合作无间.两种形式: 对象适配器(组合) 类适配器(多 ...

  9. 设计模式(十一)外观模式(Facade Pattern)

    一.引言 在软件开发过程中,客户端程序经常会与复杂系统的内部子系统进行耦合,从而导致客户端程序随着子系统的变化而变化,然而为了将复杂系统的内部子系统与客户端之间的依赖解耦,从而就有了外观模式,也称作 ...

随机推荐

  1. Bagging与方差

    在集成学习中,通常认为Bagging的主要作用是降低方差,而Boosting的主要作用是降低偏差.Boosting能降低偏差很好理解,因为其原理就是将多个弱学习器组合成强学习器.但Bagging为什么 ...

  2. 设计模式的征途—17.模板方法(Template Method)模式

    在现实生活中,很多事情都需要经过几个步骤才能完成,例如请客吃饭,无论吃什么,一般都包含:点单.吃东西.买单等几个步骤,通常情况下这几个步骤的次序是:点单=>吃东西=>买单.在这3个步骤中, ...

  3. 洛谷 P2725 解题报告

    P2725 邮票 Stamps 题目背景 给一组 N 枚邮票的面值集合(如,{1 分,3 分})和一个上限 K -- 表示信封上能够贴 K 张邮票.计算从 1 到 M 的最大连续可贴出的邮资. 题目描 ...

  4. Hibernate Session总结

    现在我们可以在 IDEA 下新建一个 Hibernate 项目,接着上次内容这次主要总结一下 Hibernate 的 Session,及其核心方法. Session 概述 Session 接口是 Hi ...

  5. Python_FTP通讯软件

    ftpServer.py import socket import threading import os import struct #用户账号.密码.主目录 #也可以把这些信息存放到数据库中 us ...

  6. Struts标签库详解【1】

    struts2标签详解 要在jsp中使用Struts2的标志,先要指明标志的引入.通过jsp的代码的顶部加入以下的代码: <%@taglib prefix="s" uri=& ...

  7. java并发之DelayQueue实际运用示例

    在学习Java 多线程并发开发过程中,了解到DelayQueue类的主要作用:是一个无界的BlockingQueue,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走. ...

  8. 【java错误】Non-terminating decimal expansion; no exact representable decimal result

    问题描述 意思是“无法结束的除法表达式:没有精确的除结果”.当时输入的10/3,结果应该是3.3333....333. 下面这种处理方式有问题. BigDecimal num3 = new BigDe ...

  9. Python基础之Windows下Python3.x环境搭建

    本文介绍使用Python3.x搭建Python环境,文章最后将使用Windows自带的cmd写一个简单的Python程序. 下载Python环境安装包 在https:/www.python.org/下 ...

  10. SpringCloud实战-Zuul网关服务

    为什么需要网关呢? 我们知道我们要进入一个服务本身,很明显我们没有特别好的办法,直接输入IP地址+端口号,我们知道这样的做法很糟糕的,这样的做法大有问题,首先暴露了我们实体机器的IP地址,别人一看你的 ...