设计模式の享元模板代理模式

embedded/2024/12/23 17:18:43/

文章目录


前言

  本篇是关于设计模式享元模式、模板模式、以及代理模式的学习笔记。


一、享元模式

  享元模式是一种结构型设计模式,目的是为了相似对象的复用,减少内存的消耗,在享元模式中,主要包含以下的角色:

  • Flyweight(抽象享元):定义享元对象的接口,规定了可以共享的内容。
  • ConcreteFlyweight(不可变对象):实现抽象享元接口,封装内部状态,确保可以被共享。
  • UnsharedConcreteFlyweight(可变对象):不需要共享的对象。
  • FlyweightFactory(享元工厂):管理和创建享元对象,确保可以复用已经存在的享元。
  • Client(客户端):使用享元对象,负责将外部状态传递给享元对象。

  下面通过一个案例说明一下,例如围棋,有黑白两种颜色的棋子,如果为每个棋子都创建一次对象,会占用很大的内存。如果利用享元模式进行改造,可以将下棋的行为看做是抽象享元,而棋子可以视为实现了抽象享元接口的不可变对象下棋的选手可以看作是可变对象

java">public interface PlayChess {/*** 下棋* @param player*/void playChess(Player player);}
java">/*** 棋子类*/
public class GochessPieces implements PlayChess{private String color;public GochessPieces() {}public GochessPieces(String color) {this.color = color;}@Overridepublic void playChess(Player player) {System.out.println(player + "落" + color);}
}
java">/*** 玩家*/
public class Player {private String name;public Player() {}public Player(String name) {this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "Player{" +"name='" + name + '\'' +'}';}
}
java">/*** 享元工厂*/
public class ChessFactory {private HashMap<String,PlayChess> map = new HashMap<>();public PlayChess getChess(String color){if (!map.containsKey(color)){map.put(color,new GochessPieces(color));}return (PlayChess)map.get(color);}public int poolSize(){return map.size();}
}

  模拟下棋的操作:

java">/*** 模拟下棋*/
public class Client {public static void main(String[] args) {ChessFactory chessFactory = new ChessFactory();PlayChess chess1 = chessFactory.getChess("白子");chess1.playChess(new Player("张三"));PlayChess chess2 = chessFactory.getChess("黑子");chess2.playChess(new Player("张三"));PlayChess chess3 = chessFactory.getChess("白子");chess3.playChess(new Player("李四"));PlayChess chess4 = chessFactory.getChess("黑子");chess4.playChess(new Player("李四"));PlayChess chess5 = chessFactory.getChess("白子");chess5.playChess(new Player("张三"));System.out.println("chessFactory.poolSize() = " + chessFactory.poolSize());}
}

Player{name=‘张三’}落白子
Player{name=‘张三’}落黑子
Player{name=‘李四’}落白子
Player{name=‘李四’}落黑子
Player{name=‘张三’}落白子
chessFactory.poolSize() = 2

  可以看出,虽然棋子被多次利用,但是在享元工厂中,最多只有两个棋子对象,即黑子白子,其关键点在于:

  • 共享对象:黑棋和白棋各只有一个实例,无需为每个棋子创建新对象。
  • 内外部状态分离:颜色作为内部状态是共享的;位置作为外部状态在调用时传入。
  • 减少内存占用:通过共享棋子对象,大幅减少了内存开销。

二、模板方法模式

  模板方法模式是一种行为设计模式,通过定义一个算法框架,允许子类在不改变算法结构的情况下重新定义算法的某些步骤。模板方法模式的主要目的是将算法的不变部分抽取到父类中,而将可变部分留给子类去实现,提高代码的复用性和灵活性,其中还涉及到钩子方法的使用。
  举一个生活中的案例,比如制作豆浆的过程,有相同的操作,也有针对不同类型的豆浆采取的特殊处理,用一个抽象的模板方法类,说明制作豆浆的步骤,以及对外提供制作的方法:

java">public abstract class CreateSoyaMilk {final void make(){prepare();if (isSoakBeans()){soakBeans();}grinding();boil();}/*** 钩子方法* @return*/boolean isSoakBeans(){return true;}/*** 准备材料* 不同的豆浆准备不同材料*/public abstract void prepare();/*** 浸泡豆子* 不同的豆浆浸泡不同的豆子*/public abstract void soakBeans();public void grinding(){System.out.println("打磨豆浆");}public void boil(){System.out.println("煮沸豆浆");}
}

  对于制作不同类型的豆浆,只需要继承模板方法类,然后根据自己的特点进行特殊处理即可:

java">public class RedBeanSoybeanMilk extends CreateSoyaMilk{@Overridepublic void prepare() {System.out.println("准备红豆");}@Overridepublic void soakBeans() {System.out.println("浸泡红豆");}
}

  如果黄豆豆浆不需要浸泡,则重写模板中的钩子方法

java">public class YellowBeanSoybeanMilk extends CreateSoyaMilk{@Overridepublic void prepare() {System.out.println("准备黄豆");}/*** 假设黄豆豆浆不需要浸泡*/@Overridepublic void soakBeans() {}@Overrideboolean isSoakBeans() {return false;}
}

  制作豆浆:

java">public class Client {public static void main(String[] args) {new RedBeanSoybeanMilk().make();System.out.println("**************************");new YellowBeanSoybeanMilk().make();}
}

准备红豆
浸泡红豆
打磨豆浆
煮沸豆浆


准备黄豆
打磨豆浆
煮沸豆浆

  模板方法模式也存在一定的局限性,因为是使用继承的体系结构,如果父类的修改影响较大,会导致所有子类需要同步更新。通常适用于有固定的算法结构,但子类的实现细节不同的场景。

三、代理模式

  代理模式是一种结构型设计模式,其核心目的是为了在使用目标对象的前后,对其进行拦截,增加一些统一的操作或是校验,是一种非常重要的模式,Spring AOP就是基于代理实现。
  代理模式可以分为静态代理动态代理,动态代理又可以细分为JDK动态代理Cglib动态代理。前者需要目标类实现接口,后者不需要,会在运行时动态生成目标类的子类。

3.1、静态代理

  首先演示一下什么是静态代理,静态代理要求目标类和代理类都要同时去实现接口:

java">public interface ITeacher {void teach();
}
java">public class TeacherDao implements ITeacher{@Overridepublic void teach() {System.out.println("开始上课。。。。");}
}
java">/*** 静态代理案例* 缺点,如果接口中要增加方法,被代理类和代理类都需要同步增加*/
public class TeacherDaoProxy implements ITeacher{private ITeacher teacher;public TeacherDaoProxy(ITeacher teacher){this.teacher = teacher;}@Overridepublic void teach() {System.out.println("课前准备。。。。");teacher.teach();System.out.println("下课。。。。。");}
}

课前准备。。。。
开始上课。。。。
下课。。。。。

3.2、JDK动态代理

  JDK动态代理的实现,主要依靠java.lang.reflect.Proxy包下的newProxyInstance方法,该方法会在运行时动态的生成代理类,详见:从源码角度分析JDK动态代理:

java">public interface ITeacher {void teach();
}
java">public class TeacherDao implements ITeacher {@Overridepublic void teach() {System.out.println("开始上课。。。。");}
}
java">public class JdkProxyFactory {/*** 被代理的目标类*/private Object target;public JdkProxyFactory(Object target) {this.target = target;}public Object getProxyInstance(){return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),(proxy, method, args) -> {System.out.println("课前准备");//通过反射调用目标方法Object o = method.invoke(target, args);System.out.println("下课");return o;});}
}
java">public class Client {public static void main(String[] args) {JdkProxyFactory jdkProxyFactory = new JdkProxyFactory(new TeacherDao());ITeacher proxyInstance = (ITeacher) jdkProxyFactory.getProxyInstance();proxyInstance.teach();}
}

3.3、Cglib动态代理

  JDK动态代理的实现,主要依靠org.springframework.cglib.proxy.MethodInterceptor包下的intercept方法,和JDK动态代理不同的是,不要求目标类实现接口,而是直接通过字节码技术生成目标类的子类

java">public class TeacherDao{public void teach() {System.out.println("开始上课。。。。");}
}
java">/*** cglib动态代理,不需要目标类实现接口* 运行时使用字节码技术动态增强*/
public class CglibProxyFactory implements MethodInterceptor {Object target;public CglibProxyFactory(Object target) {this.target = target;}public Object getProxyInstance(){Enhancer enhancer = new Enhancer();enhancer.setCallback(this);enhancer.setSuperclass(target.getClass());return enhancer.create();}@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("课前准备");Object invoke = method.invoke(target, objects);System.out.println("下课");return invoke;}
}
java">public class Client {public static void main(String[] args) {CglibProxyFactory cglibProxyFactory = new CglibProxyFactory(new TeacherDao());TeacherDao proxyInstance = (TeacherDao) cglibProxyFactory.getProxyInstance();proxyInstance.teach();}
}

  但是目标类,或方法如果被privatefinal关键字修饰,以及目标方法被static修饰,则无法进行代理,因为上述修饰符的方法无法被继承。

