GOF23--23种设计模式(三)
一.桥接模式
Java中的桥接模式(Bridge Pattern)是一种结构性设计模式,它将抽象部分和实现部分分离,使它们可以独立变化,同时通过桥接对象将它们连接起来。
这种模式将抽象与其实现解耦,使得抽象和实现可以独立变化。抽象和它的实现通过一个桥接类进行连接,使得它们可以各自独立地变化。
桥接模式在应用上,通过一个应用实例引入吧
我们在开发一些应用程序或者web网站的时候,不仅仅是把它开发出来这么简单,还需要做一些兼容的东西,比如我们开发环境一般是在widows或者macOS上开发的,那么就有一个兼容性的问题,你开发的项目到底是在哪里运行的,比如一款APP它不仅仅在pc端可以运行,而且还需要再Android上运行,客户的再哪里有需求,我们就需要再哪里做兼容
但是,我们做兼容的时候,这些系统都要做兼容,是不是有一些麻烦呢?我们开发者开发APP和web,系统有windows,mac和Android,如果使用适配器模式的思想,就是一个一个的来做适配
那么小学就学过排列组合,那么需要适配的类有APP-Windows,APP-mac,APP-Android,web-Windows,web-mac,web-Android,一共需要六个适配类
这样适配很冗余,开发难度也加大了,而且不支持横向拓展,我们思考一下,难道世界上就只有这三种系统嘛?很显然不是的,就比如Linux,ChromeOS,ios,HarmonyOS等等
如果这些系统都来做适配的话,那么排列组合就是14种以上,需要手动的实现14种以上的实现类,很显然是不现实的
如下图:

所以我们可以把这种适配的方式抽象出来,对于这种有着某种关系的适配方式,我们可以把它抽象成两个二维坐标,这样操作以后将这个二维坐标的建立一个连接,使得两坐标之间的交点就是实现类,而两个坐标点就是参数,被组合进去的结果
什么意思呢?直接上图示:

桥接模式下的适配,只需要横向新增,当需要适配的时候,就将两个坐标点来进行连接,这种方式叫桥接
所以实现的关键在于那种连接,就是那座桥
在桥接模式中使用的是组合的方式将一个坐标轴组合进来,在使用适配的时候,会需要用户丢进来两个参数,一个是使用的系统,另一个是需要适配的应用
有了这两个桥接在一起,就可以完成所有的类适配
桥接模式最大的优点是可以横向拓展,现在我新增一个系统,直接在横轴上新增就好了,不需要对纵轴操作,它们之间有桥来联系,相同的新增一个服务,如文件服务,可以直接在纵轴上新增,不需要更改横轴
根本原因是它们已经属于两个域了,他们互不干预,通过桥来连接这两个域
如下图示:

