设计模式② :交给子类
一、前言
有时候不想动脑子,就懒得看源码又不像浪费时间所以会看看书,但是又记不住,所以决定开始写"抄书"系列。本系列大部分内容都是来源于《 图解设计模式》(【日】结城浩 著),内容仅用于个人学习记录,可随意转载。
二、Template Method 模式
Template Method 模式 :将具体处理交给子类
1. 介绍
在父类中定义处理流程框架,在子类中实现具体处理的模式就称为 Template Method 模式。
在 Template Method 模式 中登场的角色:
- AbstractClass(抽象类):AbstractClass 角色不仅负责实现模板方法,还负责声明在模板方法中所使用到的抽象方法。这些抽象方法由子类ConcreteClass角色负责实现。
- ConcreteClass (具体类):该角色负责具体实现AbstractClass角色中定义的抽象方法。
类图如下, 抽象模板类会提供两个 methodA 、 methodB 两个抽象方法供子类实现,同时自身实现一个模板方法 templateMethod。在 templateMethod 方法中定义程序的行为,但行为的具体内容则是由子类实现:
Demo 如下:
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 部分,如下(下面代码仅作演示,真实代码并非如下)。
private <T> T execute(String sql){
// 1. 获取DB 连接以及其他预处理
doDbConnect();
// 2. Sql 执行
executeSql(sql);
// 3. 释放资源
releaseDbConnection();
}
可以看出 JdbcTemplate#execute 作为一个 Template Method 通过完成了 DB 连接与释放的功能。但实际上 Spring几乎所有的外接扩展都采用回调模式模式来执行。如下, 通过 callback 回调来执行具体的业务逻辑:
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 获取最终的数据集。
// 抽象模板方法,供不同通道的子类实现 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; } }
-
项目B中,需要对客户资料进行解析,资料固定是一个 PDF、一个 Excel 文件,需要对两个文件中的数据解析并汇总处理,而可能存在的情况是PDF 和 Excel 存在多套格式。即可以定义出来一个 FileTemplate ,不同格式的 PDF 和 Excel 实现不同的 FileTemplate ,最终完成多种模板格式的解析(可以通过策略模式对每一种不同的格式的文件实现单独的解析策略,进一步解耦)
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 如下:
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 方法中可以实现自定义的逻辑:
<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 实例来获取对应的全局数据。如下:
/** * 顶层定义一个 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。
参考内容
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!