设计模式之美笔记10 柔情只为你懂 2022-11-28 10:36 149阅读 0赞 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 ### 文章目录 ### * * 序言 * 代理模式 * 桥接模式 * * 1. 原理解析 * 2. 桥接模式的应用 * 装饰器模式 * * java IO类的奇怪用法 * 基于继承的设计方案 * 基于装饰器模式的设计方案 * 适配器模式 * * 适配器模式应用场景 * * 1. 封装有缺陷的接口设计 * 2. 统一多个类的接口设计 * 3. 替换依赖的外部系统 * 4. 兼容老版本接口 * 5. 适配不同格式的数据 * 适配器模式在java日志中的应用 * 代理、桥接、装饰器、适配器的区别 ## 序言 ## 之前是创建型模式,主要解决对象的创建问题,封装复杂的创建过程,解耦对象的创建代码和使用代码。其中单例模式创建全局唯一的对象;工厂模式创建不同但是相关类型的对象(继承同一父类或接口的一组子类),由给定的参数决定创建哪种类型的对象;建造者模式创建复杂对象,通过设置不同的可选参数,定制化的创建不同的对象;原型模式针对创建成本较大的对象,利用对已有对象进行复制的方式创建,达到节省创建时间的目的。 之后,学习另一种类型的设计模式:结构型模式。主要总结了一些类或对象组合在一起的经典结构,包括:代理模式、桥接模式、装饰器模式、适配器模式、门面模式、组合模式、享元模式。 ## 代理模式 ## proxy design pattern,不改变原始类(或叫被代理类)代码的情况下,通过引入代理类给原始类附加功能。分静态代理和动态代理。 一种是和被代理对象实现相同的接口。一种是字节码。 动态代理之前也了解过。不赘述。 ## 桥接模式 ## ### 1. 原理解析 ### 桥接模式,也叫做桥梁模式,bridge design pattern。有两种不同的理解。 GoF的《设计模式》的定义:Decouple an astraction from its implementation so that the two can vary independently。将抽象和实现解耦,让他们可以独立的变化。 另一种理解:一个类存在两个(或多个)独立变化的维度,通过组合的方式,让这两个(或多个)维度可以独立进行扩展。也就是组合优于继承。 以JDBC为例解释GoF的定义。JDBC驱动是桥接模式的经典应用。具体代码参看之前整理的[数据库][Link 1]部分,如果mysql换成Oracle,只需要将`com.mysql.jdbc.Driver` 替换为`oracle.jdbc.driver.OracleDriver` 即可。当然也可以将需要加载的Driver类写到配置文件中,程序启动时,自动加载配置。 具体如何进行数据库切换的?看`com.mysql.jdbc.Driver` 的代码: public class Driver extends NonRegisteringDriver implements java.sql.Driver { static { try { java.sql.DriverManager.registerDriver(new Driver()); }catch (SQLException e){ throw new RuntimeException("can't register driver!"); } } // construct a new driver and register it with DriverManager public Driver() throws SQLException{ // Require for Class.forName().newInstance() } } 执行`Class.forName("com.mysql.jdbc.Driver")` 时,实际做两件事。一是要求JVM查找并加载指定的Driver类,第二件事是执行该类的静态代码,也就是将Mysql Driver注册到DriverManager中。 那DriverManager类是干什么的?具体代码如下。当把具体的Driver实现类注册到DriverManager后,后续所有对jdbc接口的调用,都会委派到对具体的Driver实现类来执行,而Driver实现类都实现相同的接口,因此可以灵活切换Driver。 public class DriverManager { private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>(); //... static { loadInitialDrivers(); println("JDBC DriverManager initialized"); } //... public static synchronized void registerDriver(java.sql.Driver driver, DriverAction da) throws SQLException { if(driver != null) { registeredDrivers.addIfAbsent(new DriverInfo(driver, da)); } else { throw new NullPointerException(); } } public static Connection getConnection(String url,String user, String password) throws SQLException { java.util.Properties info = new java.util.Properties(); if (user != null) { info.put("user", user); } if (password != null) { info.put("password", password); } return (getConnection(url, info, Reflection.getCallerClass())); } //... } 桥接模式的定义是“将抽象和实现解耦,让它们可以独立变化”。在jdbc中,jdbc本身就相当于抽象,也即是跟具体的数据库无关的、被抽象出来的一套“类库”。具体的Driver相当于“实现”,也就是跟具体数据库相关的一套“类库”。jdbc和driver独立开发,通过对象之间的组合关系,组装到一起。jdbc的所有逻辑操作,最终都委托给Driver执行。 ### 2. 桥接模式的应用 ### 之前有过一个API接口监控告警的例子:根据不同的告警规则,触发不同类型的告警。告警支持多种通知渠道,包括:邮件、微信、短信、自动语音电话。通知的紧急程度有多种类型:SERVER(严重)、URGENCY(紧急)、NORMAL(普通)、TRIVIAL(无关紧要)。不同的紧急程度对应不同的通知渠道,如严重级别的消息通过自动语音电话告知相关人员。 当时的代码实现中,关于发送告警信息的部分,只给出粗略的设计 public class ErrorAlertHandler extends AlertHandler { public ErrorAlertHandler(AlertRule rule,Notification notification){ super(rule,notification); } @Override public void check(ApiStatInfo apiStatInfo){ if (apiStatInfo.getErrorCount() > rule.getMatchedRule(apiStatInfo.getApi())){ notification.notify(NotificationEmergencyLevel.SERVER,"..."); } } } Notification类的代码实现有个最明显的问题,就是很多if-else分支逻辑,每个if-else分支中的代码逻辑都比较复杂,发送通知的所有逻辑扎堆在Notification类中,需要将不同渠道的发送逻辑剥离出来,形成独立的消息发送类MsgSender,其中Notification类相当于抽象,MsgSender相当于实现,两者可以独立开发,通过组合关系(也就是桥梁)任意组合在一起。重构后 public interface MsgSender { void send(String message); } public class TelephoneMsgSender implements MsgSender { private List<String> telephones; public TelephoneMsgSender(List<String> telephones) { this.telephones = telephones; } @Override public void send(String message) { //... } } public class EmailMsgSender implements MsgSender{ //...和TelephoneMsgSender类似... } public class WechatMsgSender implements MsgSender{ //...和TelephoneMsgSender类似... } public abstract class Notification { protected MsgSender msgSender; public Notification(MsgSender msgSender) { this.msgSender = msgSender; } public abstract void notify(String message); } public class ServereNotification extends Notification { public ServereNotification(MsgSender msgSender) { super(msgSender); } @Override public void notify(String message) { msgSender.send(message); } } public class UrgencyNotification extends Notification { //...和ServereNotification类似... } public class NormalNotification extends Notification { //...和ServereNotification类似... } public class TrivialNotification extends Notification { //...和ServereNotification类似... } 当然,Notification类的三个成员变量通过set方法设置,存在一个明显的问题,就是emailAddresses、telephones、wechatIds的数据可能在Notification类外部被修改,最好用构建者模式。 ## 装饰器模式 ## 主要解决继承过于复杂的问题,通过组合替代继承,主要作用是给原始类添加增强功能。有个特点,可以对原始类嵌套使用多个装饰器。以java的io类为例,学习装饰器模式。 ### java IO类的奇怪用法 ### java的IO类库非常庞大和复杂,有几十个类,负责IO数据的读写。如果分类,可从下面两个维度分为四类 <table> <thead> <tr> <th></th> <th>字节流</th> <th>字符流</th> </tr> </thead> <tbody> <tr> <td>输入流</td> <td>InputStream</td> <td>Reader</td> </tr> <tr> <td>输出流</td> <td>OutputStream</td> <td>Writer</td> </tr> </tbody> </table> 针对不同的读取和写入场景,又在此基础上扩展很多子类。 以下面代码为例,打开test.txt文件,读取数据,其中,InputStream是个抽象类,FileInputStream是专门读取文件流的子类,BufferedInputStream是个支持带缓存功能的数据读取类,可提高数据读取的效率。 InputStream in = new FileInputStream("/xx/xx/test.txt"); InputStream bin = new BufferedInputStream(in); byte[] data = new byte[128]; while(bin.read(data)!=-1){ //... } 初看代码,会觉得java的IO的用法较为麻烦,需要先创建一个FileInputStream对象,然后传递给BufferedInputStream对象来使用。那为啥不设计一个继承FileInputStream并且支持缓存的BufferedFileInputStream类,就可以直接创建BufferedFileInputStream类对象,打开文件读取数据,就简单的多了。 ### 基于继承的设计方案 ### 如果InputStream只有一个子类FileInputStream,在FileInputStream基础上,设计一个孙子类BufferedFileInputStream还可以接受,但实际上,继承InputStream的子类很多,需要给每个子类派生支持缓存读取的子类。除了缓存,还需要对功能进行其他方面的增强,如DataInputStream,支持按照基本数据类型(int、boolean、long等)读取数据。 这种情况下,如果继续按照继承方式,会派生太多的孙子类。而这仅仅是附加了两个增强功能,如果更多增强功能,导致组合爆炸。 ### 基于装饰器模式的设计方案 ### 组合替代继承,只看简化的代码: public abstract class InputStream{ //... public int read(byte b[]) throw IOException{ return read(b,0,b.length); } //... } public class BufferedInputStream extends InputStream{ protected volatile InputStream in; protected BufferedInputStream(InputStream in){ this.in = in; } //...实现基于缓存的读取数据接口... } public class DataInputStream extends InputStream{ protected volatile InputStream in; protected DataInputStream(InputStream in){ this.in = in; } //...实现读取基本类型数据接口... } 那装饰器模式是否就是简单的组合替代继承?当然不是,从java的IO类库设计看,相较于简单的组合,装饰器模式还有两个较为特殊的地方: 第一个是:装饰器类和原始类继承同样的父类,这样就可以对原始类“嵌套”多个装饰器类。如下面,对FileInputStream嵌套两个装饰器类:BufferedInputStream和DataInputStream,让它既支持缓存读取,又支持按照基本数据类型读取数据。 InputStream in = new FileInputStream("/xx/xx/test.txt"); InputStream bin = new BufferedInputStream(in); DataInputStream din = new DataInputStream(bin); int data = din.readInt(); 第二个是:**装饰器类是对功能的增强**,也是装饰器模式应用场景的一个重要特点。和代理模式相比,代理模式中,代理类附加的是跟原始类无关的功能,而装饰器模式中,装饰器类附加的是跟原始类相关的增强功能。 //代理模式的代码结构(下面的接口可替换为抽象类) public interface IA{ void f(); } public class A implements IA{ public void f(){ //...} } public class AProxy implements IA{ private IA a; public AProxy(IA a){ this.a = a; } public void f(){ //新添加的代理逻辑 a.f(); //新添加的代理逻辑 } } //装饰器模式的代码结构(下面的接口可替换为抽象类) public interface IA{ void f(); } public class A implements IA{ public void f(){ //...} } public class ADecorator implements IA{ private IA a; public ADecorator(IA a){ this.a = a; } public void f(){ //功能增强代码 a.f(); //功能增强代码 } } 实际看JDK的源码,BufferedInputStream、DataInputStream并非继承自InputStream,而是另一个叫FileInputStream的类,为啥要引入这个类呢? 再看BufferedInputStream的代码,InputStream是个抽象类而非接口,而且大部分方法(如read())都有默认实现,按理说,只要在BufferedInputStream重新实现需要增加缓存功能的方法即可,其他方法继承InputStream的默认实现。实际并不行。 对于即便不需要增加缓存功能的方法来说,BufferedInputStream还是必须把它重新实现一遍,简单包裹对InputStream对象的方法调用。具体代码如下。如果不重新实现,BufferedInputStream类无法将最终读取数据的任务,委托给传递进来的InputStream对象来完成。 public class BufferedInputStream extends InputStream{ protected volatile InputStream in; protected BufferedInputStream(InputStream in){ this.in = in; } //f()方法不需要增强,只是重新调用下InputStream in对象的f() public void f(){ in.f(); } } 实际上,DataInputStream也存在同样的问题,为避免代码重复,java IO抽象出一个装饰器父类FileInputStream。InputStream的所有的装饰器类都继承自这个装饰器父类。这样,装饰器只要实现需要增强的方法即可,其他方法继承装饰器父类的默认实现。 public class FileInputStream extends InputStream{ protected volatile InputStream in; protected FileInputStream(InputStream in){ this.in = in; } public int read() throw IOException{ return in.read(); } //... } ## 适配器模式 ## 适配器模式Adapter Design Pattern,用来做适配,将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。例子就是USB接口。 有两种实现方式:类适配器和对象适配器。其中,类适配器使用继承关系实现,对象适配器使用组合关系实现。具体代码如下。其中ITarget表示要转化成的接口定义。Adaptee是一组不兼容ITarget接口定义的接口,Adaptor将Adaptee转化为一组符合ITarget接口定义的接口。 //类适配器:基于继承 public interface ITarget{ void f1(); void f2(); void fc(); } public class Adaptee{ public void fa(){ //...} public void fb(){ //...} public void fc(){ //...} } public class Apdator extends Adaptee implements ITarget{ public void f1(){ super.fa(); } public void f2(){ //...重新实现f2().. } // fc()不需要实现,直接继承自Adaptee,这是跟对象适配器最大的不同点 } //对象适配器:基于组合 public interface ITarget{ void f1(); void f2(); void fc(); } public class Adaptee{ public void fa(){ //...} public void fb(){ //...} public void fc(){ //...} } public class Adaptor implements ITarget{ private Adaptee adaptee; public Adaptor(Adaptee adaptee){ this.adaptee = adaptee; } public void f1(){ adaptee.fa();//委托给Adatee } public void f2(){ //...重新实现f2()... } public void fc(){ adaptee.fc(); } } 针对这两种实现方式,实际开发使用哪种的标准:一个是Adaptee接口的个数,一个是Adaptee和ITarget的契合程度。 * 如果Adaptee接口不多,都可以 * 如果Adaptee接口很多,而且Adaptee和ITarget接口定义大部分相同,推荐使用类适配器,因为Adaptor复用父类Adaptee的接口,比起对象适配器的实现方式,Adaptor的代码量少一些 * 如果Adaptee接口很多,而且Adaptee和ITarget接口定义大部分都不相同,推荐使用对象适配器,因为组合结构相对于继承更加灵活 ### 适配器模式应用场景 ### 一般,适配器模式可看做“补偿模式”,用来补救设计上的缺陷,算是无奈之举。如果设计初期,就能协调规避接口不兼容的问题,就没必要用了。哪些场景会出现接口不兼容? #### 1. 封装有缺陷的接口设计 #### 假如依赖的外部系统在接口设计方面有缺陷(如包含大量静态方法),引入后会影响我们自身代码的可测试性。为隔离设计上的缺陷,希望对外部系统提供的接口二次封装,抽象出更好的接口设计,可采用适配器模式。 public class CD{ //这个类来自外部SDK,无权修改它的代码 //... public static void staticFunction1(){ //...} public void uglyNamingFunction2(){ //...} public void toomanyParamsFunction3(int a, int b,...){ //...} public void lowPerformanceFunction4(){ //...} } //使用适配器模式重构 public interface ITarget{ void function1(); void function2(); void function3(ParamsWrapperDefinition paramsWrapper); void function4(); //... } //注意:适配器类的命名不一定非要末尾带Apdator public class CDAdaptor extends CD implements ITarget{ //... public void function1(){ super.staticFunction1(); } public void function2(){ super.uglyNamingFunction2(); } public void function3(ParamsWrapperDefinition paramsWrapper){ super.toomanyParamsFunction3(paramsWrapper.getA(),...); } public void function4(){ //...reimplement it... } } #### 2. 统一多个类的接口设计 #### 某个功能的实现依赖多个外部系统(或者说类),通过适配器模式,将他们的接口适配为统一的接口定义,然后使用多态的特性复用代码逻辑。 假设系统要对用户输入的文本内容做敏感词过滤,为提高过滤的召回率,引入多款第三方敏感词过滤系统,依次对用户输入的内容过滤,过滤掉尽可能多的敏感词,但每个系统提供的过滤接口都不同。意味着无法复用一套逻辑来调用各个系统。此时可用适配器模式,将所有系统的接口适配为统一的接口定义,这样可复用调用敏感词过滤的代码。 public class ASensitiveWordsFilter { //A敏感词过滤系统的接口 //text是原始文本,方法输出用***替换敏感词之后的文本 public String filterSexyWords(String text){ //... } public String filterPoliticalWords(String text){ //... } } public class BSensitiveWordsFilter { //B敏感词过滤系统提供的接口 public String filter(String text){ //... } } public class CSensitiveWordsFilter { //C敏感词过滤系统提供的接口 public String filter(String text,String mask){ //... } } public class RiskManagement { //未适配之前的代码:可测试性、扩展性不好 private ASensitiveWordsFilter aFilter = new ASensitiveWordsFilter(); private BSensitiveWordsFilter bFilter = new BSensitiveWordsFilter(); private CSensitiveWordsFilter cFilter = new CSensitiveWordsFilter(); public String filterSensitiveWords(String text){ String maskedText = aFilter.filterSexyWords(text); maskedText = aFilter.filterPoliticalWords(maskedText); maskedText = bFilter.filter(maskedText); maskedText = cFilter.filter(maskedText,"***"); return maskedText; } } // 改造 public interface ISensitiveWordsFilter { // 统一接口定义 String filter(String text); } public class ASensitiveWordsFilterAdaptor implements ISensitiveWordsFilter { private ASensitiveWordsFilter aFilter; @Override public String filter(String text) { String maskedText = aFilter.filterSexyWords(text); maskedText = aFilter.filterPoliticalWords(maskedText); return maskedText; } } //... 省略BSensitiveWordsFilterAdaptor CSensitiveWordsFilterAdaptor... //扩展性更好,更符合开闭原则,如果添加新的敏感词过滤系统,这个类完全不用改动 public class RiskManagement { private List<ISensitiveWordsFilter> filters = new ArrayList<>(); public void addSensitiveWordsFilter(ISensitiveWordsFilter filter){ filters.add(filter); } public String filterSensitiveWords(String text){ String maskedText = text; for (ISensitiveWordsFilter filter: filters){ maskedText = filter.filter(maskedText); } return maskedText; } } #### 3. 替换依赖的外部系统 #### 当我们把项目中依赖的一个外部系统替换为另一个外部系统,利用适配器模式,可减少代码的改动 //外部系统A public interface IA{ //... void fa(); } public class A implements IA{ //... public void fa(){ //...} } //在项目中,外部系统A的使用范例 public class Demo{ private IA a; public Demo(IA a){ this.a = a; } //... } Demo d = new Demo(new A()); //将外部系统A替换为外部系统B public class BAdaptor implements IA{ private B b; public BAdaptor(B b){ this.b = b; } public void fa(){ //... b.fb(); } } //借助BAdaptor,Demo的代码中,调用IA接口的地方都无需改动,只需将BAdaptor注入到Demo即可 Demo d = new Demo(new BAdaptor(new B())); #### 4. 兼容老版本接口 #### 做版本升级时,对一些废弃的接口,不能直接删除,而是暂时保留,标注为deprecated,并将内部实现逻辑委托为新的接口实现。如JDK1.0包含一个遍历集合容器的类Enumeration,JDK2.0考虑重构该类,改名为Iterator,并优化代码。为兼容老代码,可暂时保留Enumeration,并将其实现替换为直接调用Iterator public class Collections{ public static Enumeration enumeration(final Collection c){ return new Enumeration(){ Iterator i = c.iterator(); public boolean hasMoreElements(){ return i.hasNext(); } public Object nextElement(){ return i.next(); } } } } #### 5. 适配不同格式的数据 #### 适配器除了用于接口的适配,还能用在不同格式的数据之间的适配。如把不同征信系统拉取的不同格式的征信数据,统一为相同的格式,以方便存储和使用。此外,java的Arrays.asList()也可看做数据适配器,将数组类型的数据转化为集合容器类型。 ### 适配器模式在java日志中的应用 ### java有很多日志框架,如log4j、logback,以及JDK提供的JUL(java.util.logging)和Apache的JCL(Jakarta Commons Logging)等。 大部分日志框架提供相似的功能,但并未实现统一的接口,不像jdbc,一开始就制定数据库操作的接口规范。 如果项目的某个组件使用log4j,而项目本身用的是logback,项目相当于有了两套日志打印框架,而每种日志框架都有自己特有的配置方式。管理很复杂,因此,需要统一日志打印框架。 Slf4j相当于jdbc,提供了一套打印日志的统一接口规范,不过只定义了接口,并未提供具体的实现,要配合其他日志框架使用。此外,slf4j也晚于这些日志框架,因此,不仅提供统一的接口定义,还提供了针对不同日志框架的适配器,对不同日志框架的接口二次封装,适配为统一的slf4j接口定义。 //slf4j统一的接口定义 public interface Logger{ public boolean isTraceEnabled(); public void trace(String msg); public void trace(String format,Object arg); public void trace(String format,Object arg1,Object arg2); public void trace(String msg,Throwable t); public boolean isDebugEnabled(); public void debug(String msg); public void debug(String format,Object arg); public void debug(String format,Object arg1,Object arg2); public void debug(String format,Object[] argArray); public void debug(String msg,Throwable t); //...省略info warn error等接口 } //log4j日志框架的适配器 //Log4jLoggerAdapter实现了LocationAwareLogger接口,而该接口继承自Logger接口 public final class Log4jLoggerAdapter extends MarkerIgnoringBase implents LocationAwareLogger,Serializable{ final transient org.apache.log4j.Logger logger;//log4j public boolean isDebugEnabled(){ return logger.isDebugEnabled(); } public void debug(String msg){ logger.log(FQCN,Level.DEBUG,msg,null); } public void debug(String format,Object arg){ if(logger.isDebugEnabled()){ FormattingTuple ft = MessageFormatter.format(format,arg); logger.log(FQCN,Level.DEBUG,ft.getMessage(),ft.getThrowable()); } } public void debug(String format,Object arg1,Object arg2){ if(logger.isDebugEnabled()){ FormattingTuple ft = MessageFormatter.format(format,arg1,arg2); logger.log(FQCN,Level.DEBUG,ft.getMessage(),ft.getThrowable()); } } public void debug(String format,Object[] argArray){ if(logger.isDebugEnabled()){ FormattingTuple ft = MessageFormatter.format(format,argArray); logger.log(FQCN,Level.DEBUG,ft.getMessage(),ft.getThrowable()); } } public void debug(String msg,Throwable t){ logger.log(FQCN,Level.DEBUG,msg,t); } //...省略一堆接口的实现... } 所以,开发业务系统或开发框架、组件时,统一使用slf4j提供的接口编写打印日志的代码,具体使用哪种日志框架实现(log4j、logback…),可动态指定(使用java的SPI技术),只需要将相应的SDK导入项目即可。 如果有些老的项目没有用Slf4j,而是直接用JCL打印,想替换为log4j,怎么办?slf4j还提供反向适配器,也就是从slf4j到其他日志框架的适配,先将JCL切换为Slf4j,再将Slf4j切换为log4j。 ### 代理、桥接、装饰器、适配器的区别 ### 都可称为wrapper模式,也就是通过wrapper类二次封装原始类。区别: * 代理模式:在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式的最大不同 * 桥接模式:目的是将接口部分和实现部分分离,从而让它们可较为容易、相对独立的加以改变 * 装饰器模式:在不改变原始类接口的情况下,对原始类功能增强,并支持多个装饰器的嵌套使用 * 适配器模式:事后的补救策略,提供跟原始类不同的接口,而代理类、装饰器模式提供的是跟原始类相同的接口 [Link 1]: https://blog.csdn.net/wjl31802/article/details/88837052
相关 设计模式之美笔记16 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 解释器模式 解释器模式的原理和实现 深藏阁楼爱情的钟/ 2022年12月01日 11:53/ 0 赞/ 136 阅读
相关 设计模式之美笔记15 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 访问者模式 访问者模式的诞生 我就是我/ 2022年12月01日 05:16/ 0 赞/ 143 阅读
相关 设计模式之美笔记14 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 状态模式 背景 什么 水深无声/ 2022年11月30日 15:51/ 0 赞/ 146 阅读
相关 设计模式之美笔记13 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 策略模式 策略模式的原理和实现 忘是亡心i/ 2022年11月30日 12:27/ 0 赞/ 145 阅读
相关 设计模式之美笔记12 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 观察者模式 原理及应用场景剖析 深碍√TFBOYSˉ_/ 2022年11月30日 04:18/ 0 赞/ 174 阅读
相关 设计模式之美笔记11 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 门面模式 门面模式的原理和实现 ゞ 浴缸里的玫瑰/ 2022年11月28日 13:41/ 0 赞/ 156 阅读
相关 设计模式之美笔记10 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 序言 代理模式 桥接模式 柔情只为你懂/ 2022年11月28日 10:36/ 0 赞/ 150 阅读
相关 设计模式之美笔记9 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 工厂模式 1. 简单工厂 待我称王封你为后i/ 2022年11月28日 00:41/ 0 赞/ 148 阅读
相关 设计模式之美笔记8 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 单例模式 1. 为什么要使用单例 柔光的暖阳◎/ 2022年11月26日 07:52/ 0 赞/ 154 阅读
相关 设计模式之美笔记7 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 实战1:id生成器的重构 1. 需求背景 女爷i/ 2022年11月25日 13:19/ 0 赞/ 187 阅读
还没有评论,来说两句吧...