桥接模式模拟测试:
创建一个Server接口:
//此接口需要APP或Web项目实现
public interface ServerDemo {
//打开方式
void open();
}
APP实现类:实现上面的接口
public class AppInstance implements ServerDemo{
//APP,表明是APP的实现类
@Override
public void open() {
System.out.println("打开应用App");
}
}
web实现类
public class WebInstance implements ServerDemo{
//web,表明是web的实现类
@Override
public void open() {
System.out.println("打开web网页");
}
}
抽象的系统类,将server接口组合进来
使用组合的方式来建立桥接
//抽象的system类
public abstract class SystemDem {
//将server接口组合进来,使得server和system有一种桥接关系
protected ServerDemo sd; public SystemDem(ServerDemo sd) {
this.sd = sd;
}
public void open(){
sd.open();
}
}
各个系统继承这个抽象类,并重写这其中的方法:
class WindowsOs extends SystemDem{
//windows系统
public WindowsOs(ServerDemo sd) {
super(sd);
}
@Override
public void open() {
System.out.println("WindowsOS =>");
super.open();
}
}
class MacOs extends SystemDem {
//macOS
public MacOs(ServerDemo sd) {
super(sd);
}
@Override
public void open() {
System.out.println("MacOS =>");
super.open();
}
}
写主方法:
public static void main(String[] args) {
//在windows系统下打开APP
WindowsOs ws = new WindowsOs(new AppInstance());
ws.open();
//在Mac系统下打开Web网页
MacOs ms = new MacOs(new WebInstance());
ms.open();
}
查看上面例子:
如果我们要在windos上适配APP,只需要在Windows系统中将App实例丢进去就行了
这个例子实现桥接模式就在system的抽象类中将需要适配的Server接口组合进来了,使得它们之间产生了联系,仿佛一座桥一样把他们连接在了一起
现在大概了解到了什么是将抽象部分和实现部分分离,代码实现的是坐标轴,使用的时候是坐标轴的交点(也是抽象出来的部分)
注意点:
桥接模式和适配器模式是兼容的,在这些设计模式之间,都是互相兼容的
二.静态代理模式
静态代理模式是一种设计模式,它通过创建一个代理类来处理被代理类的所有方法调用,从而实现了一些额外的功能或逻辑。
在静态代理模式中,代理类和被代理类通常具有相同的方法和属性,这样就可以在被代理类的所有方法调用时,先经过代理类的处理。这种模式通常用于在不修改原有代码的情况下,增加一些额外的功能或逻辑,例如日志记录、事务处理等。
实现静态代理模式需要以下步骤:
- 定义被代理类:被代理类通常是需要被代理的对象,它包含一些方法和属性。
- 定义代理类:代理类需要与被代理类具有相同的方法和属性,以便在调用被代理类的方法时能够进行相应的处理。
- 在代理类中实现被代理类的方法:在代理类中实现被代理类的方法时,可以调用被代理类的方法,同时可以加入一些额外的逻辑。
- 使用代理类:在使用被代理类的对象时,实际上使用的是代理类的对象。因此,可以在使用过程中调用代理类的方法,以实现额外的功能或逻辑。
静态代理模式的主要优点是可以实现对多个对象的代理,同时不需要修改原有的代码。此外,静态代理模式还可以在调用被代理类的方法时进行性能优化,例如缓存结果等。
浅浅的举一个例子来表示静态代理模式;
现在我是一个北漂的打工人,刚到北京,需要租一套房子,但是北京很大,我往往都是看得到房子看不到房东主人,为什么?
因为科技发达的今天,房东早就不自己出租房子的,而是交接房屋中介,那么找中介很简单,我直接再信息栏看传单就行了,无形中我们就完成了一个建议代理模式的构造
谁要租房子?我
谁可以帮我租到房子?房屋中介
谁真的有房子?房东主人
那么这三者种,谁是代理?肯定是房屋中介
我们思考一下为什么要找中介租房?中介有房东托付给他的房源,中介更加专业,可以带我看房,并且根据我的需求选房
换到正题上来,这就是为什么需要使用代理模式,中介有房东托付给他的房源:用设计模式来说就是代理类拥有被代理的属性和方法;而可以带我看房,并且根据我的需求选房:表示代理类会比真实类多更多的方法,和拓展业务,但是又不改变原来的业务代码,使得横向拓展业务

静态代理模式模拟
代理类需要的接口:
//中介的接口
//代理类需要实现的方法
public interface ProxyDemo {
//出租房子
void send();
//带租客看房子
void sayHouse();
//根据租客的需求分析房子
void analyseDemand();
}
代理类,实现上面的接口,增加拓展业务:
public class ProxyRole implements ProxyDemo{
//将被代理类组合进来
private Landlord ld;
//需要那个房东的房子直接注入进来
public void setLd(Landlord ld) {
this.ld = ld;
}
@Override
public void send() {
ld.send();
System.out.println("中介出租");
}
@Override
public void sayHouse() {
System.out.println("中介带你看房");
}
@Override
public void analyseDemand() {
System.out.println("中介根据需求分析,然后提出适合你的房子");
}
public void achieveSend(){
analyseDemand();
sayHouse();
send();
}
}
被代理的类,真实角色,把自己的业务组合给代理角色,保证自己的纯粹的业务:
//房东本人
public class Landlord {
//很纯粹,出租一个房子
public void send(){
System.out.println("出租房子(房东)");
}
}
租房人,向中介租房(主方法):
public static void main(String[] args) {
//我需要住房ld的房子
Landlord ld = new Landlord();
//将这个房东的信息交给中介,它带我去看房
ProxyRole pr = new ProxyRole();
pr.setLd(ld);
//开始租房
pr.achieveSend();
}
输出结果:

