读书笔记:“幸福并非瞬间发生,他与运气、概率无关,用钱买不到,也不能倚仗权势巧取豪夺。它不受外在事物操纵,而取决于我们对外在事物的阐释”。这句话触动很大,很多时候的精神内耗,无妄的想象揣测导致我们对外在的看法变得消极。阿德勒心理学提出的目的论,引导我们的就是改变自己对外在事物的阐释,从而做到他者信赖、他者贡献,主动去改变自己,也可以改变世界,建立维护双向的良好关系。
一、前言背景
二、模板方法模式
2.1 实战demo-场景
2.2 模板方法模式的优点
2.3 tomcat 源码应用模板模式
三、外观模式(门面模式)
3.1 实战demo-场景
3.2 外观模式的优点
3.3 tomcat 源码应用外观模式
四、单例模式
4.1 实战demo-场景
4.1.1 线程不安全-懒汉单例
4.1.2 线程安全-懒汉-单例模式
4.1.3 线程安全-饿汉-单例模式
4.2 单例模式优点
4.3 Spring 源码应用单例模式
一、前言背景
在上一篇系列文章《设计模式觉醒系列(01)设计模式的基石 | 六大原则的核心是什么?》,我们已经详细分享了设计模式的六大原则,以及总结设计模式在研发设计过程中的核心作用。可以说,设计模式就是专门帮我们解决设计问题的经典方案,也是帮助研发人员提高代码可复用性、可维护性、可扩展性、可阅读性的内功心法。
设计模式总共有20多种,如果一篇一篇来写,可以写很长时间,读者也可以订阅关注很长时间。实际而言,很多设计模式都是基于六大基本原则衍生出来,都有共性,为了减少阅读疲劳,以及帮助有缘同学尽快掌握了解齐全这20多种设计模式,后续每篇文章,我们尽量至少分享2个设计模式,期望10篇左右分享完毕。今天我们一次分享三个最简单实用、又非常接地气的设计模式,相信你一定都见过、用过。
附带多说一句,每个设计模式并非完美,都有优缺点。没有绝对的完美解决方案,唯有合适的业务场景应用合适的设计模式,争取最佳实践效果。
二、模板方法模式
模板方法模式(Template Method Pattern),英文原义是:定义一个算法的框架,将一些步骤的实现,延迟到子类中去。在模板方法模式里,父类定义了一个模板方法(它真的就是一个方法method()),然后在这个方法里包含了一系列的操作,比如按顺序调用方法a、b、c、d。而其中的某些操作父类并没有实现,并定义为抽象方法,让子类去实现。
比如以下非常简单的demo,定一个美好一天的抽象类,里面有个模板方法start(),模板方法里按顺序调用了起床、刷牙、吃早餐的方法。
其他三个方法,父类已经实现起床方法getup()+一个吃早餐方法eatBreakfast(),而刷牙方法延迟给子类去实现。
// 抽象美好一天类 abstract class AbstractNiceDay { // 模板[方法],定义一天的开始,需要起床,刷牙,吃早餐 public final void start() { getup(); brushtooth(); eatBreakfast(); } // 起床,公共方法,父类已经实现好 private void getup() { System.out.println("拉丁解牛说技术,每个人早上都会起床"); } // 刷牙,抽象方法,由子类实现,有一些人可能不爱刷牙,比如小朋友 protected abstract void brushtooth(); // 吃早餐,公共方法,父类已经实现好 private void eatBreakfast() { System.out.println("拉丁解牛说技术,每个人早上都吃早餐"); }
具体应用,如果小火、小美他们都开启美好的一天,就分别继承这个相信这个AbstractNiceDay父类,实现里面刷牙方法就可以复用父类的模板方法start()正式开启快乐的一天。这个很直观,大家都能理解,也就不用画图说明。
2.1 实战demo-场景
假如我们需要设计开发一个文档生成器,文档生成包括新建具体文档、文档增加水印标签、保存文档三个步骤。
package lading.java.designpattern.templatemethod; /** * 业务场景:文档生成器,支持PDF,word,excel,csv,ppt等文件 * 文档处理器-抽象类 * 包含三个步骤, * 1、生成具体文档 * 2、文档加水印 * 3、保存文档 */ public abstract class AbstractDocumentGenerator { //定义模板方法,按顺序生成文档 public final void generateDocument() { createDocument(); addTarget(); saveDocument(); } //子类自己实现 protected abstract void createDocument(); //文档加水印,已实现 protected void addTarget() { System.out.println("给文档内容加拉丁解牛说技术的水印或者标识"); } protected void saveDocument() { System.out.println("保存文档到固定文档目录/file/ladingjieniu/doc"); } } package lading.java.designpattern.templatemethod; /** * 业务场景:文档生成器,支持PDF,word,excel,csv,ppt等文件 * 文档处理器-抽象类 * 包含三个步骤, * 1、生成具体文档 * 2、文档加水印 * 3、保存文档 */ public abstract class AbstractDocumentGenerator { //定义模板方法,按顺序生成文档 public final void generateDocument() { createDocument(); addTarget(); saveDocument(); } //子类自己实现 protected abstract void createDocument(); //文档加水印,已实现 protected void addTarget() { System.out.println("给文档内容加拉丁解牛说技术的水印或者标识"); } protected void saveDocument() { System.out.println("保存文档到固定文档目录/file/ladingjieniu/doc"); } }
2.2 模板方法模式优点
子类可以在严格遵守父类(或者说算法)模板结构的前提下,重新自定义实现具体的某些步骤。这里充分体现了六大核心原则的开闭原则(OCP),对具体步骤实现修改开放,对模板规则框架修改封闭。
此外也提高代码的复用性,子类复用父类已实现的部分步骤方法,减少代码编写。
以及也提高代码可扩展性。
2.3 tomcat 源码应用模板模式
在tomcat源码里,javax.servlet.http.HttpServlet 类的service方法,也是应用了模板方法。service()方法就是个模板方法,他会根据不同请求类型调用相应的处理方法。而里面调用的具体方法,比如doGet、doPost,这些方法在子类可以自定义去实现。源码:
三、外观模式(门面模式)
外观模式(facade),也叫门面模式。它的定义是:一个子系统的外部与其内部的通信必须通过一个统一的对象进行。具体是:外观模式为子系统的一组接口提供一个高层次的接口(IFacade,或者定一个xxFacade外观类)。这样可以隐藏子系统的复杂性,使得子系统更易于使用。当客户端调用子系统时候,只需要通过这个高层次的外观接口(Facade类)就可以调用子系统的功能,从而达到客户端与子系统解耦。
比如说一会3.1例子demo的订单系统,订单系统有支付、库存、物流分配三个子系统功能,外部第三方平台需要调用我们订单系统下定单。我们可以定义一个专门处理订单的OrderFacade类,封装好三个功能,供外部客户端去调用。
3.1 实战demo-场景
具体如下,通过OrderFacade类封装好底层内部的支付、物流、库存管理功能。外部client只需要调用OrderFacade类就可以实现下单功能。
package lading.java.designpattern.facade; /** * 物流分配子模块 */ public class LogisticsService { public void assignExpress(){ System.out.println("说技术拉丁分配物流公司接单安排派送"); } } package lading.java.designpattern.facade; /** * 支付服务模块 */ public class PayService { public void processPay(){ System.out.println("拉丁处理订单支付扣款事宜"); } } package lading.java.designpattern.facade; /** * 库存管理模块 */ public class StoreService { public void deductProd(){ System.out.println("ladingjieniu 减少产品库存"); } } package lading.java.designpattern.facade; /** * 内部订单系统门面-外观类 */ public class OrderFacade { //内置相关子类功能 private LogisticsService logisticsService = new LogisticsService(); private PayService payService = new PayService(); private StoreService storeService = new StoreService(); public void processOrder() { payService.processPay(); storeService.deductProd(); logisticsService.assignExpress(); } /** * 客户端调用下单 * * @param args */ public static void main(String[] args) { OrderFacade facade = new OrderFacade(); facade.processOrder(); } }
3.2 外观模式的优点
外观模式非常像公司内部的门户,通过提供高层次的外观模式类,隐藏子系统的交互细节,有效降低客户端与内部模块的耦合度。
由于解耦,子系统的修改,不影响外观类,也不影响客户端的调用,提高代码的可扩展性、可维护性。
外观模式充分体现了依赖倒转原则(细节依赖抽象,降低模块依赖)和迪米特法则(最少知道,一个类对另一个类知道越少越好)的思想。
3.3 tomcat 源码应用外观模式
在tomcat源码里,RequestFacade就是外观模式的经典标准应用。RequestFacade实现了HttpServletRequest接口,底层封装了Request对象,客户端ApplicationDispatcher在应用Request时候,就不需要关系Request的底层逻辑,通过RequestFacade类就可以调用,实现解耦。
此外tomcat里的ResponseFacade、StandardSessionFacade都是用了外观模式。
四、单例模式
单例模式,singleton pattern,原义很简单:确保一个类只有一个实例,并自行实例化给整个系统使用。这个特点决定里单例模式适合在对资源开销敏感、要求资源共享复用场景下应用,比如缓存、静态资源。
单例模式有几种实现方式,一个是懒汉模式,等需要用到实例的时候再去实例化,这种模式需要注意线程安全。另一个是饿汉模式,在类加载的时候就实例化对象,这种是线程安全的。接下来对这两种模式的三个案例提供demo分享。
4.1 实战demo-场景
4.1.1 线程不安全-懒汉单例
package lading.java.designpattern.singleton; /** * 并发不安全,单例模式 */ public class UnsafeSingleton { private static UnsafeSingleton singleton; //避免外部构造多个对象 private UnsafeSingleton() { } //仅该方法可以获得实例 public static UnsafeSingleton getSingleton() { //多个线程同时执行到这部分,就会出现多个实例 if (singleton == null) { singleton = new UnsafeSingleton(); } return singleton; } }
4.1.2 线程安全-懒汉-单例模式
package lading.java.designpattern.singleton; /** * 懒汉单例,双重检查,线程安全的单例模式 */ public class SafeSingletonLazy { private static volatile SafeSingletonLazy singleton; //私有,防止外部实例化 private SafeSingletonLazy() { } public static SafeSingletonLazy getSingleton() { if (singleton == null) { synchronized (SafeSingletonLazy.class) { if (singleton == null) { singleton = new SafeSingletonLazy(); } } } return singleton; } }
4.1.3 线程安全-饿汉-单例模式
package lading.java.designpattern.singleton; /** * 饿汉-单例模式,线程安全 * 单例对象在加载的时候就实例化好该实例,线程安全 */ public class SafeSingletonHungry { private static final SafeSingletonHungry singletonHungry = new SafeSingletonHungry(); //私有化,防止外部实例化 private SafeSingletonHungry() { } public static SafeSingletonHungry getInstance() { return singletonHungry; } }
4.2 单例模式优点
通过确保一个类在整个应用程序生命周期里只有一个实例对象,直接好处就是减少创建对象的开销,为系统节省资源。
4.3 Spring 源码应用单例模式
我们熟悉的spring的IOC容器的实例管理有经典实践,@Bean默认就是单例模式,也可以通过@Scope("prototype")进行修改。
推荐阅读拉丁解牛相关专题系列(欢迎交流讨论):
2、JVM进阶调优系列(2)字节面试:JVM内存区域怎么划分,分别有什么用?