前言

​ 今天开始我们专题的第八课了。本章节将介绍:三个设计模式,适配器模式、装饰者模式和观察者模式。通过学习适配器模式,可以优雅的解决代码功能的兼容问题。另外有重构需求的人群一定需要掌握装饰者模式。本章节参考资料书籍《Spring 5核心原理》中的第一篇 Spring 内功心法(Spring中常用的设计模式)(没有电子档,都是我取其精华并结合自己的理解,一个字一个字手敲出来的,如果觉得本文对你有用,请点个推荐)。

适配器模式

适配器模式的应用场景

​ 适配器模式(Adapter Pattern)是指将一个类的接口转换成客户期望的另一个接口,使原本的接口不兼容的类可以一起工作,属于结构型设计模式。

适配器适用于以下几种业务场景:

​ 1、已经存在的类,它的方法和需求不匹配(方法结果相同或相似)的情况。

​ 2、适配器模式不是软件设计阶段考虑的设计模式,是随着软件维护,由于不同产品、不同厂家造成功能类似而接口不相同情况下的解决方案。有点亡羊补牢的感觉。生活中也非常的应用场景,例如电源插转换头、手机充电转换头、显示器转接头。

在中国民用电都是 220V 交流电,但我们手机使用的锂电池使用的 5V 直流电。因此,我 们给手机充电时就需要使用电源适配器来进行转换。下面我们有代码来还原这个生活场 景,创建 AC220 类,表示 220V 交流电:

package com.study;

/**
* @author wangzhongyuan
*/
public class AC220 {
public int outputAC220V(){
int output = 220;
System.out.println("提供220V交流电");
return output;
}
}

创建5V电源的接口:

package com.study;

/**
* @author wangzhongyuan
*/
public interface DC5 {
int output5V();
}

创建提供5V电源的适配器:

package com.study;

/**
* @author wangzhongyuan
*/
public class PowerAdapter implements DC5{
AC220 ac220; public PowerAdapter(AC220 ac220) {
this.ac220 = ac220;
} @Override
public int output5V() {
int outputAC220V = ac220.outputAC220V();
System.out.println("将220v转换成5v");
return outputAC220V/44;
}
}

测试代码:

package com.study;

/**
* @author wang.zhongyuan
* @version 1.0
* @date 2020/7/20 11:17 下午
*/
public class DemoTest { public static void main(String[] args) {
DC5 powerAdapter = new PowerAdapter(new AC220());
powerAdapter.output5V();
}
}

输出结果:

![image-20200720232235613](/Users/wangzhongyuan/Library/Application Support/typora-user-images/image-20200720232235613.png)

从上面的代码样式可以看出,适配器就是通过增加一个适配器类持有原有提供者的对象,实现了二者的兼容。

适配模式的优缺点

优点:

1、能提高类的透明性和复用,现有的类复用但不需要改变。

2、目标类和适配器类解耦,提高程序的扩展性。

3、在很多业务场景中符合开闭原则。

缺点:

1、适配器编写过程需要全面考虑,可能会增加系统的复杂性。

2、增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱。

装饰者模式

装饰者模式的应用场景

​ 装饰者模式(Decorator Pattern)是指在不改变原有对象的基础之上,将功能附加到对 象上,提供了比继承更有弹性的替代方案(扩展原有对象的功能),属于结构型模式。 装饰者模式在我们生活中应用也比较多如给煎饼加鸡蛋;给蛋糕加上一些水果;给房子 装修等,为对象扩展一些额外的职责。装饰者在代码程序中适用于以下场景:

1、用于扩展一个类的功能或给一个类添加附加职责。

2、动态的给一个对象添加功能,这些功能可以再动态的撤销。

代码实现

来看一个这样的场景,上班族白领其实大多有睡懒觉的习惯,每天早上上班都是踩点,于是很多小伙伴为了多赖一会儿床都不吃早餐。那么,也有些小伙伴可能在上班路上碰 到卖煎饼的路边摊,都会顺带一个到公司茶水间吃早餐。卖煎饼的大姐可以给你的煎饼 加鸡蛋,也可以加香肠。

