设计模式之结构型模式(适配器、桥接、组合、享元、装饰者、外观、代理)

文章目录

    • 一、结构型设计模式
    • 二、适配器模式
    • 三、桥接模式
    • 四、组合模式
    • 五、享元模式
    • 六、装饰者模式
    • 七、外观模式
    • 八、代理设计模式

一、结构型设计模式

这篇文章我们来讲解下结构型设计模式,结构型设计模式,主要处理类或对象的组合关系,为如何设计类以形成更大的结构提供指南。

结构型设计模式包括:适配器模式(Adapter Pattern)、桥接模式(Bridge Pattern)、组合模式(Composite Pattern)、装饰器模式(Decorator Pattern)、外观模式(Facade Pattern)、享元模式(Flyweight Pattern)、代理模式(Proxy Pattern)

二、适配器模式

适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。它结合了两个独立接口的功能。
优点:

  1. 可以让任何两个没有关联的类一起运行。
  2. 提高了类的复用。
  3. 增加了类的透明度。
  4. 灵活性好。

这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。举个真实的例子,在视频播放器中,假设视频播放器只能播放MP4格式的视频,那现在又有个VLC格式的视频,就不能播放了,那要如何解决这个问题?如果我们做个转换器,将VLC格式的视频转换为MP4格式的视频不就可以播放了吗,那这个转换器我们就可以采用适配器设计模式来设计。

下面使用程序演示下上面的例子:

  1. 定义视频接口
public interface VideoInterFace {String getVideoPath();
}
  1. 定时Mp4格式视频实例
public class Mp4Video implements VideoInterFace {@Overridepublic String getVideoPath() {return "Mp4视频的路径";}
}
  1. 定义VLC格式视频实例
public class VlcVideo implements VideoInterFace{@Overridepublic String getVideoPath() {return "Vlc视频的路径";}
}
  1. 定义播放器,只接口Mp4格式的视频
public class Player {private Mp4Video video;public Player(Mp4Video video) {this.video = video;}public void play() {System.out.println(StringFormatter.concat("播放视频视频地址:", video.getVideoPath()).getValue());}
}
  1. 需要播放VLC格式的视频,定义Mp4的适配器,并接收VLC格式视频,进行转码。
public class Mp4Adapter extends Mp4Video {private VlcVideo vlcVideo;public Mp4Adapter(VlcVideo vlcVideo) {this.vlcVideo = vlcVideo;}@Overridepublic String getVideoPath() {System.out.println(StringFormatter.concat("开始格式转换,vlc地址:", vlcVideo.getVideoPath()).getValue());return "转换后的Mp4路径!";}
}
  1. 测试
public class demo {public static void main(String[] args) {Player player = new Player(new Mp4Video());player.play();VlcVideo vlcVideo = new VlcVideo();Player player1 = new Player(new Mp4Adapter(vlcVideo));player1.play();}
}

在这里插入图片描述

从上面的例子可以看出,需要播放VLC格式,就需要写一个目标适配器,这里是Mp4适配器,并继承Mp4,使之有Mp4的特性,并在内部做相应的转换即可,提高了系统的可扩展性。

三、桥接模式

