设计模式之美笔记4 逃离我推掉我的手 2023-03-02 13:21 5阅读 0赞 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 ### 文章目录 ### * * 经典设计原则续 * * 1. 依赖反转原则 * * 1. 控制反转IOC * 2. 依赖注入DI * 3. 依赖注入框架DI Framework * 4. 依赖反转原则DIP * 2. KISS和YAGNI原则 * * 1. 如何理解KISS原则 * 2. 代码行数越少越简单吗 * 3. 代码逻辑复杂就违反KISS原则吗 * 4. 如何写出满足KISS原则的代码 * 5. YAGNI和KISS是一回事吗 * 3. DRY原则 * * 1. 概念 * 2. 实现逻辑重复 * 3. 功能语义重复 * 4. 代码执行重复 * 5. 代码复用性code reusability * 4. 迪米特法则LOD * * 1. 概念 * 2. 理论解读和代码实战1 * 3. 理论解读和代码实战2 ## 经典设计原则续 ## ### 1. 依赖反转原则 ### 思考题: 1. “依赖反转”这个概念指的是“谁跟谁”的“什么依赖”被反转了?“反转”两个字该如何理解? 2. 经常听到另外两个概念:“控制反转”和“依赖注入”,它们和“依赖反转”有什么区别和联系?是同一个事情吗? 3. java语言的spring框架中的IOC跟这些概念有什么关系? #### 1. 控制反转IOC #### 英文Inversion Of Control,暂时不要将它和spring框架的IOC联系到一起。 举例看什么是控制反转 public class UserServiceTest{ public static boolean doTest(){ //... } public static void main(String[] args){ //这部分逻辑可放到框架中 if(doTest()){ System.out.println("Test succeed."); }else{ System.out.println("Test failed."); } } } 上述代码中,所有流程由程序员控制。如果抽象出下面这个框架,再看如何利用框架实现同样的功能: public abstract class TestCase{ public void run(){ if(doTest()){ System.out.println("Test succeed."); }else{ System.out.println("Test failed."); } } public abstract void doTest(); } public class JunitApplication{ private static final List<TestCase> testCases = new ArrayList<>(); public static void register(TestCase testCase){ testCases.add(testCase); } public static final void main(String[] args){ for(TestCase case:testCases){ case.run(); } } } 将这个简化版的测试框架引入工程中,只需在框架预留的扩展点,也就是TestCase类的doTest()抽象方法中,填充具体的测试代码即可实现之前的功能。完全不需要写负责执行流程的main()方法 public class UserServiceTest extends TestCase{ @Override public boolean doTest(){ //... } } //注册操作还可通过配置的方式实现,不需要程序员显式调用register JunitApplication.register(new UserServiceTest()); 上述案例就是通过框架实现“控制反转”的例子。框架提供一个可扩展的代码骨架,用来组装对象、管理整个执行流程。程序员用框架开发时,只需要往预留的扩展点,添加自己业务相关的代码,就能利用框架驱动整个程序流程的执行。 这里的“控制”指的是对程序执行流程的控制,而“反转”是指在没用框架之前,程序员自己控制整个程序的执行,而用框架后,程序的执行流程通过框架控制。流程的控制权从程序员“反转”到框架。 实际上,实现控制反转的方法很多,除了上述例子所示类似模板设计模式的方法之外,还有依赖注入等方法。所以,控制反转不是一种具体的实现技巧,而是一个比较笼统的设计思想,一般用来指导框架层面的设计。 #### 2. 依赖注入DI #### 依赖注入Dependency Injection,跟控制反转相反,是一种具体的编码技巧。有个形象的说法:依赖注入是一个标价25美元,而实际只值5美分的概念。理解应用很简单。概括就是:不通过new()方法的方式在类内部创建依赖类对象,而是将依赖的类对象在外部创建好后,通过构造函数、方法参数等方式传递(或注入)给类使用。 举例说明。Notification类负责消息推送,依赖MessageSender类实现推送商品促销、验证码等消息给用户。分别用依赖注入和非依赖注入两种方式实现。 //=======非依赖注入实现方式======== public class Notification{ private MessageSender messageSender; public Notification(){ this.messageSender = new MessageSender();//有点像硬编码 } public void sendMessage(String cellphone,String message){ //...省略校验逻辑等... this.messageSender.send(cellphone,message); } } public class MessageSender{ public void send(String cellphone,String message){ //... } } //使用Notification Notification notification = new Notification(); //============依赖注入方式实现========== public class Notification{ private MessageSender messageSender; //通过构造函数将messageSender传递进来 public Notification(MessageSender messageSender){ this.messageSender = messageSender; } public void sendMessage(String cellphone,String message){ //...省略校验逻辑等... this.messageSender.send(cellphone,message); } } //使用Notification MessageSender messageSender = new MessageSender(); Notification notification = new Notification(messageSender); 通过依赖注入的方式将依赖的类对象传递进来,提高了代码的扩展性,可以灵活的替换依赖的类。当然,还能继续优化,将MessageSender定义为接口,基于接口而非实现编程。 public class Notification{ private MessageSender messageSender; //通过构造函数将messageSender传递进来 public Notification(MessageSender messageSender){ this.messageSender = messageSender; } public void sendMessage(String cellphone,String message){ //...省略校验逻辑等... this.messageSender.send(cellphone,message); } } public interface MessageSender{ void send(String cellphone,String message); } //短信发送类 public class SmsSender implements MessageSender{ @Override public void send(String cellphone,String message){ //... } } //站内信发送类 //短信发送类 public class InboxSender implements MessageSender{ @Override public void send(String cellphone,String message){ //... } } //使用Notification MessageSender messageSender = new SmsSender(); Notification notification = new Notification(messageSender); 依赖注入是编写可测试性代码最有效的手段。 #### 3. 依赖注入框架DI Framework #### 采用依赖注入实现的Notification类中,虽然不需要硬编码,在类内部通过new来创建MessageSender对象,但创建对象、组装(或注入)对象的工作仅仅是被移动到更上层的代码而已,还需要我们自己实现。 public class Demo{ public static final void main(String[] args){ MessageSender sender = new SmsSender();//创建对象 Notification notification = new Notification(sender);//依赖注入 notification.sendMessage("13510733521","短信验证码:2346"); } } 实际的软件开发,一些项目可能会涉及非常多的类,类对象的创建和依赖注入非常复杂,而对象创建和依赖注入的工作,本身和具体业务无关,抽象为框架自动完成,也就是“依赖注入框架”。通过框架提供的扩展点,简单配置所有要创建的类对象、类和类之间的依赖关系,就能实现由框架自动创建对象、管理对象的生命周期、依赖注入等原本要程序员做的事情。 现成的依赖注入框架如Google Guice,java spring等。spring框架自称是控制反转容器(Inversion Of Control Container)。spring框架的控制反转主要是通过依赖注入实现。 #### 4. 依赖反转原则DIP #### 依赖反转原则Dependency Inversion Principle,也叫依赖倒置原则。英文描述:High-level modules shouldn’t depend on low-level modules. Both modules should depend on abstractions. In addition, abstractions shouldn’t depend on details. Details depend on abstractions。也就是:高层模块不要依赖低层模块,应该通过抽象来互相依赖。此外,抽象不要依赖具体实现细节,具体实现细节依赖抽象。 高层和低层模块的划分:在调用链上,调用者属于高层,被调用者属于低层。这个原则主要是指导框架层面的设计。以tomcat这个servlet容器为例。tomcat是运行java web应用程序的容器。web应用程序代码只需部署到tomcat容器下,就能被tomcat容器调用执行。因此,tomcat是高层模块,web应用程序代码是低层模块。tomcat和程序代码之间没有直接的依赖关系,都依赖同一个“抽象”,也就是servlet规范。servlet规范不依赖具体的tomcat容器和应用程序的实现细节,而tomcat容器和应用程序依赖servlet规范。 > spring开发的项目在tomcat中,控制权在tomcat手中,微服务兴起,tomcat内嵌到springboot项目中,控制权反转到springboot手中。 ### 2. KISS和YAGNI原则 ### 问题: * 怎么理解KISS原则的“简单”两个字?什么样的代码才算“简单”?怎样的代码才算“复杂”? * 如何才能写出“简单”的代码?YAGNI原则和KISS原则说的是一回事吗? #### 1. 如何理解KISS原则 #### 英文好几个版本:Keep It Simple and Stupid。尽量保持简单。是个万金油类型的设计原则,可用于很多场景,如iPhone的设计。那如何在代码开发中应用这条原则呢?如何落地呢? #### 2. 代码行数越少越简单吗 #### 先看案例,下面三段代码可实现同样一个功能:检查输入的字符串ipAddress是否是合法的IP字段。 一个合法的IP地址由4个数字组成,并通过"."进行分割。每组数字取值范围是0~255.第一组较特殊,不允许为0。 //第一种实现方式:使用正则表达式 public boolean isValidIpAddressV1(String ipAddress){ if(StringUtils.isBlank(ipAddress)) return false; String regex = "^(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[1-9])\\." +"(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\." +"(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\." +"(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)$"; return ipAddress.matches(regex); } //第二种实现方式:使用现成的工具类 public boolean isValidIpAddressV2(String ipAddress){ if(StringUtils.isBlank(ipAddress)) return false; String[] ipUnits = StringUtils.split(ipAddress,'_'); if(ipUnits.length != 4){ return false; } for(int i=0; i<4; ++i){ int ipUnitIntValue; try{ ipUnitIntValue = Integer.parseInt(inUnits[i]); }catch(NumberFormatException e){ return false; } if(ipUnitIntValue < 0 || ipUnitIntValue>255){ return false; } if(i==0 && ipUnitIntValue==0){ return false; } } return true; } //第三种,不用任何工具类 public boolean isValidIpAddressV3(String ipAddress){ char[] ipChars = ipAddress.toCharArray(); int length = ipChars.length; int ipUnitIntValue = -1; boolean isFirstUnit = true; int unitsCount = 0; for(int i=0; i<length;++i){ char c = ipChars[i]; if(c=='.'){ if(ipUnitIntValue<0 || ipUnitIntValue>255) return false; if(isFirstUnit && ipUnitIntValue==0) return false; if(isFirstUnit) isFirstUnit=false; ipUnitIntValue = -1; unitsCount++; continue; } if(c<'0' || c>'9'){ return false; } if(ipUnitIntValue == -1) ipUnitIntValue=0; ipUnitIntValue = ipUnitIntValue*10+(c-'0'); } if(ipUnitIntValue<0 || ipUnitIntValue>255) return false; if(unitsCount != 3) return false; return true; } 第一种正则,写出来就很难,也难保证不出bug,可读性和可维护性也差;第二种使用一些现成的工具类,处理IP地址字符串;第三种,不用任何工具类,逐一处理IP地址中的字符,判断是否合法。第三种更有难度,更易出现bug,当然性能更高些。 如何选择,如果一般的业务代码,首选第二种,第三种其实是过度优化,牺牲代码的可读性,性能提升并不明显,投入产出比较低。 #### 3. 代码逻辑复杂就违反KISS原则吗 #### 如果代码的逻辑复杂、实现难度大、可读性也不好,是否就一定违反KISS原则? 以KMP字符串匹配算法的代码实现为例,该算法以快速高效著称。当需要处理长文本字符串匹配问题(几百MB大小文本内容的匹配),或者字符串匹配是某个产品的核心功能(如Vim、Word等文本编辑器),又或者字符串匹配算法是系统性能瓶颈时,应该选择该算法。本身就复杂的问题,用复杂的算法解决,并不违反KISS原则。 当然,平时项目开发涉及到的字符串匹配问题,大多都是较小的文本,直接调用现成的字符串匹配方法即可。要看应用场景。 #### 4. 如何写出满足KISS原则的代码 #### * 不要用同事可能不懂的技术来实现,如前面的正则表达式,还有些过于高级的语法 * 不要重复造轮子,善于使用已有的工具类库 * 不要过度优化,牺牲代码的可读性 #### 5. YAGNI和KISS是一回事吗 #### YAGNI原则的英文:You Ain’t Gonna Need It。你不会需要它。意为:不要去设计当前用不到的功能;不要编写当前用不到的代码。也就是不要做过度设计。 如用redis存储配置信息,可能以后会用zookeeper,没必要提前编写这块代码,只需要预留好扩展点即可。再比如,不要在项目中提前引入不需要依赖的开发包。如maven等配置文件,提前引入大量常用的library包,就违反YAGNI原则。 YAGNI原则讲的是“要不要做”的问题(当前不需要的就不要做),KISS原则讲的是“如何做”的问题(尽量保持简单)。 ### 3. DRY原则 ### #### 1. 概念 #### 英文描述:Don’t Repeat Yourself。应用于编程,可理解为:不要写重复的代码。 三种场景: #### 2. 实现逻辑重复 #### 看下面这段代码是否违反DRY原则: public class UserAuthenticator{ public void authenticate(String username,String password){ if(!isValidUsername(username)){ //...throw InvalidUsernameException } if(!isValidPassword(password)){ //...throw InvalidPasswordException } //...省略其他代码... } private boolean isValidUsername(String username){ // check not null, not empty if(StringUtils.isBlank(username)){ return false; } // check length:4~64 int length = username.length(); if(length < 4 || length > 64){ return false; } // contains only lowcase characters if(!StringUtils.isAllLowerCase(username)){ return false; } // contains only a~z,0~9,dot for(int i=0;i<length;i++){ char c = username.charAt(i); if(!((c>='a' && c<='z') || (c>='0' && c<='9') || c=='.')){ return false; } } return true; } private boolean isValidPassword(String password){ // check not null, not empty if(StringUtils.isBlank(password)){ return false; } // check length:4~64 int length = password.length(); if(length < 4 || length > 64){ return false; } // contains only lowcase characters if(!StringUtils.isAllLowerCase(password)){ return false; } // contains only a~z,0~9,dot for(int i=0;i<length;i++){ char c = password.charAt(i); if(!((c>='a' && c<='z') || (c>='0' && c<='9') || c=='.')){ return false; } } return true; } } 有两处非常明显的重复的代码片段:isValidUsername()和isValidPassword()方法。看起来违反DRY原则,但如果重构为isValidUsernameOrPassword(),代码行数减少,但并非更好。合并后的方法,负责两个事情,违反了单一职责原则和接口隔离原则。 其实这两个方法,虽然代码实现逻辑看起来重复,但语义上并不重复。语义不重复的意思是:从功能上看,这两个方法干的是完全不重复的两件事情,一个校验用户名,一个校验密码。如果某天需要修改校验密码的逻辑,那还需要重新拆分。 如果代码实现逻辑相同,但语义不同,判定不违反DRY原则。对于包含重复代码的问题,可以抽象成更细的粒度的方法来解决。如将校验只包含az、09、dot的逻辑封装为boolean onlyContains(String str,String charlist);方法。 #### 3. 功能语义重复 #### 再看另一个例子,同一个项目代码,有两个方法:isValidIp()和checkIfIpValid()。尽管命名不同,实现逻辑不同,但功能相同,都是判断IP地址是否合法。 原因可能是两个方法由两个不同的同事开发,其中一个不知道已经有了isValidIp()方法,自己又定义并实现另一个方法。这种就违反了DRY原则,因为功能重复。需要统一调一个方法。因为假如不统一实现思路,结果有些地方用isValidIp(),另一些地方调用checkIfIpValid(),不熟悉的同事增加阅读难度,而且很疑惑。此外,如果哪天IP地址判定合法的规则改变,结果只改了其中一个,忘了改另一个,导致出现莫名其妙的bug。 #### 4. 代码执行重复 #### 下面例子,UserService中login()方法校验用户登录是否成功。 public class UserSerive{ private UserDao userDao;//IOC框架注入 public User login(String email,String password){ boolean existed = userDao.checkIfUserExisted(email,password); if(!existed){ //...throw AuthenticationFailureException } User user = userDao.getUserByEmail(email); return user; } } public class UserDao{ public boolean checkIfUserExisted(String email,String password){ if(!EmailValidation.validate(email)){ //...throw InvalidEmailException... } if(!PasswordValidation.validate(password)){ //...throw InvalidPasswordException... } //...query db to check if email&password exists... } public User getUserByEmail(String email){ if(!EmailValidation.validate(email)){ //...throw InvalidEmailException... } //...query db to get user by email... } } 上面这段代码,没有逻辑重复和语义重复,但仍违反了DRY原则。因为代码存在执行重复。 最明显的就是在login()方法中,email校验逻辑执行两次。修改的话,只需要将校验逻辑从UserDao中移除,统一放到UserService中即可。 此外,还有一处较为隐蔽的执行重复,login()方法并不需要调checkIfUserExisted()方法,只需调一次getUserByEmail()方法,从数据库获取用户的emai、password,跟用户输入的信息对比,判断是否登录成功。 这样的优化很有必要,因为两个方法都需要查询数据库,这种IO操作较为耗时,尽量减少这类IO操作。 重构之后的代码: public class UserSerive{ private UserDao userDao;//IOC框架注入 public User login(String email,String password){ if(!EmailValidation.validate(email)){ //...throw InvalidEmailException... } if(!PasswordValidation.validate(password)){ //...throw InvalidPasswordException... } User user = userDao.getUserByEmail(email); if(user==null || !password.equals(user.getPassword)){ //...throw AuthenticationFailureException... } return user; } } public class UserDao{ public boolean checkIfUserExisted(String email,String password){ //...query db to check if email&password exists... } public User getUserByEmail(String email){ //...query db to get user by email... } } #### 5. 代码复用性code reusability #### 什么是代码的复用性? 首先区分三个概念:代码复用性(Code Reusability)、代码复用(Code Reuse)和DRY原则。 代码复用表示一种行为:开发新功能时,尽量复用已存在的代码。代码的可复用性表示代码可被复用的特性或能力。DRY原则是一条原则:不要写重复的代码。 **区别:** * 不重复并不代表可复用。这个角度,DRY原则和代码的可复用性是两回事 * 复用和可复用性关注角度不同。可复用性是从开发者角度讲;复用是从代码使用者角度讲。如A同事开发的UrlUtils类可复用性很好,B同事开发新功能时复用了A的这个类 如何提高可复用性? * 减少代码耦合 * 满足单一职责原则 * 模块化 * 业务和非业务逻辑分离 * 通用代码下沉 越底层的代码越通用,会被更多模块调用,因此,通用代码尽量下沉到更下层 * 继承、多态、抽象、封装 利用继承将公共的代码抽取到父类,子类复用父类的属性和方法;利用多态,动态的替换代码的部分逻辑,可复用性增强;抽象和封装,从更广义的层面理解,越是抽象、不依赖具体的实现,越容易复用;代码封装为模块,隐藏可变细节,暴露不变的接口,越容易复用 * 应用模板等设计模式 一些设计模式也提高代码的复用性。如模板模式利用多态实现。 此外,复用意识也很重要,写代码时,多思考,这部分代码是否可抽取出来,作为独立的模块、类或者方法供多处使用。 另外还有个著名的原则,“Rule Of Three”。第一次开发时,没有复用的需求,可暂时不考虑复用性。之后开发新功能,发现可复用之前代码,就重构这段代码,让其可复用。 ### 4. 迪米特法则LOD ### 问题: * 什么是“高内聚、低耦合” * 如何利用迪米特法则实现“高内聚、低耦合” * 有哪些代码设计明显违反迪米特法则,该如何重构 #### 1. 概念 #### 高内聚、低耦合是一个非常重要的设计思想,能有效的提高代码的可读性和可维护性,缩小功能改动导致的代码改动范围。很多设计原则都以实现代码的高内聚、低耦合为目的,如单一职责原则、基于接口而非实现编程等。 高内聚用来指导类本身的设计,低耦合指导类与类之间的依赖关系的设计。并非完全独立不相关,高内聚有助于低耦合,低耦合又需要高内聚的支持。 * 什么是高内聚 指的是相近的功能应放到同一个类中,不相近的功能不要放到同一个类中。相近的功能往往会被同时修改,放到一个类,修改集中,代码易维护。 * 什么是低耦合 在代码中,类与类之间的依赖关系简单清晰。即使两个类有依赖关系,一个类的改动不会或很少导致依赖类的代码改动。依赖注入、接口隔离、基于接口而非实现编程,以及迪米特法则,都是为了实现低耦合。 * 内聚和耦合之间的关系 代码耦合度高,会牵一发而动全身,改动影响多个类。高内聚、低耦合的代码结构更简单清晰,可维护性和可读性更好。 * 迪米特法则的描述 英文Law Of Demeter,另一个名字,最小知识原则,英文:The Least Knowledge Principle。英文定义:Ench unit should have only limited knowledge about other units: only units “closely” related to the current unit. Or: Each unit should only talk to its friends; Don’t talk to strangers。 也就是:不该有直接依赖的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口。 #### 2. 理论解读和代码实战1 #### 前半部分:不该有直接依赖的类之间,不要有依赖。 案例实现简化版的搜索引擎爬取网页的功能。代码包含三个主要的类。NetworkTransporter类负责底层网络通信,根据请求获取数据;HtmlDownloader类来处理URL获取网页;Document表示网页文档,后续网页内容的抽取、分词、索引都是以此为处理对象。 public class NetworkTransporter{ //省略属性和其他方法 public Byte[] send(HtmlRequest htmlRequest){ //... } } public class HtmlDownloader{ private NetworkTransporter transporter;//IOC注入 public Html downloadHtml(String url){ Byte[] rawHtml = transporter.send(new HtmlRequest(url)); return new Html(rawHtml); } } public class Document{ private Html html; private String url; public Document(String url){ this.url = url; HtmlDownloader downloader = new HtmlDownloader(); this.html = downloader.downloadHtml(url); } //... } 这段代码虽然能用,但有些缺陷。 首先,看NetworkTransporter类。作为底层网络通信类,希望尽可能通用,而不是只服务于下载HTML,不应该直接依赖太具体的发送对象HtmlRequest。应如何重构呢?假如现在去商店买东西,不会直接把钱包给收银员,让收银员自己从钱包里拿钱,而是你从钱包里把钱拿出来交给收银员。这里,HtmlRequest对象相当于钱包,里面的address和content对象相当于钱。应把address和content给NetworkTransporter。 public class NetworkTransporter{ //省略属性和其他方法 public Byte[] send(String address,Byte[] data){ //... } } 再看HtmlDownloader类。设计没问题,但刚修改了NetworkTransporter的send()方法,这个类也要相应的修改 public class HtmlDownloader{ private NetworkTransporter transporter;//IOC注入 public Html downloadHtml(String url){ HtmlRequest htmlRequest = new HtmlRequest(url); Byte[] rawHtml = transporter.send(htmlRequest.getAddress(),htmlRequest.getContent().getBytes()); return new Html(rawHtml); } } 最后看Document类。它的问题较多,主要有三点。 1. 构造函数的downloader.downloadHtml()逻辑复杂,耗时长,不应放到构造函数,影响性能。 2. HtmlDownloader对象在构造函数中通过new创建 3. 业务含义上,Document网页文档没必要依赖HtmlDownloader类,违反迪米特法则 改造: public class Document{ private Html html; private String url; public Document(String url){ this.url = url; this.html = downloader.downloadHtml(url); } //... } //通过工厂方法创建Document public class DocumentFactory{ private HtmlDownloader downloader; public DocumentFactory(HtmlDownloader downloader){ this.downloader = downloader; } public Document createDocument(String url){ Html html = downloader.downloadHtml(url); return new Document(url,html); } } #### 3. 理论解读和代码实战2 #### 原则的后半部分:“有依赖关系的类之间,尽量只依赖必要的接口”。如之前讲的Serialization类负责序列化和反序列化。假设在项目中,有些类只用到序列化操作,而另一些类只用到反序列化。那只用到序列化操作的类不应该依赖反序列化接口,反之亦然。 因此,我们应该将Serialization类拆分为更小粒度的类,但这样违反了高内聚的设计思想。那如何解决这个问题?通过引入两个接口就能解决该问题。也就是接口隔离原则。 public interface Serializable{ String serialize(Object object); } public interface Deserializable{ Object deserialize(String text); } public class Serialization implements Serializable,Deserializable{ @Override public String serialize(Object object){ String serializedResult = ...; ... return serializedResult; } @Override public Object serialize(String str){ Object deserializedResult = ...; ... return deserializedResult; } } public class DemoClass1{ private Deserializable deserializer; public Demo(Deserializable deserializer){ this.deserializer = deserializer; } //... } public class DemoClass2{ private Serializable serializer; public Demo(Serializable serializer){ this.serializer = serializer; } //... } 上述的代码思路,体现了基于接口而非实现编程的设计原则,结合迪米特法则,可以总结出一条新的设计原则,基于最小接口而非最大实现编程。
相关 设计模式之美笔记4 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 经典设计原则续 1. 依赖反转原则 逃离我推掉我的手/ 2023年03月02日 13:21/ 0 赞/ 6 阅读
相关 设计模式之美笔记16 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 解释器模式 解释器模式的原理和实现 深藏阁楼爱情的钟/ 2022年12月01日 11:53/ 0 赞/ 153 阅读
相关 设计模式之美笔记15 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 访问者模式 访问者模式的诞生 我就是我/ 2022年12月01日 05:16/ 0 赞/ 159 阅读
相关 设计模式之美笔记14 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 状态模式 背景 什么 水深无声/ 2022年11月30日 15:51/ 0 赞/ 175 阅读
相关 设计模式之美笔记13 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 策略模式 策略模式的原理和实现 忘是亡心i/ 2022年11月30日 12:27/ 0 赞/ 163 阅读
相关 设计模式之美笔记12 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 观察者模式 原理及应用场景剖析 深碍√TFBOYSˉ_/ 2022年11月30日 04:18/ 0 赞/ 192 阅读
相关 设计模式之美笔记11 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 门面模式 门面模式的原理和实现 ゞ 浴缸里的玫瑰/ 2022年11月28日 13:41/ 0 赞/ 186 阅读
相关 设计模式之美笔记10 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 序言 代理模式 桥接模式 柔情只为你懂/ 2022年11月28日 10:36/ 0 赞/ 173 阅读
相关 设计模式之美笔记9 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 工厂模式 1. 简单工厂 待我称王封你为后i/ 2022年11月28日 00:41/ 0 赞/ 178 阅读
相关 设计模式之美笔记7 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 实战1:id生成器的重构 1. 需求背景 女爷i/ 2022年11月25日 13:19/ 0 赞/ 204 阅读
还没有评论,来说两句吧...