创建煎饼类:

package com.study;

/**
* @author wang.zhongyuan
* @version 1.0
* @date 2020/7/20 11:57 下午
*/
public class Battercake { public String getMsg(){
return "煎饼";
} public int getPrice(){
return 5;
}
}

给煎饼加个蛋:

package com.study;

/**
* @author wang.zhongyuan
* @version 1.0
* @date 2020/7/20 11:59 下午
*/
public class BattercakeWithEgg extends Battercake{
@Override
public String getMsg() {
return super.getMsg() + "加鸡蛋";
} @Override
public int getPrice() {
return super.getPrice() + 1;
}
}

再加个烤肠:

package com.study;

/**
* @author wang.zhongyuan
* @version 1.0
* @date 2020/7/21 12:00 上午
*/
public class BatterCakeWithEggAndSausage extends BattercakeWithEgg{
@Override
public String getMsg() {
return super.getMsg() +"加烤肠";
} @Override
public int getPrice() {
return super.getPrice() + 3;
}
}

测试代码:

package com.study;

/**
* @author wang.zhongyuan
* @version 1.0
* @date 2020/7/21 12:01 上午
*/
public class DemoTest { public static void main(String[] args) {
BatterCakeWithEggAndSausage batterCakeWithEggAndSausage = new BatterCakeWithEggAndSausage();
String msg = batterCakeWithEggAndSausage.getMsg();
int price = batterCakeWithEggAndSausage.getPrice();
System.out.println("买了:"+msg+",价格为:"+price);
}
}

输出结果:

​ ![image-20200721000740128](/Users/wangzhongyuan/Library/Application Support/typora-user-images/image-20200721000740128.png)

运行结果没有问题,但是如果用户需要一个加 2 个鸡蛋加 1 根香肠的煎饼,那么用我们现在的类结构是创建不出来的,也无法自动计算出价格,除非再创建一个类做定制。 如果需求再变,一直加定制显然是不科学的。那么下面我们就用装饰者模式来解决上面 的问题。

创建煎饼抽象类:

package com.study;

/**
* @author wang.zhongyuan
* @version 1.0
* @date 2020/7/21 12:18 上午
*/
public abstract class AbstractBatterCake {
protected abstract String getMsg();
protected abstract int getPrice();
}

创建基础套餐煎饼:

package com.study;

/**
* @author wang.zhongyuan
* @version 1.0
* @date 2020/7/21 12:19 上午
*/
public class BatterCakeWithBase extends AbstractBatterCake{
@Override
protected String getMsg() {
return "煎饼";
} @Override
protected int getPrice() {
return 5;
}
}

创建额外套餐的抽象装饰类:

package com.study;

/**
* @author wang.zhongyuan
* @version 1.0
* @date 2020/7/21 12:21 上午
*/
public abstract class BatterCakeDecorator extends AbstractBatterCake{
AbstractBatterCake batterCake; public BatterCakeDecorator(AbstractBatterCake batterCake) {
this.batterCake = batterCake;
} protected abstract void doSomething(); @Override
protected String getMsg() {
return this.batterCake.getMsg();
} @Override
protected int getPrice() {
return this.batterCake.getPrice();
}
}

创建加鸡蛋套餐:

package com.study;

/**
* @author wang.zhongyuan
* @version 1.0
* @date 2020/7/20 11:59 下午
*/
public class BattercakeWithEgg extends BatterCakeDecorator{ public BattercakeWithEgg(AbstractBatterCake batterCake) {
super(batterCake);
} @Override
protected void doSomething() { } @Override
public String getMsg() {
return super.getMsg() + "加鸡蛋";
} @Override
public int getPrice() {
return super.getPrice() + 1;
}
}

创建加烤肠套餐:

package com.study;