桥接模式(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化。它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦
这种模式涉及到一个作为桥接的接口,使得实体类的功能独立于接口实现类。这两种类型的类可被结构化改变而互不影响。
优点:

  1. 抽象和实现的分离。
  2. 优秀的扩展能力。
  3. 实现细节对客户透明。

举个例子:绘画不同颜色的各种图像,画不同的形状和涂颜色,便是两个不同的功能,但两者又相互联系,在画完形状后需要涂颜色,但颜色和形状有使多种多样的,此时就可以采用桥接设计模式,将两者的抽象化与实现化解耦,形状和颜色可以独立变化

下面使用程序演示下上面的例子:

  1. 定义颜色的接口
public interface ColorApi {public void drawCircle();
}
  1. 定义不同颜色的实现,这里采用红色和绿色
public class ReqColor implements ColorApi {@Overridepublic void drawCircle() {System.out.println("开始涂红色!");}
}
public class GreenColor implements ColorApi {@Overridepublic void drawCircle() {System.out.println("开始涂绿色!");}
}
  1. 定义形状的接口
public interface ShapeApi {//画形状void draw();//画形状并涂颜色void drawShapeAndsColor();
}
  1. 定义形状的抽象模板,将共性的操作定义到抽象中
public abstract class ShapeAbstract implements ShapeApi {public ColorApi colorApi;public ShapeAbstract(ColorApi colorApi) {this.colorApi = colorApi;}@Overridepublic void drawShapeAndsColor() {draw();colorApi.drawCircle();}
}
  1. 定义圆形的实例
public class Circle extends ShapeAbstract {public Circle(ColorApi colorApi) {super(colorApi);}@Overridepublic void draw() {System.out.println("开始画圆形!");}
}
  1. 定义矩形的实例
public class Rectangle extends ShapeAbstract {public Rectangle(ColorApi colorApi) {super(colorApi);}@Overridepublic void draw() {System.out.println("开始画矩形");}
}
  1. 演示
public class demo {public static void main(String[] args) {ShapeApi shapeReq = new Circle(new ReqColor());shapeReq.drawShapeAndsColor();ShapeApi shapeGreen = new Circle(new GreenColor());shapeGreen.drawShapeAndsColor();ShapeApi rectangle = new Rectangle(new GreenColor());rectangle.drawShapeAndsColor();}
}

在这里插入图片描述

上面可以看出,可以灵活的定义形状和颜色的组合,并且他们两个都可以独立变化,添加新的形状只需,建立新的类并实现形状接口,添加颜色也是如此,极大的提高的系统的可扩展性和可维护型。

四、组合模式

组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象
组合模式依据树形结构来组合对象,用来表示部分以及整体层次。它创建了对象组的树形结构。
优点:

  1. 高层模块调用简单。
  2. 节点自由增加。

缺点:
在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。

举个例子:一个公司,从上到下分为,公司、部门、小组等,他们整个在一起才能称为一个完整的公司,要表示做个公司的结构,就可以采用组合设计模式。

下面使用程序演示下上面的例子:

  1. 定义属性类,用来表示不同层级的对象
@Data
public class Property {private String name;//下一层的子集private List<Property> next;public Property(String name) {this.name = name;next = new ArrayList<Property>();}public void add(Property e) {next.add(e);}public void remove(Property e) {next.remove(e);}public List<Property> getSubordinates(){return next;}
}
  1. 使用演示
public class demo {public static void main(String[] args) {Property company = new Property("公司");Property department = new Property("部门");Property group = new Property("小组");company.add(department);department.add(group);System.out.println(company);}
}

在这里插入图片描述

上面就演示了一个公司的组合,通过company对象就可以获得整个公司的各个部分的对象。组合设计模式主要适合于整体部分的场景。

五、享元模式

享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。它提供了减少对象数量从而改善应用所需的对象结构的方式。
享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。他的优点是大大减少对象的创建,降低系统的内存,使效率提高,但也有可能造成内存的浪费。比如Spring的采用容器的方式存储bean,使用时从容器中获取。

还是拿画不同颜色形状的例子演示下享元设计模式的使用:

  1. 定义形状的接口
public interface Shape {void draw();
}
  1. 定义圆形的实现,并接收一个颜色值:
public class Circle implements Shape {private String color;public Circle(String color) {this.color = color;}@Overridepublic void draw() {System.out.println(StringFormatter.concat("开始画 ", color, " 色的圆 ").getValue());}
}
  1. 定义形状对象获取工厂,根据颜色值将对象存储到HashMap中,后再根据颜色值取对象,达到复用的效果。
public class ShapeFactory {private static final HashMap<String, Shape> circleMap = new HashMap<>();public static Shape getCircle(String color) {if (!circleMap.containsKey(color)) {System.out.println(StringFormatter.concat(">> 创建", color, "颜色的圆 ").getValue());circleMap.put(color, new Circle(color));}return circleMap.get(color);}
}
  1. 使用
public class demo {private static final String colors[] ={"Red", "Green", "Blue", "White", "Black"};public static void main(String[] args) {for (int i = 0; i < 20; ++i) {Shape circle = ShapeFactory.getCircle(getRandomColor());circle.draw();}}private static String getRandomColor() {return colors[(int) (Math.random() * colors.length)];}
}

在这里插入图片描述

上面可以看出,享元设计模式可以大大减少对象的创建,但也有可以造成内存的浪费,比如某个对象的使用频率非常低,如果一直存在内存中就有点浪费空间了。

六、装饰者模式

装饰者模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。它是作为现有的类的一个包装
装饰类和被装饰类可以独立发展,不会相互耦合,装饰者模式是继承的一个替代模式,装饰者模式可以动态扩展一个实现类的功能。

举个例子:还是绘画不同的形状的例子,加入系统中有画各种形状的功能,但随着功能后期的演化,需要画出带有边框的各种形状,那么此时就可以采用装饰者设计模式来做增强。

下面使用程序演示下上面的例子:

  1. 定义形状接口
public interface Shape {void draw();
}
  1. 定义圆形的实现
public class Circle implements Shape {@Overridepublic void draw() {System.out.println("开始画圆形!");}
}
  1. 定义矩形的实现
public class Rectangle implements Shape {@Overridepublic void draw() {System.out.println("开始画矩形!");}
}
  1. 定义装饰器的抽象模板
public abstract class ShapeDecorator implements Shape {protected Shape decoratedShape;public ShapeDecorator(Shape decoratedShape){this.decoratedShape = decoratedShape;}@Overridepublic void draw(){decoratedShape.draw();}  
}
  1. 定义具体的边框装饰器
public class BorderShapeDecorator extends ShapeDecorator {public BorderShapeDecorator(Shape decoratedShape) {super(decoratedShape);     }@Overridepublic void draw() {decoratedShape.draw();         setRedBorder(decoratedShape);}private void setRedBorder(Shape decoratedShape){System.out.println("画边框!");}
}
  1. 演示
public class demo {public static void main(String[] args) {Shape circle = new Circle();circle.draw();Shape shape = new BorderShapeDecorator(new Circle());shape.draw();Shape shape1 = new BorderShapeDecorator(new Rectangle());shape1.draw();}
}

在这里插入图片描述

上面可以看出再不改变原先类的基础上,做了画边框的效果,对原有做增强,使用装饰者设计模式,可以大大提高系统的可扩展性。

七、外观模式

外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。它向现有的系统添加一个接口,来隐藏系统的复杂性。
这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用。
它的优点是可以减少系统相互依赖、提高灵活性、提高了安全性。但是它不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。

举个例子:画各种图形的例子,比如要画圆形、矩形、三角形,每画一种图像都要拿到对应的抽象,并调用绘制方法,如果要画的形状过多,这么多的抽象就不好管理了,而如果使用外观设计模式,提供一个统一的抽象,在这个抽象中就可以完成上面不同的绘制,这样就方便了我们的管理。

下面使用程序演示下上面的例子:

  1. 定义形状的接口
public interface Shape {void draw();
}
  1. 定义圆形的实例
public class Circle implements Shape {@Overridepublic void draw() {System.out.println("开始画圆!");}
}
  1. 定义矩形的实例
public class Rectangle implements Shape {@Overridepublic void draw() {System.out.println("开始画矩形!");}
}
  1. 定义三角形的实例
public class Triangle implements Shape {@Overridepublic void draw() {System.out.println("开始画三角形!");}
}
  1. 定义一个外观类,并调用上面的功能
public class ShapeFacade {private Shape circle;private Shape rectangle;private Shape square;public ShapeFacade() {circle = new Circle();rectangle = new Rectangle();square = new Triangle();}public void drawCircle(){circle.draw();}public void drawRectangle(){rectangle.draw();}public void drawSquare(){square.draw();}
}
  1. 演示
public class demo {public static void main(String[] args) {ShapeFacade shapeFacade = new ShapeFacade();shapeFacade.drawCircle();shapeFacade.drawRectangle();shapeFacade.drawSquare();}
}

在这里插入图片描述
外观设计模式还是比较容易理解的,就是把多个功能统一整个到一个对象中,由这个对象再去调用具体的类和方法。

八、代理设计模式

代理设计模式通过代理控制对象的访问,可以详细访问某个对象的方法,在这个方法调用处理,或调用后处理。既(AOP微实现) 。

代理有分静态代理动态代理

  • 静态代理:在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
  • 动态代理:是在使用时,动态的生成代理对象,他是在内存中构建代理对象的。

举个例子,在做数据库操作时,一般我们都会在事物中做SQL的操作,那就需要在操作前开启事物,操作后如果成功就需要提交事物,如果代用代理设计模式,就可以将事物开启提交逻辑放在代理类中,被代理的类,只需要关注业务逻辑即可。

下面以支付和事物为例演示下代理模式

采用静态代理,实现上面例子:

  1. 定义支付接口
public interface PayInterFace {void pay();
}
  1. 定义微信支付实现
public class WxPay implements PayInterFace {@Overridepublic void pay() {System.out.println("支付中...");}
}
  1. 定义支付的代理类
public class PayProxy implements PayInterFace {private WxPay pay;public PayProxy(WxPay pay) {this.pay = pay;}@Overridepublic void pay() {System.out.println("事物开始!");pay.pay();System.out.println("提交事物!");}
}
  1. 演示
public class demo {public static void main(String[] args) {PayInterFace pay = new PayProxy(new WxPay());pay.pay();}
}

在这里插入图片描述

上面的静态代理,可以看出,我们需要对每个被代理对象设计一个代理类,如果代理的功能非常多,那就需要开发人员写特别多的代理类,下面可以看下动态代理的使用。

采用动态代理,实现上面例子:
这里使用JDK自带的动态代理来实现

  1. 再定义一个支付宝的支付实现
public class ZfbPay implements PayInterFace {@Overridepublic void pay() {System.out.println("支付宝支付中...");}
}
  1. 定义代理对象,采用jdk的 InvocationHandler 接口
public class PayProxy implements InvocationHandler {private Object object;public PayProxy(Object object) {this.object = object;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("事物开始!");Object result = method.invoke(object, args);System.out.println("提交事物!");return result;}
}
  1. 演示
public class demo {public static void main(String[] args) {PayInterFace pay = (PayInterFace) Proxy.newProxyInstance(PayInterFace.class.getClassLoader(),new Class[]{PayInterFace.class},new PayProxy(new WxPay()));pay.pay();PayInterFace pay1 = (PayInterFace) Proxy.newProxyInstance(PayInterFace.class.getClassLoader(),new Class[]{PayInterFace.class},new PayProxy(new ZfbPay()));pay1.pay();}
}

在这里插入图片描述

上面使用一个代理类,代理了多个对象,相对于静态代理,是代码更简介,灵活性也更高。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/213552.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前后端数据传输格式(上)

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 作为后端&#xff0c;写…

Leetcode—198.打家劫舍【中等】

2023每日刷题&#xff08;五十二&#xff09; Leetcode—198.打家劫舍 算法思想 具体思路 首先&#xff0c;我们从上面的题目描述中抽象出题意。 ● 从一个非负整数数组中找到一个子序列&#xff0c;并且该子序列的和最大 ● 子序列中每个数的位置不能够相邻。举例来讲&…

【C语言】动态内存管理(C语言的难点与精华,数据结构的前置知识,你真的掌握了吗?)

文章目录 引言一、为什么要动态内存分配二、动态内存分配的相关函数2.1 malloc2.2 free2.3 calloc2.4 realloc 三、常见的动态内存的错误3.1 对NULL指针的解引用3.2 对动态内存越界访问3.3 对非动态内存释放3.4 对动态内存部分释放3.5 对动态内存多次释放3.6 未对动态内存释放&…

Java-JDBC操作MySQL

Java-JDBC操作MySQL 文章目录 Java-JDBC操作MySQL一、Java-JDBC-MySQL的关系二、创建连接三、登录MySQL四、操作数据库1、返回型操作2、无返回型操作 练习题目及完整代码 一、Java-JDBC-MySQL的关系 #mermaid-svg-B7qjXrosQaCOwRos {font-family:"trebuchet ms",verd…

邮政快递查询,邮政快递单号查询,按物流更新量筛选出需要的单号

批量查询邮政快递单号的物流信息&#xff0c;按物流更新量将需要的单号筛选出来。 所需工具&#xff1a; 一个【快递批量查询高手】软件 邮政快递单号若干 操作步骤&#xff1a; 步骤1&#xff1a;运行【快递批量查询高手】软件&#xff0c;并登录 步骤2&#xff1a;点击主界…

PHP短信接口防刷防轰炸多重解决方案三(可正式使用)

短信接口盗刷轰炸&#xff1a;指的是黑客利用非法手段获取短信接口的访问权限&#xff0c;然后使用该接口发送大量垃圾短信给目标用户 短信验证码轰炸解决方案一(验证码类解决)-CSDN博客 短信验证码轰炸解决方案二(防止海外ip、限制ip、限制手机号次数解决)-CSDN博客 PHP短信…

《opencv实用探索·八》图像模糊之均值滤波、高斯滤波的简单理解

1、前言 什么是噪声&#xff1f; 该像素与周围像素的差别非常大&#xff0c;导致从视觉上就能看出该像素无法与周围像素组成可识别的图像信息&#xff0c;降低了整个图像的质量。这种“格格不入”的像素就被称为图像的噪声。如果图像中的噪声都是随机的纯黑像素或者纯白像素&am…

Fiddler抓包模拟器(雷电模拟器)

Fiddler设置 List item 打开fiddler,的options 点击OK,重启fiddler 模拟器 更改网络设置 IP可以在电脑上终端上查看 然后在模拟器浏览器中输入IP:端口 安装证书

[二分查找双指针]LeetCode881: 救生艇

救生艇 作者推荐 [二分查找]LeetCode2040:两个有序数组的第 K 小乘积 本文涉及的基础知识点 二分查找算法合集 题目 给定数组 people 。people[i]表示第 i 个人的体重 &#xff0c;船的数量不限&#xff0c;每艘船可以承载的最大重量为 limit。 每艘船最多可同时载两人&am…

[足式机器人]Part2 Dr. CAN学习笔记-数学基础Ch0-5Laplace Transform of Convolution卷积的拉普拉斯变换

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN学习笔记-数学基础Ch0-5Laplace Transform of Convolution卷积的拉普拉斯变换 Laplace Transform : X ( s ) L [ x ( t ) ] ∫ 0 ∞ x ( t ) e − s t d t X\left( s \right) \mathcal{L} \left[ x\lef…

QT作业2

使用手动连接&#xff0c;将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中&#xff0c;在自定义的槽函数中调用关闭函数 将登录按钮使用qt5版本的连接到自定义的槽函数中&#xff0c;在槽函数中判断ui界面上输入的账号是否为"admin"&#xff0c;密码是否为…

【Flink系列三】数据流图和任务链计算方式

上文介绍了如何计算并行度和slot的数量&#xff0c;本文介绍Flink代码提交后&#xff0c;如何生成计算的DAG数据流图。 程序和数据流图 所有的Flink程序都是由三部分组成的&#xff1a;Source、Transformation和Sink。Source负责读取数据源&#xff0c;Transformation利用各种…

Linux各目录结构说明

文章目录 目录说明源码放哪里&#xff1f;拓展&#xff1a;Linux里面安装软件是装在home目录还是opt目录还是/usr/local好&#xff1f; bin boot dev etc home lib lib64 lostfound media mnt opt proc root run sbin srv sys tmp usr var 目录说明 bin 存放二进制可执行文件&…

《Spring Cloud Alibaba 从入门到实战》分布式配置

分布式配置 1、简介 Nacos 提供用于存储配置和其他元数据的 key/value 存储&#xff0c;为分布式系统中的外部化配置提供服务器端和客户端支持。 Spring Cloud Alibaba Nacos Config 是 Config Server 和 Client 的替代方案&#xff0c;在特殊的 bootstrap 阶段&#xff0c;…

2023.12.4 关于 Spring Boot 统一异常处理

目录 引言 统一异常处理 异常全部监测 引言 将异常处理逻辑集中到一个地方&#xff0c;可以避免在每个控制器或业务逻辑中都编写相似的异常处理代码&#xff0c;这降低了代码的冗余&#xff0c;提高了代码的可维护性统一的异常处理使得调试和维护变得更加容易&#xff0c;通…

机器学习之无监督学习:九大聚类算法

今天&#xff0c;和大家分享一下机器学习之无监督学习中的常见的聚类方法。 今天&#xff0c;和大家分享一下机器学习之无监督学习中的常见的聚类方法。 在无监督学习中&#xff0c;我们的数据并不带有任何标签&#xff0c;因此在无监督学习中要做的就是将这一系列无标签的数…

Python实现PDF-Excel

轻松解决PDF格式转Excel&#xff08;使用python实现&#xff09; 实现思路&#xff1a; 要将PDF转换为Excel&#xff0c;可以使用以下步骤&#xff1a; 解析PDF内容&#xff1a;首先&#xff0c;需要使用Python中的第三方库&#xff08;如PyPDF2、pdfminer等&#xff09;来解…

Ribbon 饥饿加载

Ribbon默认是采用懒加载&#xff0c;即第一次访问时才会去创建LoadBalanceClient&#xff0c;请求时间会很长而饥饿加载则会在项目启动时创建&#xff0c;降低第一次访问的耗时&#xff0c;通过下面配置开启饥饿加载: 一、懒加载 Ribbon 默认为懒加载即在首次启动Application…

数据结构之插入排序

目录 前言 插入排序 直接插入排序 插入排序的时间复杂度 希尔排序 前言 在日常生活中&#xff0c;我们不经意间会遇到很多排序的场景&#xff0c;比如在某宝&#xff0c;某东上买东西&#xff0c;我们可以自己自定义价格是由高到低还是由低到高&#xff0c;再比如在王者某…

修改移远提供的GobiNet、quectel-CM源码,使其支持有方N720 4G模块

最近在研究imx6ull linux下4G模块驱动的移植&#xff0c;参考的移远ec20的移植方法&#xff0c;添加了GobiNet驱动&#xff0c;编译了quectel-CM工具&#xff0c;并且可以正常拨号&#xff0c;分配到ip&#xff0c;如下&#xff1a; ping外网也没有压力&#xff0c;如下…