设计模式之美笔记16 深藏阁楼爱情的钟 2022-12-01 11:53 135阅读 0赞 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 ### 文章目录 ### * * 解释器模式 * * 解释器模式的原理和实现 * 解释器模式实战举例 * 中介模式 * * 中介模式的原理和实现 * 中介模式vs观察者模式 * 设计模式的学习路径 * * 1. 建立完善的知识体系 * 2. 建立代码质量意识 * 3. 主动学习而非被动学习 * 4. 多读几遍更有收获 * 5. 把代码写到极致 ## 解释器模式 ## 用来描述如何构建一个简单的“语言”解释器,更加小众,用在编译器、规则引擎、正则表达式中。 ### 解释器模式的原理和实现 ### 解释器模式,interpreter design pattern,定义:interpreter pattern is used to define a gramatical representation for a language and provides an interpreter to deal with this grammar. 解释器模式为某个语言定义它的语法(或者叫文法)表示,并定义一个解释器用来处理这个语法。 这里的语言,是指广义上的能承载信息的载体,如中文、盲文、摩斯密码等。要了解语言表达的信息,必须定义相应的语法规则。这样,书写者根据语法规则书写“句子”(专业说法“表达式”),阅读者根据语法规则阅读“句子”,这样才能做到信息的正确传递。而解释器模式,就是用来实现根据语法规则解读“句子”的解释器。 举个生活上的例子,类比中英文翻译,把英文翻译为中文有一定的规则,规则就是定义中的“语法”,开发一个类似Google translate的翻译器,能根据语法规则,将输入的中文翻译为英文,这个翻译器就是解释器模式定义中的“解释器”。 再举个编程的例子,假设我们定义了一个新的加减乘除计算“语言”,语法规则如下: * 运算符只包含加减乘除,且没有优先级的概念 * 表达式(也即是前面的“句子”)中,先写数字,后写运算符,空格隔开; * 按先后顺序,取出两个数字和一个运算符计算结果,结果重新放入数字的最头部位置,循环上述过程,直到只剩下一个数字,这个数字就是表达式最终的计算结果。 举例解释上述语法规则,如“8 3 2 4 - + \*”,先取出"8 3"和"-“运算符,计算得到5,表达式变成"5 2 4 + \*”,之后再取出"5 2"和"+“运算符,计算得到7,表达式变成"7 4 \*”,最后取出"7 4"和"\*" 运算符,最后得到的结果为28。 再用代码实现,如下: public class ExpressionInterpreter { private Deque<Long> numbers = new LinkedList<>(); public long interpret(String expression){ String[] elements = expression.split(" "); int length = elements.length; for (int i = 0; i < (length+1)/2;i++){ numbers.addLast(Long.parseLong(elements[i])); } for (int i = (length+1)/2; i < length; i++){ String operator = elements[i]; boolean isValid = "+".equals(operator) || "-".equals(operator) || "*".equals(operator) || "/".equals(operator); if (!isValid){ throw new RuntimeException("Expression is invalid: "+expression); } long number1 = numbers.pollFirst(); long number2 = numbers.pollFirst(); long result = 0; if (operator.equals("+")){ result = number1+number2; }else if (operator.equals("-")){ result = number1 - number2; }else if (operator.equals("*")){ result = number1 * number2; }else if (operator.equals("/")){ result = number1 / number2; } numbers.addFirst(result); } if (numbers.size() != 1){ throw new RuntimeException("Expression is invalid: "+expression); } return numbers.pop(); } } 上述代码,语法规则的解析逻辑(23、25、27、29行)集中于一个函数,对于简单的语法规则的解析,这样的设计足够。但对于复杂的语法规则的解析,逻辑复杂,代码量多,所有的解析逻辑都耦合在一个函数中,不合适,考虑拆分代码将解析逻辑拆分到独立的小类。 如何拆分?借助解释器模式。没有固定格式,核心思想是将语法解析的工作拆分到各个小类,以此避免大而全的解析类。一般做法是,将语法规则拆分为一些小的独立的单元,再对每个单元解析,最终合并为对整个语法规则的解析。 前面定义的语法规则有两类表达式,一类是数字,一类是运算符。运算符又包含加减乘除。利用解释器模式,将解析的工作拆分为NumberExpression、AdditionExpression、SubstractionExpression、MultiplicationExpression、DivisionExpression五个解析类中。重构后: public interface Expression { long interpret(); } public class NumberExpression implements Expression { private long number; public NumberExpression(long number){ this.number = number; } public NumberExpression(String number){ this.number = Long.parseLong(number); } @Override public long interpret() { return this.number; } } public class AdditionExpression implements Expression { private Expression exp1; private Expression exp2; public AdditionExpression(Expression exp1,Expression exp2){ this.exp1 = exp1; this.exp2 = exp2; } @Override public long interpret() { return exp1.interpret()+exp2.interpret(); } } public class SubstractionExpression implements Expression { private Expression exp1; private Expression exp2; public SubstractionExpression(Expression exp1,Expression exp2){ this.exp1 = exp1; this.exp2 = exp2; } @Override public long interpret() { return exp1.interpret()-exp2.interpret(); } } public class MultiplicationExpression implements Expression { private Expression exp1; private Expression exp2; public MultiplicationExpression(Expression exp1,Expression exp2){ this.exp1 = exp1; this.exp2 = exp2; } @Override public long interpret() { return exp1.interpret()*exp2.interpret(); } } public class DivisionExpression implements Expression { private Expression exp1; private Expression exp2; public DivisionExpression(Expression exp1,Expression exp2){ this.exp1 = exp1; this.exp2 = exp2; } @Override public long interpret() { return exp1.interpret() / exp2.interpret(); } } public class ExpressionInterpreter { private Deque<Expression> numbers = new LinkedList<>(); public long interpret(String expression){ String[] elements = expression.split(" "); int length = elements.length; for (int i=0;i<(length+1)/2;i++){ numbers.addLast(new NumberExpression(elements[i])); } for (int i=(length+1)/2;i<length;i++){ String operator = elements[i]; boolean isValid = "+".equals(operator) || "-".equals(operator) || "*".equals(operator) || "/".equals(operator); if (!isValid){ throw new RuntimeException("Expression is invalid: "+expression); } Expression exp1 = numbers.pollFirst(); Expression exp2 = numbers.pollFirst(); Expression combinedExp = null; if (operator.equals("+")){ combinedExp = new AdditionExpression(exp1,exp2); }else if (operator.equals("-")){ combinedExp = new SubstractionExpression(exp1,exp2); }else if (operator.equals("*")){ combinedExp = new MultiplicationExpression(exp1,exp2); }else if (operator.equals("/")){ combinedExp = new DivisionExpression(exp1,exp2); } long result = combinedExp.interpret(); numbers.addFirst(new NumberExpression(result)); } if (numbers.size()!=1){ throw new RuntimeException("Expression is invalid: "+expression); } return numbers.pop().interpret(); } } ### 解释器模式实战举例 ### 如何实现一个自定义接口告警规则功能? 平时开发中,监控系统非常重要,可时刻监控业务系统的运行情况,及时将异常报告给开发者。如,如果每分钟接口出错数超过100,监控系统就通过短信、微信、邮件等方式发送告警给开发者。 一般,监控系统支持开发者自定义告警规则,如可用下面这样一个表达式,来表示一个告警规则,意思是:每分钟api总出错数超过100或每分钟api总调用数超过10000就触发告警。 api_error_per_minute > 100 || api_count_per_minute > 10000 监控系统中,告警模块只负责根据统计数据和告警规则,判断是否触发告警。至于每分钟API接口出错数、每分钟接口调用数等统计数据的计算,是由其他模块负责。其他模块将统计数据放到一个map中,发送给告警模块。接下来只关注告警模块。 Map<String,Long> apiStat = new HashMap<>(); apiStat.put("api_error_per_minute",103); apiStat.put("api_count_per_minute",987); 为简化代码实现,假设自定义的告警规则只包含"|| && > < ==“这五个运算符,其中,”> < ==“运算符的优先级高于”|| &&“运算符,”&&“运算符优先级高于”||"。在表达式中,任意元素之间需要通过空格分隔。此外,用户可自定义要监控的key,如前面的api\_error\_per\_minute、api\_count\_per\_minute。 如何实现该需求? public class AlertRuleInterpreter { // key1 > 100 && key2 < 1000 || key3 == 200 public AlertRuleInterpreter(String ruleExpression){ //todo } //<String,Long> apiStat = new HashMap<>() //apiStat.put("key1",103); //apiStat.put("key2",987); public boolean interpret(Map<String,Long> stats){ //todo } } public class DemoTest { public static void main(String[] args) { String rule = "key1 > 100 && key2 < 30 || key3 < 100 || key4 == 88"; AlertRuleInterpreter interpreter = new AlertRuleInterpreter(rule); Map<String,Long> stats = new HashMap<>(); stats.put("key1",101L); stats.put("key3",121L); stats.put("key4",88L); boolean alert = interpreter.interpret(stats); System.out.println(alert); } } 实际上,可把自定义的告警规则,看做一种特殊“语言”的语法规则。实现一个解释器,能够根据规则,针对用户输入的数据,判断是否触发告警。利用解释器模式,将解析表达式的逻辑拆分到各个小类,避免大而复杂的大类的出现。补全代码。 public interface Expression { boolean interpret(Map<String,Long> stats); } public class GreaterExpression implements Expression { private String key; private long value; public GreaterExpression(String strExpression){ String[] elements = strExpression.trim().split("\\s+"); if (elements.length != 3 || !elements[1].trim().equals(">")){ throw new RuntimeException("Expression is invalid: "+strExpression); } this.key = elements[0].trim(); this.value = Long.parseLong(elements[2].trim()); } public GreaterExpression(String key,long value){ this.key = key; this.value = value; } @Override public boolean interpret(Map<String, Long> stats) { if (!stats.containsKey(key)){ return false; } long statValue = stats.get(key); return statValue > value; } } public class LessExpression implements Expression { private String key; private long value; public LessExpression(String strExpression){ String[] elements = strExpression.trim().split("\\s+"); if (elements.length != 3 || !elements[1].trim().equals("<")){ throw new RuntimeException("Expression is invalid: "+strExpression); } this.key = elements[0].trim(); this.value = Long.parseLong(elements[2].trim()); } public LessExpression(String key,long value){ this.key = key; this.value = value; } @Override public boolean interpret(Map<String, Long> stats) { if (!stats.containsKey(key)){ return false; } long statValue = stats.get(key); return statValue < value; } } public class EqualExpression implements Expression { private String key; private long value; public EqualExpression(String strExpression){ String[] elements = strExpression.trim().split("\\s+"); if (elements.length != 3 || !elements[1].trim().equals("==")){ throw new RuntimeException("Expression is invalid: "+strExpression); } this.key = elements[0].trim(); this.value = Long.parseLong(elements[2].trim()); } public EqualExpression(String key,long value){ this.key = key; this.value = value; } @Override public boolean interpret(Map<String, Long> stats) { if (!stats.containsKey(key)){ return false; } long statValue = stats.get(key); return statValue == value; } } public class AndExpression implements Expression { private List<Expression> expressions = new ArrayList<>(); public AndExpression(String strAndExpression){ String[] strExpressions = strAndExpression.split("&&"); for (String strExpr:strExpressions){ if (strExpr.contains(">")){ expressions.add(new GreaterExpression(strExpr)); }else if (strExpr.contains("<")){ expressions.add(new LessExpression(strExpr)); }else if (strExpr.contains("==")){ expressions.add(new EqualExpression(strExpr)); }else { throw new RuntimeException("Expression is invalid: "+strAndExpression); } } } public AndExpression(List<Expression> expressions){ this.expressions.addAll(expressions); } @Override public boolean interpret(Map<String, Long> stats) { for (Expression expr:expressions){ if (!expr.interpret(stats)){ return false; } } return true; } } public class OrExpression implements Expression { private List<Expression> expressions = new ArrayList<>(); public OrExpression(String strOrExpression){ String[] orExpressions = strOrExpression.split("\\|\\|"); for (String orExpr : orExpressions){ expressions.add(new OrExpression(orExpr)); } } public OrExpression(List<Expression> expressions){ this.expressions.addAll(expressions); } @Override public boolean interpret(Map<String, Long> stats) { for (Expression expr:expressions){ if (expr.interpret(stats)){ return true; } } return false; } } public class AlertRuleInterpreter { private Expression expression; public AlertRuleInterpreter(String ruleExpression){ this.expression = new OrExpression(ruleExpression); } public boolean interpret(Map<String,Long> stats){ return expression.interpret(stats); } } > 其实,spring的el表达式就是解释器模式。 ## 中介模式 ## ### 中介模式的原理和实现 ### 中介模式,mediator design pattern,定义:mediator pattern defines a separate(mediator) object that encapsulates the interaction between a set of objects and the objects delegate their interaction to a mediator object instead of interacting with each other directly. 中介模式定义了一个单独的(中介)对象,来封装一组对象之间的交互,将这组对象之间的交互委派给与中介对象交互,避免对象之间的直接交互。 中介模式的设计思想,跟中间层很像,通过引入中介这个中间层,将一组对象之间的交互关系(或者说依赖关系)从多对多(网状关系)转换为一对多(星状关系)。原来一个对象跟n个对象交互,现在只需跟一个中介对象交互,从而最小化对象之间的交互关系,降低代码的复杂度,提高代码的可读性和可维护性。 中介模式经典的例子,就是航空管制。为了让飞机在飞行时互不干扰,每架飞机都需要知道其他飞机每时每刻的位置,需要时刻跟其他飞机通信,通信网络非常复杂。这时,引入“塔台”这个中介,让每架飞机只跟塔台通信,发送自己的位置给塔台,由塔台负责每架飞机的调度,简化通信网络。 编程相关的例子 就是UI控件。假设有个比较复杂的对话框,对话框有很多控件,比如按钮、文本框、下拉框等。当对某个控件进行操作时,其他控件会作出相应的反应。如,在下拉框中选择“注册”,注册相关的控件会显示在对话框中,如果在下拉框选择“登录”,登录相关的控件会出现在对话框。 按照通常喜欢的UI界面的开发方式,将需求用代码实现,如下,这种实现方式,控件和控件之间相互操作、相互依赖。 public class UIControl { private static final String LOGIN_BIN_ID = "loin_btn"; private static final String REG_BIN_ID = "reg_btn"; private static final String USERNAME_INPUT_ID = "username_input"; private static final String PASSWORD_INPUT_ID = "pswd_input"; private static final String REPEATED_PASSWORD_INPUT_ID = "repeated_pswd_input"; private static final String HINT_TEXT_ID = "hint_text"; private static final String SELECTION_ID = "selection"; public static void main(String[] args) { Button loginButton = (Button)findViewById(LOGIN_BIN_ID); Button regButton = (Button)findViewById(REG_BIN_ID); final Input usernameInput = (Input) findViewById(USERNAME_INPUT_ID); final Input passwordInput = (Input) findViewById(PASSWORD_INPUT_ID); Input repeatedPswdInput = (Input) findViewById(REPEATED_PASSWORD_INPUT_ID); Text hintText = (Text) findViewById(HINT_TEXT_ID); Selection selection = (Selection)findViewById(SELECTION_ID); loginButton.setOnclickListener(new OnClickListener(){ @Override public void onClick(View v){ String username = usernameInput.text(); String password = passwordInput.text(); //校验数据... //做业务处理... } }); regButton.setOnclickListener(new OnClickListener(){ @Override public void onClick(View v){ //获取usernameInput passwordInput repeatedPswdInput数据... //校验数据... //做业务处理... } }); //...省略selection下拉选择框相关代码... } } 按照中介模式,重新实现,各个控件只跟中介对象交互,中介对象负责所有业务逻辑的处理。 public interface Mediator { void handleEvent(Component component,String event); } public class LandingPageDialog implements Mediator { private Button loginButton; private Button regButton; private Selection selection; private Input usernameInput; private Input passwordInput; private Input repeatedPswdInput; private Text hintText; @Override public void handleEvent(Component component, String event) { if (component.equals(loginButton)){ String username = usernameInput.text(); String password = passwordInput.text(); //校验数据... //做业务处理... }else if (component.equals(regButton)){ //获取usernameInput passwordInput repeatedPswdInput数据... //校验数据... //做业务处理... }else if (component.equals(selection)){ String selectedItem = selection.select(); if (selectedItem.equals("login")){ usernameInput.show(); passwordInput.show(); repeatedPswdInput.hide(); hintText.hide(); //...省略其他代码 }else if (selectedItem.equals("register")){ //... } } } } public class UIControl { private static final String LOGIN_BIN_ID = "loin_btn"; private static final String REG_BIN_ID = "reg_btn"; private static final String USERNAME_INPUT_ID = "username_input"; private static final String PASSWORD_INPUT_ID = "pswd_input"; private static final String REPEATED_PASSWORD_INPUT_ID = "repeated_pswd_input"; private static final String HINT_TEXT_ID = "hint_text"; private static final String SELECTION_ID = "selection"; public static void main(String[] args) { final Button loginButton = (Button)findViewById(LOGIN_BIN_ID); final Button regButton = (Button)findViewById(REG_BIN_ID); Input usernameInput = (Input) findViewById(USERNAME_INPUT_ID); Input passwordInput = (Input) findViewById(PASSWORD_INPUT_ID); Input repeatedPswdInput = (Input) findViewById(REPEATED_PASSWORD_INPUT_ID); Text hintText = (Text) findViewById(HINT_TEXT_ID); Selection selection = (Selection)findViewById(SELECTION_ID); final Mediator dialog = new LandingPageDialog(); dialog.setLoginButton(loginButton); dialog.setRegButton(regButton); dialog.setUsernameInput(usernameInput); dialog.setPasswordInput(passwordInput); dialog.setRepeatedPswdInput(repeatedPswdInput); dialog.setHintText(hintText); dialog.setSelection(selection); loginButton.setOnclickListener(new OnClickListener(){ @Override public void onClick(View v){ dialog.handleEvent(loginButton,"click"); } }); regButton.setOnclickListener(new OnClickListener(){ @Override public void onClick(View v){ dialog.handleEvent(regButton,"click"); } }); //... } } 从代码中看中介出,原本业务逻辑分散在各个控件中,现在集中到了中介类中。实际上既有好处又有坏处。好处是简化控件之间的交互,坏处是中介类会变成大而复杂的“上帝类”(god class)。使用中介模式时,要根据实际情况,平衡对象之间交互的复杂度和中介类本身的复杂度。 ### 中介模式vs观察者模式 ### 观察者模式有很多实现方式,虽然经典的实现方式没有彻底解耦观察者和被观察者。观察者需注册到被观察者中,被观察者状态更新需调用观察者的update()方法。但,跨进程实现方式中,可利用消息队列实现彻底的解耦。观察者和被观察者只跟消息队列交互,观察者完全不知道被观察者的存在,被观察者也完全不知道观察者的存在。 前面提到,中介模式也是为了解耦对象之间的交互,所有的参与者都只跟中介交互。而观察者模式的消息队列,有点类似中介模式的“中介”,观察者模式的观察者和被观察者,类似中介模式的“参与者”,那两个模式的区别在哪里? 观察者模式中,尽管一个参与者既可以是观察者,同时也可以是被观察者,但大部分情况下,交互关系往往都是单向的,一个参与者要么是观察者,要么是被观察者,不会兼具两种身份。也就是说,在观察者模式的应用场景中,参与者之间的交互关系比较有条理。 而中介模式相反,只有当参与者之间的交互关系错综复杂,维护成本很高时,才考虑中介模式,毕竟,中介模式的应用有一定的副作用。可能会产生大而复杂的上帝类。此外,如果一个参与者状态的改变,其他参与者执行的操作有一定的先后顺序的要求,这时,中介模式可利用中介类,通过先后调用不同的参与者的方法,实现顺序的控制,而观察者模式无法实现顺序要求。 ## 设计模式的学习路径 ## ### 1. 建立完善的知识体系 ### 这个专栏涵盖了编写高质量代码的方方面面,如面向对象、设计原则、设计思想、编码规范、重构技巧、设计模式。知识框架如下。先建立整体的知识框架,再慢慢深入、各自攻破,这也是学习任何一门新技术、新知识最有效的方法。 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dqbDMxODAy_size_16_color_FFFFFF_t_70_pic_center] ### 2. 建立代码质量意识 ### 学完专栏,只要写代码时,能不由自主的思考代码质量,有意识的打磨代码,对代码质量有所追求,就说明入门了。想一想,在写代码或者读别人的代码时,是否开始思考代码质量问题?如果没有,在今后一个月内,写代码前、中、后,都思考一下代码的扩展性、可读性、可维护性、可测试性等代码质量问题,看自己编写的代码是否符合这些质量要求,有没有继续优化重构的地方。 ### 3. 主动学习而非被动学习 ### 主动学习,主动思考,遇到不会的、理解不了的知识点,自己主动思考一下、查查资料,或者和同事讨论下,试着自己总结下专栏的内容,认真思考每个课后题。 找一篇觉得还没有透彻理解的文章,花一天的时间实现下里面的代码,把文章的重点内容自己思考,整理,输出为文章。 ### 4. 多读几遍更有收获 ### 作者写这个专栏的时候,查阅了大量的文章和资料,有时,一篇文章写好几天,这期间有长时间、高强度的阅读、思考和揣摩,需要跟作者一样花很长时间,才能有类似的水平。 一个人的认知和理解能力受限于他的经历和经验。如果开发经验不多,看专栏的时候,难免会抓不住重点或理解的不够透彻,建议反复学习,理论结合项目的实践,这个过程可能持续很长时间,1年,2年甚至3年、五年,只有这样,才能积累出真正的能力、建立真正的竞争壁垒。 找专栏的一篇文章,反复读上10遍(带着思考读),肯定比只读一遍理解更透彻。 ### 5. 把代码写到极致 ### 100段烂代码不如写1段好代码,对代码能力的提高大。把一个事情做到极致,往往也能把其他很多事情做到极致。 > 项目实战部分的大部分代码都放在我的[github][]上,欢迎拍砖 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dqbDMxODAy_size_16_color_FFFFFF_t_70_pic_center]: /images/20221123/42778fd4391f4e5cb31a5df292820c8b.png [github]: https://github.com/wangjinliang1991/DailyTask
相关 设计模式之美笔记16 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 解释器模式 解释器模式的原理和实现 深藏阁楼爱情的钟/ 2022年12月01日 11:53/ 0 赞/ 136 阅读
相关 设计模式之美笔记15 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 访问者模式 访问者模式的诞生 我就是我/ 2022年12月01日 05:16/ 0 赞/ 142 阅读
相关 设计模式之美笔记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 赞/ 173 阅读
相关 设计模式之美笔记11 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 门面模式 门面模式的原理和实现 ゞ 浴缸里的玫瑰/ 2022年11月28日 13:41/ 0 赞/ 155 阅读
相关 设计模式之美笔记10 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 序言 代理模式 桥接模式 柔情只为你懂/ 2022年11月28日 10:36/ 0 赞/ 149 阅读
相关 设计模式之美笔记9 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 工厂模式 1. 简单工厂 待我称王封你为后i/ 2022年11月28日 00:41/ 0 赞/ 148 阅读
相关 设计模式之美笔记8 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 单例模式 1. 为什么要使用单例 柔光的暖阳◎/ 2022年11月26日 07:52/ 0 赞/ 153 阅读
相关 设计模式之美笔记7 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 实战1:id生成器的重构 1. 需求背景 女爷i/ 2022年11月25日 13:19/ 0 赞/ 186 阅读
还没有评论,来说两句吧...