/**
* @author wang.zhongyuan
* @version 1.0
* @date 2020/7/21 12:00 上午
*/
public class BatterCakeWithSausage extends BatterCakeDecorator{ public BatterCakeWithSausage(AbstractBatterCake batterCake) {
super(batterCake);
} @Override
protected void doSomething() { } @Override
public String getMsg() {
return super.getMsg() +"加烤肠";
} @Override
public int getPrice() {
return super.getPrice() + 3;
}
}

测试代码:

package com.study;

/**
* @author wang.zhongyuan
* @version 1.0
* @date 2020/7/21 12:01 上午
*/
public class DemoTest { public static void main(String[] args) {
AbstractBatterCake batterCake;
batterCake = new BatterCakeWithBase();
batterCake = new BattercakeWithEgg(batterCake);
batterCake = new BatterCakeWithSausage(batterCake);
//再加个鸡蛋
batterCake = new BattercakeWithEgg(batterCake);
System.out.println(batterCake.getMsg()+",价格:"+batterCake.getPrice());
}
}

输出结果:

![image-20200721003623768](/Users/wangzhongyuan/Library/Application Support/typora-user-images/image-20200721003623768.png)

装饰者模式最本质的特征是讲原有类的附加功能抽离出来,简化原有类的逻辑。通过这样两个案例,我们可以总结出来,其实抽象的装饰者是可有可无的,具体可以根据业务模型来选择。

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

装饰者和适配器模式都是包装模式(Wrapper Pattern),装饰者也是一种特殊的代理模式。

		装饰者模式 															适配器模式
形式 是一种非常特别的适配器模式 没有层级关系, 装饰器模式有层级关系
定义 装饰者和被装饰者都实现同一个接口, 适配器和被适配者没有必然的联系,通 常是采用继承或代理的形式进行包装
主要目的是为了扩展之后依旧保 留 OOP 关系
关系 满足 is-a 的关系 满足 has-a 的关系
功能 注重覆盖、扩展 注重兼容、转换
设计 前置考虑 后置考虑

装饰者模式的优缺点

优点:

1、装饰者是继承的有力补充,比继承灵活,不改变原有对象的情况下动态地给一个对象扩展功能,即插即用。

2、通过使用不同装饰类以及这些装饰类的排列组合,可以实现不同效果。

3、装饰者完全遵守开闭原则。

缺点:

1、会出现更多的代码,更多的类,增加程序复杂性。

2、动态装饰时,多层装饰时会更复杂。

观察者模式

观察者模式的应用场景

​ 观察者模式(Observer Pattern)定义了对象之间的一对多依赖,让多个观察者对象同 时监听一个主体对象,当主体对象发生变化时,它的所有依赖者(观察者)都会收到通 知并更新,属于行为型模式。观察者模式有时也叫做发布订阅模式。观察者模式主要用 于在关联行为之间建立一套触发机制的场景。

代码实现

​ 小伙伴们肯定都刷过抖音,遇到喜欢的作品都会点个❥喜欢,我们通过模拟喜欢这个事件来实践下观察者模式。当你点击喜欢时,会触发两个事件,一个喜欢数量会增加+1,二是作者会受到消息。你可能会想到MQ,异步队列等,其实JDK本身就提供这样的API。

创建事件模型类,用于区分什么样的事件,观察者可以根据不同事件类型做不同处理:

package com.study.demo4;

/**
* 事件模型
* @Author wangzhongyuan
* @Date 2020/7/21 11:28
* @Version 1.0
**/
public enum EventModel {
LIKE_EVENT("喜欢事件",1,null),
MCOMENT_ENVET("评论事件",2,null)
; private String message;
private int type;
private Object date; EventModel(String message, int type, Object date) {
this.message = message;
this.type = type;
this.date = date;
}}

创建事件类,被观察者对象,调用方可以向该对象中传送事件:

package com.study.demo4;

import java.util.Observable;

/**
* 事件总线(被观察者)
* @Author wangzhongyuan
* @Date 2020/7/21 11:24
* @Version 1.0
**/
public class EventBus extends Observable{
/**
* 被观察者方法
* @param eventModel
*/
public void postEvent(EventModel eventModel){
System.out.println("推送事件");
setChanged();
//通知观察者
notifyObservers(eventModel);
}
}