3.4、小结

  动态代理和静态代理的区别,主要体现在:

  • 静态代理是在编译期间由开发者显式编写代理类或通过工具生成,代理类在运行时已经存在:
    在这里插入图片描述
  • 动态代理在运行时动态生成代理类,代理类的字节码不会提前编写,而是通过反射或字节码操作框架生成。(使用JDK或第三方字节码库提供的API)
    运行时,JDK 动态代理会生成类似下面的代理类:com.light.proxy.jdkproxy.$Proxy0,其不会存在于target目录下。
      在Spring AOP中,也会根据是否实现了接口,去选择不同的动态代理。


http://www.ppmy.cn/embedded/148121.html

相关文章

druid与pgsql结合踩坑记

最近项目里面突然出现一个怪问题&#xff0c;数据库是pgsql&#xff0c;jdbc连接池是alibaba开源的druid&#xff0c;idea里面直接启动没问题&#xff0c;打完包放在centos上和windows上cmd窗口都能直接用java -jar命令启动&#xff0c;但是放到国产信创系统上就是报错&#xf…

STM32F407 | Embedded IDE01 - vscode搭建Embedded IDE开发环境(支持JLINK、STLINK、DAPLINK)

导言 Embedded IDE官网:https://em-ide.com/docs/intro 我猜肯定有部分人使用SI Keil开发STM32项目&#xff0c;也有vscode Keil开发STM32程序。SI或vscode编写代码&#xff0c;然后切换Keil编译、下载、调试程序。有一段时间&#xff0c;我也是这么干的。但是&#xff0c;程…

分布式系统架构:服务容错

1.为什么需要容错 分布式系统的本质是不可靠的&#xff0c;一个大的服务集群中&#xff0c;程序可能崩溃、节点可能宕机、网络可能中断&#xff0c;这些“意外情况”其实全部都在“意料之中”。故障的发生是必然的&#xff0c;所以需要设计一套健壮的容错机制来应对这些问题。 …

CSS系列(30)-- 逻辑属性详解

前端技术探索系列&#xff1a;CSS 逻辑属性详解 &#x1f310; 致读者&#xff1a;探索国际化布局的艺术 &#x1f44b; 前端开发者们&#xff0c; 今天我们将深入探讨 CSS 逻辑属性&#xff0c;这个强大的国际化布局特性。 基础概念 &#x1f680; 逻辑属性映射 /* 物理…

MySQL 实战:小型项目中的数据库应用(一)

MySQL 简介与小型项目适配性分析 MySQL 是一个开源的关系型数据库管理系统&#xff0c;由瑞典 MySQL AB 公司开发&#xff0c;现属于 Oracle 公司。它在 Web 应用方面被广泛使用&#xff0c;也是一种关联数据库管理系统&#xff0c;能将数据保存在不同的表中&#xff0c;以此增…

黑客术语3

19、免杀 : 就是通过加壳、加密、修改特征码、加花指令等等技术来修改程序&#xff0c; 使其逃过杀毒软件的查杀。 20 、加壳 : 就是利用特殊的算法&#xff0c;将 EXE 可执行程序或者 DLL 动态连接库文件的 编码进行改变&#xff08;比如实现压缩、加密&#xff09;&a…

UniApp 应用心得与总结(Android)

UniApp属于跨平台的应用开发框架&#xff0c;在实际的业务应用中给予了开发者友好的体验。其优点主要体现在完善的开发文档&#xff0c;强大的兼容性与参与人数众多丰富的社区资源。经过一段时间的业务运用与体验&#xff0c;我实现了从 零到一的 N 的运用与开发。这篇文章主要…

git退掉远程仓库里的某个修改和记录

文章目录 步骤 1: 找到目标提交的上一个提交步骤 2: 使用 git reset 回退本地分支步骤 3: 强制推送到远程仓库步骤 4: 验证注意事项 如果你想要撤销远程仓库的这次合并提交&#xff0c;并且删除记录&#xff0c;你可以按照以下步骤进行操作。注意&#xff0c;这个操作会修改历史…