静态代理再理解
在我们的业务开发中,假设我们已经写好了一个对用户进行增删改查的类
它可以实现对用户进行操作,但是现在公司的需求新增了,要做日志功能,每个用户进行了什么操作都要在日志中展示,用于维护数据安全
我们可以把原始的类进行改造,在其中加上日志的信息,但是一个写好的类,你去更改它,如果改坏了怎么办,再说了在公司中去更改已经跑过的原代码本来就是大忌
这个时候就可以使用代理模式,在不改变原始代码的类的情况下,横向拓展出来一个日志功能
当然我们可以使用这种方式横向拓展出来很多的方法,日志功能只是一个简单的测试,但目的都是为了保证原始代码更加的纯粹
图解:

代码模拟
代理类需要实现的接口,也是代理需要拓展的功能:
//代理类需要实现的接口
public interface userImpInterface {
//新增日志方法
void printLog(String msg);
//新增事务方法
void useTransaction(String msg);
//原始业务
void addUser();
void delUser();
void updateUser();
void queryUser();
}
代理类,需要实现上述接口,并拓展方法:
//代理类
public class userImp implements userImpInterface{
//将被代理类组合进来
private userService us;
//注入被代理角色
public void setUs(userService us) {
this.us = us;
}
//打印日志
@Override
public void printLog(String msg) {
System.out.println("使用了"+msg+"方法");
}
//使用事务
@Override
public void useTransaction(String msg) {
System.out.println(msg+"方法使用了事务");
} @Override
public void addUser() {
printLog("addUser()");
us.addUser();
useTransaction("addUser()");
} @Override
public void delUser() {
printLog("addUser()");
us.delUser();
} @Override
public void updateUser() {
printLog("addUser()");
us.updateUser();
} @Override
public void queryUser() {
printLog("addUser()");
us.queryUser();
}
}
真实角色,被代理的类,拥有一些增删改查的初始方法,不改动,保持纯粹:
//真实类,提供对用户的操作
public class userService {
public void addUser(){
System.out.println("新增用户的方法");
}
public void delUser(){
System.out.println("删除用户的方法");
}
public void updateUser(){
System.out.println("更改用户的方法");
}
public void queryUser(){
System.out.println("查询用户的方法");
}
}
主方法:
public static void main(String[] args) {
//原始业务
userService us = new userService();
//组合到代理类中
userImp ui = new userImp();
ui.setUs(us);
//代理类实现了日志方法的拓展,可以直接调用代理的方法
ui.addUser();
}
结果展示:

静态代理模式的缺点:
很明显,每个真实角色都会有一个代理类进行拓展,这使得代码量会翻倍,即使一个代理类可以代理多个真实角色,也会有代码翻倍的情况
由于需要创建额外的代理类,因此会增加代码的复杂性和维护成本。
三.动态代理
上面学习了静态代理以后,我们知道了静态代理的缺点就是代码翻倍,每个真实角色都会使代理类的代码翻倍
究其原因呢?就是静态代理无法实现对真实角色进行统一管理,如果我们能让真实角色变成可自己加载到代理类,而调用那个代理类就加载那个真实角色就好了
这就是动态代理要做的,动态代理,动在可以自己加载真实角色,从而使代理类成为一个模板,需要使用那个真实角色,调用代理类模板加载就行了
百度百科:动态代理是一种设计模式,它允许你在运行时动态地创建代理对象,对原始对象进行封装,从而实现对原始对象的操作进行拦截、增强或修改。
动态代理的主要目的是提供一种更加灵活的方式来扩展对象的行为,而不是直接修改原始代码。
在Java中,动态代理主要通过java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler接口来实现
实现动态代理的方式有很多,官方推荐使用基于接口的JDK动态代理,也就是需要使用到Proxy方法和实现InvocationHandler接口
举个例子,是静态代理租房子的例子
代理类需要实现的接口:
//中介的接口
//代理类需要实现的方法
public interface ProxyDemo {
//出租房子
void send();
}
真实角色,被代理的类:
//房东本人
public class Landlord implements ProxyDemo {
//很纯粹,出租一个房子
public void send(){
System.out.println("出租房子(房东)");
}
}
第一步:实现InvocationHandler接口
实现这个接口就需要重写里面的invoke方法,这个方法是调用代理类的时候,自动就会执行的
invoke方法里可以调用其它的方法将其添加在真实角色方法的前面或者后面
//动态生成的代理类
public class MyInvocationHandler implements InvocationHandler {
//要被代理的接口
private ProxyDemo pr; public void setPr(ProxyDemo pr) {
this.pr = pr;
}
//处理代理实例,并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理的拓展方法
beforeMethod();
//这里执行的真实角色的方法,具体执行什么需要让调用者调度
Object invoke = method.invoke(pr, args);
//代理类的拓展方法
afterMethod();
return invoke;
}
//在执行真实角色前的方法
public void beforeMethod(){
System.out.println("执行方法前");
}
//在执行真实角色后的方法
public void afterMethod(){
System.out.println("执行方法后");
}
}
第二步:通过Proxy类生成动态代理类
//生成得到代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(), pr.getClass().getInterfaces(),this);
}
它也需要写到实现InvocationHandler接口的类下
Proxy.newProxyInstance()方法创建动态代理的实例
生成动态代理类需要的三个参数
- 第一个参数:用于加载代理类的 ClassLoader,当前类就是代理类,所以使用它的类加载器
- 第二个参数:需要代理的接口,面向接口代理,传入接口类
- 第三个参数:实现了InvocationHandler的类对象,一般就是当前类,所以传入this
第三步:测试
public static void main(String[] args) {
//真实角色
Landlord ld = new Landlord();
//构造生成代理类对象
MyInvocationHandler handler = new MyInvocationHandler();
handler.setPr(ld);
//生成代理类
ProxyDemo proxy = (ProxyDemo)handler.getProxy();
//调用此方法时就会执行动态代理的invoke方法
//会将send()方法传给invoke()方法
proxy.send();
}
总结:
灵活性:动态代理可以在运行时动态地创建代理对象,因此可以灵活地根据需要改变目标对象的行为。这使得动态代理非常适合用于实现一些需要在运行时动态改变行为的场景
扩展性:由于动态代理使用的是接口,因此它可以轻松地扩展到任何实现了接口的目标对象。这使得动态代理非常适合用于实现一些需要适配不同接口的场景
性能优化:虽然动态代理会增加一些额外的开销,但是通过合理的优化和缓存机制,可以有效地提高目标对象的性能
最大的优点就是解决了静态代理一次只能代理一个真实角色类的情况,动态代理一次代理的就是一个接口,所以它能一次代理一类业务,通过丢入不同的真实角色,生成不同的代理类,使得代码在运行中有非常高的灵活性
并且动态代理上面的步骤都是模板,记住模板可以自己手写动态代理不是难事
GOF23--23种设计模式(三)的更多相关文章
- GOF提出的23种设计模式是哪些 设计模式有创建形、行为形、结构形三种类别 常用的Javascript中常用设计模式的其中17种 详解设计模式六大原则
20151218mark 延伸扩展: -设计模式在很多语言PHP.JAVA.C#.C++.JS等都有各自的使用,但原理是相同的,比如JS常用的Javascript设计模式 -详解设计模式六大原则 设计 ...
- Java开发中的23种设计模式详解
[放弃了原文访问者模式的Demo,自己写了一个新使用场景的Demo,加上了自己的理解] [源码地址:https://github.com/leon66666/DesignPattern] 一.设计模式 ...
- Java开发中的23种设计模式详解(转)
设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了 ...
- Java开发中的23种设计模式(转)
设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了 ...
- Java 23种设计模式
转自: http://zz563143188.iteye.com/blog/1847029 ; i<count; i++){ list.add(new MailSender()); } } pu ...
- 从追MM谈Java的23种设计模式(转)
从追MM谈Java的23种设计模式 这个是从某个文章转载过来的.但是忘了原文链接.如果知道的,我追加一下. 1.FACTORY-追MM少不了请吃饭了,麦当劳的鸡翅和肯德基的鸡翅都是MM爱吃的东西 ...
- java 23种设计模式及具体例子 收藏有时间慢慢看
设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代 码可靠性. 毫无疑问,设计模式 ...
- JAVA:23种设计模式详解(转)
设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了 ...
- 从追MM谈Java的23种设计模式
从追MM谈Java的23种设计模式 1.FACTORY—追MM少不了请吃饭了,麦当劳的鸡翅和肯德基的鸡翅都是MM爱吃的东西,虽然口味有所不同,但不管你带MM去麦当劳或肯 德基,只管向服务员说“来四个鸡 ...
- 23种设计模式全解析 (java版本)
转自:http://blog.csdn.net/longyulu/article/details/9159589 其中PHP常用的五种设计模式分别为:工厂模式,单例模式,观察者模式,策略模式,命令模式 ...
随机推荐
- 背景图片随机API
在美化博客园的时候,遇到了一个问题:博客背景图片只支持一张图片,看到有道友说可以用API随机图片. 于是就有了这篇文章. 本文主要整理了一些随机图片API,希望对你有帮助. 岁月小筑 https:// ...
- CSP初赛错题集
初赛错题集 洛谷有题 NOIP 2018 T9 给定一个含N 个不相同数字的数组,在最坏情况下,找出其中最大或最小的数,至少需要N - 1 次比较操作.则最坏情况下,在该数组中同时找最大与最小的数至少 ...
- 一篇了解springboot3请求参数种类及接口测试
SpringBoot3数据请求: 原始数据请求: //原始方式 @RequestMapping("/simpleParam") public String simpleParam( ...
- 码编译安装nginx
1.解释源码安装nginx软件的预编译,编译以及安装,分别是在做什么,需要注意什么? 预编译(configure): ./configure 00prefix=/usr/local/nginx --u ...
- 2023江苏省领航杯(部分CRYPTO题目复现)
决赛 回文 1.题目信息 =QfzEDO4YDNlBzN4gzN0YGM1QzYyUGZ3QDZzgDM7V2Sn52bI52Q= 2.解题方法 base64解码,两种思路: 要么是去掉前面=号解码 ...
- Python 中多态性的示例和类的继承多态性
单词 "多态" 意味着 "多种形式",在编程中,它指的是具有相同名称的方法/函数/操作符,可以在许多不同的对象或类上执行. 函数多态性 一个示例是 Python ...
- NewStarCTF 2023 公开赛道 WEEK4|MISC 部分WP
R通大残 1.题目信息 R通大残,打了99,补! 2.解题方法 仔细分析题目,联想到隐写的R通道. 首先解释一下:R是储存红色的通道,通道里常见有R(红).G(绿).B(蓝)三个通道,如果关闭了R通道 ...
- QT(5)-QHeaderView
@ 目录 1 说明 2 函数 2.1 级联调整大小 2.2 默认对齐方式 2.3 count() 2.4 表头默认单元格大小 2.5 hiddenSectionCount() 2.6 分区显示和隐藏 ...
- 夯实JAVA基本之一 —— 泛型详解(1):基本使用(转)
一.引入1.泛型是什么首先告诉大家ArrayList就是泛型.那ArrayList能完成哪些想不到的功能呢?先看看下面这段代码:ArrayList<String> strList = ne ...
- STL deque容器
deque - 双向队列 1.队列的基本知识 队列的基本特性就是先进先出(FIFO),也就是第一个进去的元素第一个出来.即队列就是一个只允许在一端进行插入,在另一端进行删除操作的线性表.Queue接口 ...