创建不同业务的观察者:

package com.study.demo4;

import java.util.Observable;
import java.util.Observer; /**
* 观察者1
*
* @Author 19054253
* @Date 2020/7/21 11:32
* @Version 1.0
**/
public class OneObserver implements Observer { public void update(Observable o, Object arg) {
System.out.println("观察1,监听到了事件,触发:给作者推送消息!");
}
}
package com.study.demo4;

import java.util.Observable;
import java.util.Observer; /**
* 观察者2
*
* @Author 19054253
* @Date 2020/7/21 11:48
* @Version 1.0
**/
public class TwoObserver implements Observer { public void update(Observable o, Object arg) {
System.out.println("观察2,监听到了事件,触发:喜欢总数+1");
}
}

调用测试:

package com.study.demo4;

/**
* @Author wangzhongyuan
* @Date 2020/7/21 11:35
* @Version 1.0
**/
public class DemoTest {
public static void main(String[] args) {
//被观察者
EventBus eventBus = new EventBus();
//观察者
OneObserver oneObserver = new OneObserver();
TwoObserver twoObserver = new TwoObserver();
//监听观察者
eventBus.addObserver(oneObserver);
eventBus.addObserver(twoObserver);
//被观察者触发事件
eventBus.postEvent(EventModel.LIKE_EVENT);
}
}

输出结果:

从上面代码可以看出来,观察者模式的本质就是,被观察者对象持有观察者对象的引用,由被观察者去通知了观察者去做了某件事。JDK源码中,观察者模式也应用非常多。例如java.awt.Event就是观察者模式的一种,只不过Java很少被用来写桌面程序。以上就是我模拟的事件机制。

观察模式的优缺点

优点:

1、观察者和被观察者之间建立了一个抽象的耦合。

2、观察者模式支持广播通信。

缺点:

1、观察者之间有过多的细节依赖、提高时间消耗及程序的复杂度。

2、使用要得当,要避免循环调用。

大型Java进阶专题(八)设计模式之适配器模式、装饰者模式和观察者模式的更多相关文章

  1. 大型Java进阶专题(五) 设计模式之单例模式与原型模式

    前言 ​ 今天开始我们专题的第四课了,最近公司项目忙,没时间写,今天抽空继续.上篇文章对工厂模式进行了详细的讲解,想必大家对设计模式合理运用的好处深有感触.本章节将介绍:单例模式与原型模式.本章节参考 ...

  2. 大型Java进阶专题(四) 设计模式之工厂模式

    前言 ​ 今天开始我们专题的第三课了,开始对设计模式进行讲解,本章节介绍:了解设计模式的由来,介绍设计模式能帮我们解决那些问题以及剖析工厂模式的历史由来及应用场景.本章节参考资料书籍<Sprin ...

  3. 大型Java进阶专题(九) 设计模式之总结

    前言 ​ 关于设计模式的文章就到这里了,学习这门多设计模式,你是不是有这样的疑惑,发现很多设计模式很类似,经常会混淆某些设计模式.这章节我们将对设计模式做一个总结,看看各类设计模式有什么区别.需要注意 ...

  4. 大型Java进阶专题(七) 设计模式之委派模式与策略模式

    前言 ​ 今天开始我们专题的第七课了.本章节将介绍:你写的代码中是否觉得很臃肿,程序中有大量的if...else,想优化代码,精简程序逻辑,提升代码的可读性,这章节将介绍如何通过委派模式.策略模式让你 ...

  5. 大型Java进阶专题(一) 前言

    前言 ​ 各位读者好,本系列为Java进阶专题,为那些有一定工作经验,做了多年业务的码农,希望突破技术瓶颈,但没有形成系统的Java只是体系,缺乏清晰的提升方法和学习路径的人,比如作者本人.该课题的是 ...

  6. 大型Java进阶专题(六)设计模式之代理模式

    代理模式 前言 又开始我的专题了,又停滞了一段时间了,加油继续吧.都知道 SpringAOP 是用代理模式实现,到底是怎么实现的?我们来一探究竟,并且自己仿真手写还原部分细节. 代理模式的应用 在生活 ...

  7. 大型Java进阶专题(二) 软件架构设计原则(上)

    前言 ​ 今天开始我们专题的第一课了,也是我开始进阶学习的第一天,我们先从经典设计思想开始,看看大牛市如何写代码的,提升技术审美.提高核心竞争力.本章节参考资料书籍<Spring 5核心原理&g ...

  8. 大型Java进阶专题(三) 软件架构设计原则(下)

    前言 ​ 今天开始我们专题的第二课了,本章节继续分享软件架构设计原则的下篇,将介绍:接口隔离原则.迪米特原则.里氏替换原则和合成复用原则.本章节参考资料书籍<Spring 5核心原理>中的 ...

  9. 大型Java进阶专题(十一) 深入理解JVM (下)

    前言 ​ 前面我们了解了JVM相关的理论知识,这章节主要从实战方面,去解读JVM. 类加载机制 ​ Java源代码经过编译器编译成字节码之后,最终都需要加载到虚拟机之后才能运行.虚拟机把描述类的数据从 ...

