设计模式② :交给子类

文章目录

  • 一、前言
  • [二、Template Method 模式](#二、Template Method 模式)
    • [1. 介绍](#1. 介绍)
    • [2. 应用](#2. 应用)
    • [3. 总结](#3. 总结)
  • [三、Factory Method 模式](#三、Factory Method 模式)
    • [1. 介绍](#1. 介绍)
    • [2. 应用](#2. 应用)
    • [3. 总结](#3. 总结)
  • 参考内容

一、前言

有时候不想动脑子,就懒得看源码又不像浪费时间所以会看看书,但是又记不住,所以决定开始写"抄书"系列。本系列大部分内容都是来源于《 图解设计模式》(【日】结城浩 著),内容仅用于个人学习记录,可随意转载。

二、Template Method 模式

Template Method 模式 :将具体处理交给子类

1. 介绍

在父类中定义处理流程框架,在子类中实现具体处理的模式就称为 Template Method 模式。

在 Template Method 模式 中登场的角色:

  • AbstractClass(抽象类):AbstractClass 角色不仅负责实现模板方法,还负责声明在模板方法中所使用到的抽象方法。这些抽象方法由子类ConcreteClass角色负责实现。
  • ConcreteClass (具体类):该角色负责具体实现AbstractClass角色中定义的抽象方法。

类图如下, 抽象模板类会提供两个 methodA 、 methodB 两个抽象方法供子类实现,同时自身实现一个模板方法 templateMethod。在 templateMethod 方法中定义程序的行为,但行为的具体内容则是由子类实现:


Demo 如下:

java 复制代码
	public abstract class AbstractClass {
		// 定义两个由子类实现的方法
	    protected abstract void methodA();
		// 定义两个由子类实现的方法
	    protected abstract void methodB();
	    
	    // 模板方法,在合适的场景可以将该方法定义为 final。
	    // 模板类通过该方法定义了整个程序的行为,如下行为为:先调用 methodA 再调用 methodB
	    // 对于子类则只需要关注 methodA 和 methodB 的具体实现,而不需要关注整个程序的行为。
	    public void templateMethod() {
	        methodA();
	        methodB();
	    }
	}
	// 子类只需要关注抽象方法的实现,而不需要关注整个调用过程
	public class StudentClass extends AbstractClass {
	
	    @Override
	    public void methodA() {
	        System.out.println("StudentClass.methodA");
	    }
	
	    @Override
	    public void methodB() {
	        System.out.println("StudentClass.methodB");
	    }
	}
	
	public class TeacherClass extends AbstractClass {
	
	    @Override
	    public void methodA() {
	        System.out.println("TeacherClass.methodA");
	    }
	
	    @Override
	    public void methodB() {
	        System.out.println("TeacherClass.methodB");
	    }
	}

2. 应用

Spring模板方法模式实质是模板方法模式和回调模式的结合,是Template Method不需要继承的另一种实现方式,如 JdbcTemplate、RedisTeplate、MongoTemplate 等。

这里我们以 JdbcTemplate为例,当我们调用 JdbcTemplate#execute 执行 Sql 时,JdbcTemplate#execute 流程是 DB 连接、Sql 执行、DB 释放,而我们实际只编写了Sql 部分,如下(下面代码仅作演示,真实代码并非如下)。

java 复制代码
	private <T> T execute(String sql){
		// 1. 获取DB 连接以及其他预处理
		doDbConnect();
		// 2. Sql 执行
		executeSql(sql);
		// 3. 释放资源
		releaseDbConnection();
	}

可以看出 JdbcTemplate#execute 作为一个 Template Method 通过完成了 DB 连接与释放的功能。但实际上 Spring几乎所有的外接扩展都采用回调模式模式来执行。如下, 通过 callback 回调来执行具体的业务逻辑:

java 复制代码
    public final Object execute(StatementCallback callback){    
        Connection con=null;    
        Statement stmt=null;    
        try {    
            con=getConnection();    
            stmt=con.createStatement();    
            Object retValue=callback.doWithStatement(stmt);    
            return retValue;    
        } catch(SQLException e){    
            ...    
        } finally{    
            closeStatement(stmt);    
            releaseConnection(con);    
        }    
    }  

JDBC的抽象和对Hibernate的集成,都采用了一种理念或者处理方式,那就是模板方法模式与相应的Callback接口相结合。



个人使用:该部分内容是写给自己看的,帮助自身理解,因此就不交代项目背景了,读者请自行忽略(◐ˍ◑)

  • 项目A中,需要根据通道的不同构建不同的数据集,便使用了如下的模板模式,不同的通道实现各自的模板类,其中 DataHead 和 DataDetail 可以通过继承的方式进行各个通道的数据扩展。通过 DataTemplate#getFinalData 获取最终的数据集。

    java 复制代码
    // 抽象模板方法,供不同通道的子类实现
    public abstract class DataTemplate {
    
        /**
         * 获取头数据
         * @return
         */
        protected abstract DataHead getDataHead();
    
        /**
         * 获取详细数据
         * @return
         */
        protected abstract List<DataDetail> getDataDetail();
    
        /**
         * 获取最终的数据集
         * @return
         */
        public FinalData getFinalData() {
            final FinalData finalData = new FinalData();
            finalData.setDataHead(getDataHead());
            finalData.setDataDetailList(getDataDetail());
            return finalData;
        }
    }
  1. 项目B中,需要对客户资料进行解析,资料固定是一个 PDF、一个 Excel 文件,需要对两个文件中的数据解析并汇总处理,而可能存在的情况是PDF 和 Excel 存在多套格式。即可以定义出来一个 FileTemplate ,不同格式的 PDF 和 Excel 实现不同的 FileTemplate ,最终完成多种模板格式的解析(可以通过策略模式对每一种不同的格式的文件实现单独的解析策略,进一步解耦)

    java 复制代码
    public abstract class FileTemplate {
    
        /**
         * 获取头数据
         * @return
         */
        protected abstract PdfData getPdfData();
    
        /**
         * 获取详细数据
         * @return
         */
        protected abstract ExcelData getExcelData();
    
        /**
         * 获取最终的数据集
         * @return
         */
        public FileData getFinalData() {
            final FileData finalData = new FileData();
            finalData.setPdfData(getPdfData());
            finalData.setExcelData(getExcelData());
            return finalData;
        }
    }

3. 总结

在 Template Method 模式中,可以使用继承(实现)改变程序的行为。这是因为 Template Method 模式在父类中定义程序行为的框架,在子类中决定具体的处理。在该模式中,处理的流程被定义在父类中,而具体的处理则交给了子类。

在 Strategy 模式中,可以使用委托改变程序的行为。与 Template Method 模式中改变部分程序行为不同的是, Strategy 模式用于替换整个算法。


相关设计模式:

  • Factory Method 模式:Factory Method 模式是将 Template Method 模式用于生成实例的一个典型例子。
  • Strategy 模式:在 Template Method 模式中,可以使用继承(实现)改变程序的行为。这是因为 Template Method 模式在父类中定义程序行为的框架,在子类中决定具体的处理。在该模式中,处理的流程被定义在父类中,而具体的处理则交给了子类。而在 Strategy 模式中,他可以使用委托改变程序的行为。与 Template Method 中改变部分程序行为不同的是,Strategy 模式用于替换整个算法

三、Factory Method 模式

Factory Method 模式 :将实例的生成交给子类。

1. 介绍

Template Method 模式在父类中定义程序行为的框架,在子类中决定具体的处理。如果将该模式用于生成实例,那么他将演变成 Factory Method 模式。在该模式中,父类决定实例的生成方式,但并不决定所要生成的具体的类,具体的处理全部交给子类负责。这样就可以将生成实例的框架和实际负责生成实例的类解耦。


Factory Method 模式中登场的角色

  • Product (产品):Product 属于框架这一方,是一个抽象类。它定义了在 Factory Method 模式中生成的那些实例所持有的接口 (API),但具体的处理则由子类角色决定。
  • Creator (创建者):Creator 属于框架这一方,它是负责生成 Product 角色的抽象类,但具体的处理规则由子类角色决定。Creator 角色对于实际负责生成实例的ConcreteCreator角色一无所知,它唯一知道的就是只要调用 Product 角色和生成实例的方法就可以生成 Product 的实例。
  • ConcreteProduct (具体的产品):ConcreteProduct 属于具体的加工者一方,它决定了具体的产品。
  • ConcreteCreator(具体的创建者):ConcreteCreator 属于具体的加工者一方,它负责生产具体的产品。

类图如下:我们可以得知父类(框架)这一方面的角色的关系与子类(具体加工)这一方面的角色关系是平行的。这里Creator 定义了 create 方法用于创建(生产) Product 实例,同时 factoryMethod 则是该工厂类的其他工厂方法,具体需要根据业务去定义。


Demo 如下:

java 复制代码
	public abstract class Creator {
	    // create 方法用于创建 具体 Product,并可以执行其他逻辑,如 调用registerProduct方法实现注册功能等(具体看业务需求)
	    public final Product create(){
	        final Product product = createProduct();
	        registerProduct(product);
	        return product;
	    }
	    
	    // 下面是 factoryMethod 方法
	    
	    // 使用 createProduct 创建 Product,目的是父类与子类解耦。
	    protected abstract Product createProduct();
	    // 注册产品
	    protected abstract void registerProduct(Product product);
	
	}
	
	public abstract class Product {
	    // 随意定义的两个 方法
	    public abstract void methodA();
	
	    public abstract void methodB();
	}
	
	public class ConcreteCreator extends Creator{
	    // 实现 factoryMethod, 根据业务需要可以有不同的实现
	
	    @Override
	    protected Product createProduct() {
	        return new ConcreteProduct();
	    }
	    // 根据业务需要进行实现,这里随意调用了 Product 的两个方法
	    @Override
	    protected void registerProduct(Product product) {
	        product.methodA();
	        product.methodB();
	    }
	}
	
	public class ConcreteProduct extends Product {
	
	    @Override
	    public void methodA() {
	        System.out.println("ConcreteProduct.methodA");
	    }
	
	    @Override
	    public void methodB() {
	        System.out.println("ConcreteProduct.methodB");
	    }
	}
	
	// 用于测试的 Main 方法
	public class FactoryMethodMain {
	    public static void main(String[] args) {
	        // 实际场景下  Creator 应该有多个实现类,而在这种情况下可以使用工厂模式或简单工厂模式来获取 Creator 实例。
	        Creator creator = new ConcreteCreator();
	        // 调用模板方法创建具体实例
	        final Product product = creator.create();
	        // TODO : do something
	    }
	}

2. 应用

  • Spring 框架支持通过 factory-bean 和 factory-method 属性的方式来指定工厂方法来创建Bean。如下指定 DesignConfig#designDemo 的方法来创建 DesignDemo 并注册到容器中,在指定的 factory-method 方法中可以实现自定义的逻辑:

    xml 复制代码
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="designConfig" class="com.kingfish.pojo.config.DesignConfig"/>
        <bean id="designDemo" class="com.kingfish.pojo.DesignDemo" factory-bean="designConfig" factory-method="designDemo"/>
    
    </beans>


个人使用:该部分内容是写给自己看的,帮助自身理解,因此就不交代项目背景了,读者请自行忽略(◐ˍ◑)

  • 项目A中,不同的 Region 都会获取相同的数据,但数据来源以及拼接不同,因此计划在服务启动或在其他触发条件下,生成一个 RegionContent 类,可以作为上下文或者其他的数据,不同 Region 进行具体实现,根据 Region 的不同加载不同的 RegionContent 实例来获取对应的全局数据。如下:

    java 复制代码
    /**
     * 顶层定义一个 Creator 接口
     */
    public interface ContextCreator {
        RegionContent createContext();
    }
    
    // 定义一个 Region Creator 的抽象类,用于创建 RegionContent 
    public abstract class RegionContentCreator implements ContextCreator {
    	
        @Override
        public final RegionContent createContext() {
            final RegionContent regionContent = doCreateContent();
            postProcess(regionContent);
            return regionContent;
        }
    	// 创建方法
        protected abstract RegionContent doCreateContent();
    	// 后置处理,在 RegionContent 创建的后置处理 - 需要的话可以实现
        protected abstract void postProcess(RegionContent regionContent);
    }
    
    
    // SH  Region的实现
    @Slf4j
    public class ShRegionContentCreator extends RegionContentCreator {
        @Override
        protected RegionContent doCreateContent() {
            return new ShRegionContext();
        }
    
        @Override
        protected void postProcess(RegionContent regionContent) {
            log.info("[region 后期处理][regionContext = {}]", regionContent);
        }
    }
    
    // RegionContent 接口定义
    public interface RegionContent {
        /**
         * 返回 region 标识
         * @return
         */
        String getRegion();
    
        /**
         * 获取关区内容
         * @return 懒得定义实现类,所以返回 Object
         */
        Object getContent();
    }
    
    // SH RegionContent 的实现
    public class ShRegionContext implements RegionContent {
    
    
        @Override
        public String getRegion() {
            // 应该用全局变量或者枚举
            return "sh";
        }
    
        @Override
        public Object getContent() {
            // 随便返回
            return "这里是 SH Region 的 Content";
        }
    }
    
    @Slf4j
    public class DemoMain {
        public static void main(String[] args) {
        	// 创建 RegionContent 。实际业务会有多个 RegionContentCreator, 可以根据环境或者参数加载
            RegionContentCreator regionContentCreator = new ShRegionContentCreator();
            final RegionContent content = regionContentCreator.createContext();
            // 随便打印
            log.info("region = {}, regionContent = {}", content.getRegion(), content.getContent());
        }
    }

3. 总结

相关的设计模式:

  • Template Method 模式:Factory Method 模式是 Template Method 的典型应用
  • Singleton 模式:多数情况下 Singleton 模式用于扮演 Creator 角色 或者 ConcreteCreator 橘色的类,这是因为在程序中没有必要存在多个Creator 或 ConcreteCreator 角色的实例。
  • Composite 模式:有时候可以将 Composite 模式用于 Product 或 ConcreteProduct 角色。
  • Iterator 模式:有时在 Iterator 模式中使用 iterator 方法生成 Iterator 的实例时会使用 Factory Method。

参考内容

https://mp.weixin.qq.com/s/JUV4cnE_HqRMFriKMHk0Ug

相关推荐
触底反弹2 小时前
🤯 面试被问 AI Workflow 和 Agent 有啥区别?3 张图 + 2 段代码讲清楚!
人工智能·设计模式·面试
杨充1 天前
10.可测试性实战设计
设计模式·开源·代码规范
杨充1 天前
9.重构十二式的实战
设计模式·开源·代码规范
杨充1 天前
6.设计原则的全景图
设计模式·开源·全栈
杨充1 天前
2.面向对象的特性
设计模式
杨充1 天前
7.SOLID原则案例汇
设计模式·开源·全栈
杨充1 天前
8.反模式与坏味道
设计模式·开源·代码规范
杨充1 天前
3.接口vs抽象类比较
设计模式
咖啡八杯1 天前
文法、BNF与AST
java·设计模式·解释器模式·ast·文法