随机推荐

  1. windows 64位上安装mysql 5.7版本

    下载的mysql不是安装exe的软件,而是在windows上编译好的二进制mysql软件 下载安装之后配置环境变量:将目录D:\Program Files\mysql-5.7.18-winx64\my ...

  2. C# CLosedXML四句代码搞定DataTable数据导出到Excel

    最近用到DataTable导出到Excel,网上看了一下,都不怎么好使,逛了下GitHub一下完美解决了 用到的.net库CLosedXML,这个库用于读取,处理和写入Excel 2007+(.xls ...

  3. 【服务器】CentOs7系统使用宝塔面板搭建网站,有FTP配置(保姆式教程)

    内容繁多,请耐心跟着流程走,在过程中遇到问题请在下面留言(我只是小白,请专业人士喷轻点). 这次用thinkphp5.1做演示,单纯的做演示,我打算下一篇文章用typecho(博客框架)演示. 前言 ...

  4. 用一杯茶时间搭建Gitea服务器

     一.简单介绍 Gitea搭建局域网内的基于git的代码托管服务器,可以实现的功能包括:组织管理.团队管理.组织仓库设定.团队仓库分配.组织及团队权限分配管理.仓库添加PC协作者.仓库添加组织团队.分 ...

  5. 重学 Java 设计模式:实战备忘录模式「模拟互联网系统上线过程中,配置文件回滚场景」

    作者:小傅哥 博客:https://bugstack.cn - 原创系列专题文章 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 实现不了是研发的借口? 实现不了,有时候是功能复杂度较高难以实 ...

  6. 前端笔记(关于webpack打包时内存溢出问题的解决)

    首先安装increase-memory-limit cnpm install -g increase-memory-limit 重启cmd,并在项目跟目录中运行一下 increase-memory-l ...

  7. Java中时间加减的比较

    public class TestDate{ public static void main(String[] args){try{ Date date=new Date(); DateFormat  ...

  8. pl/sql案例

    项目生命周期: 瀑布模型 拿到一个项目后,首先:分析需要用到的SQL语句: 其次:分析需要定义的变量初始值是多少,怎么得到最终值: 案例一: 统计每年入职的员工数量以及总数量: SQL语句:selec ...

  9. CentOS7下普通账号通过systemctl管理服务需要输入root密码问题

    问题描述: 使用普通账号test通过systemctl启动系统服务提示需要输入root密码: 解决方案: 根据上面提示得知权限由polkit进行管理,对应的是org.freedesktop.syste ...

  10. POJ3057 Evacuation 二分图匹配+最短路

    POJ3057 Evacuation 二分图匹配+最短路 题目描述 Fires can be disastrous, especially when a fire breaks out in a ro ...