栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

应对需求扩展——简单工厂+模板模式实践

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

应对需求扩展——简单工厂+模板模式实践

应对需求扩展——简单工厂+模板模式实践

前言:软件设计开发需要关注需求的长期变化,SOLID(S-单一职责原则,O-开放封闭原则,L-里氏替换原则,I-接口隔离原则,D-依赖倒置原则)软件设计原则中,开闭原则是软件设计的重要方向。开闭原则即对扩展开放,对修改关闭,最简单的理解为:新增需求时,尽量通过新增代码(主要是新增类)而不是通过修改已有代码来实现,因为修改现有代码会引入新的不稳定因素,原本的单元测试也需要重新开展。

设计模式是在特定场景下遵循设计原则的解决方案,学习并参考设计模式可以帮助新手了解如何写出易扩展、易维护的代码。

本案例是在学习部分设计模式后,将工厂和模板模式使用到实际开发场景的实践,提升了系统的可扩展性。


一、简单工厂模式

工厂模式一般分为三种细分的类型:简单工厂、工厂方法和抽象方法。本案例由于创建逻辑简单(只是简单的new),无需组合其他类对象并做各种初始化操作,因此无需将对象的创建逻辑拆分到不同的类中,使用简单工厂即可满足需求。

工厂模式将对象的创建交给专门的工厂完成,目的在于将对象的创建和使用解耦,使类的职责更加单一,方便修改和扩展。

例子

现有这样一个场景:根据配置文件的后缀——json、xml、yaml、properties,选择不同的解析器——JsonRuleConfigParser、XmlRuleConfigParser等,将存储在文件中的配置解析成内存对象RuleConfig。

1.1 不使用简单工厂

不使用简单工厂时,代码实现如下:

public class RuleCofigSource {
    public RuleConfig load(String ruleConfigFilePath) {
        String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);
        // 由当前类完成对象的创建
        IRuleConfigParser parser = createParser(ruleConfigFileExtension);
        if (parser == null) {
            throw new InvalidRuleConfigException(
                "Rule config file format is not supported: " + ruleConfigFilePath);
        }
        String configText = "";
        RuleConfig ruleConfig = parser.parse(configText);
        return ruleConfig;
    }

    private String getFileExtension(String filePath) {
        //...解析文件名获取扩展名,比如rule.json,返回json
        return "json";
    }

    private IRuleConfigParser createParser(String configFormat) {
        IRuleConfigParser parser = null;
        if ("json".equalsIgnoreCase(configFormat)) {
            parser = new JsonRuleConfigParser();
        } else if ("xml".equalsIgnoreCase(configFormat)) {
            parser = new XmlRuleConfigParser();
        } else if ("yaml".equalsIgnoreCase(configFormat)) {
            parser = new YamlRuleConfigParser();
        } else if ("properties".equalsIgnoreCase(configFormat)) {
            parser = new PropertiesRuleConfigParser();
        }
        return parser;
    }
}
1.2 使用简单工厂

上述代码将IRuleConfigParser类型对象的创建封装在createParser()函数中,代码逻辑相对清晰,但为了让类的职责更加单一、代码更加清晰,还可以进一步将createParser()函数剥离到一个独立的类中,让这个类只负责对象的创建,这个类就是简单工厂。具体代码如下:

  • 简单工厂的第一种实现方式

    public class RuleConfigSource {
      public RuleConfig load(String ruleConfigFilePath) {
        String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);
        IRuleConfigParser parser = RuleConfigParserFactory.createParser(ruleConfigFileExtension);
        if (parser == null) {
          throw new InvalidRuleConfigException(
                  "Rule config file format is not supported: " + ruleConfigFilePath);
        }
        String configText = "";
        RuleConfig ruleConfig = parser.parse(configText);
        return ruleConfig;
      }
    
      private String getFileExtension(String filePath) {
        //...解析文件名获取扩展名,比如rule.json,返回json
        return "json";
      }
    }
    
    public class RuleConfigParserFactory {
      public static IRuleConfigParser createParser(String configFormat) {
        IRuleConfigParser parser = null;
        if ("json".equalsIgnoreCase(configFormat)) {
          parser = new JsonRuleConfigParser();
        } else if ("xml".equalsIgnoreCase(configFormat)) {
          parser = new XmlRuleConfigParser();
        } else if ("yaml".equalsIgnoreCase(configFormat)) {
          parser = new YamlRuleConfigParser();
        } else if ("properties".equalsIgnoreCase(configFormat)) {
          parser = new PropertiesRuleConfigParser();
        }
        return parser;
      }
    }
    
  • 简单工厂的第二种实现方式

    在上面的代码实现中,每次调用RuleConfigParserFactory的createParser()方法时,都会创建一个新的IRuleConfigParser类型的对象,如果这个对象是可以复用的,为了节省内存和对象创建的时间,可以将其事先创建好缓存起来。当调用createParser()方法的时候,我们可以从缓存中取出对象直接使用。

    public class RuleConfigParserFactory {
      private static final Map cachedParsers = new HashMap<>();
    
      static {
        cachedParsers.put("json", new JsonRuleConfigParser());
        cachedParsers.put("xml", new XmlRuleConfigParser());
        cachedParsers.put("yaml", new YamlRuleConfigParser());
        cachedParsers.put("properties", new PropertiesRuleConfigParser());
      }
    
      public static IRuleConfigParser createParser(String configFormat) {
        if (configFormat == null || configFormat.isEmpty()) {
          return null;
        }
        IRuleConfigParser parser = cachedParsers.get(configFormat.toLowerCase());
        return parser;
      }
    }
    
1.3 两种方法的比较

如果现在需要为一个新的后缀格式添加一个新的解析器,在不使用简单工厂时,所有获取IRuleConfigParser解析器的类中,都要做相应的修改,关键这些创建的代码散布在项目各处,修改既费劲又很难保证完全统一。

使用简单工厂后,修改RuleConfigParserFactory类即可,调用处无需做任何修改,虽然没有做到完全符合开闭原则,但是修改集中,方便维护和扩展。

简单工厂和其他两种工厂的作用均可归结如下:

  • **封装变化:**创建逻辑有可能变化,封装成工厂类之后,创建逻辑的变更对调用者透明。

  • **代码复用:**创建代码抽离到独立的工厂类之后可以复用。

  • **隔离复杂性:**封装复杂的创建逻辑,调用者无需了解如何创建对象。

  • **控制复杂度:**将创建代码抽离出来,让原本的函数或类职责更单一,代码更简洁。

1.4 适用场景

代码中存在 if-else 分支判断,动态地根据不同的类型创建不同的对象。这种情况下,就可以考虑使用简单工厂模式,将一大推 if-else 创建对象的代码抽离出来,放到简单工厂中。

二、模板模式

模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。

这里的“算法”,我们可以理解为广义上的“业务逻辑”,并不特指数据结构和算法中的“算法”。这里的算法骨架就是“模板”,包含算法骨架的方法就是“模板方法”,这也是模板方法模式名字的由来。

2.1 基本结构
public abstract class AbstractClass {
  // 模板方法,定义算法骨架
  public final void templateMethod() {		// 定义为 final,是为了避免子类重写它		
    //...
    method1();
    //...
    method2();
    //...
  }
  
  // 需要子类实现的方法
  protected abstract void method1();
  protected abstract void method2();
}

public class ConcreteClass1 extends AbstractClass {
  @Override
  protected void method1() {
    //...
  }
  
  @Override
  protected void method2() {
    //...
  }
}

public class ConcreteClass2 extends AbstractClass {
  @Override
  protected void method1() {
    //...
  }
  
  @Override
  protected void method2() {
    //...
  }
    

AbstractClass demo = ConcreteClass1();
demo.templateMethod();

在模板模式经典的实现中,模板方法定义为 final,可以避免被子类重写。需要子类重写的方法定义为 abstract,可以强迫子类去实现。不过,在实际项目开发中,模板模式的实现比较灵活,以上两点都不是必须的。

2.2 作用和适用场景
  • 作用

    模板模式有两大作用:复用和扩展。其中,复用指的是,所有的子类可以复用父类中提供的模板方法的代码。扩展指的是,框架通过模板模式提供功能扩展点,让框架用户可以在不修改框架源码的情况下,基于扩展点定制化框架的功能。

  • 适用场景

    当多个类的算法除一些细微不同之外几乎完全一样时,可以使用该模式。将相似的实现步骤提取到超类中以去除重复代码,子类可针对特定步骤定制其不同的处理方式。

三、开发实践 3.1 需求

需求可以概述为,开发节点擦除器,针对流程图中的不同节点,例如第二列的并行节点,第三列的串行节点,做对应的节点擦除操作。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nCyOwWMy-1657625958065)(…/images/应对需求扩展——工厂+模板实践总结/image-20220712153354398.png)]

3.2 设计
  • 模板模式

    节点擦除操作可以拆解为一系列固定步骤的组合:

    1、判断当前节点是否需要进行擦除

    2、获取节点擦除器

    3、进行擦除操作

    其中,前两个步骤所有节点的操作是完全相同的,区别在于具体的擦除操作,如:并行节点的擦除逻辑为擦除当前节点和节点前后的连接线即可,串行节点则暂不支持擦除操作,其擦除方法中做的是将节点信息打印到日志中。

    因此,该应用场景与模板模式契合,我们可以定义一个抽象类Wiper,在Wiper中定义擦除节点的模板方法,模板方法定义操作步骤骨架:判断是否需要擦除->获取节点擦除器,以上两个方法作为私有方法写在Wiper中,供子类复用,最后定义一个擦除操作的抽象方法,将擦除操作延迟到子类去实现,实现差异化的擦除操作。

  • 简单工厂

    Wiper中需要根据参数获取节点擦除器,为了使Wiper的职责更加单一,代码结构更加清晰简洁,将节点擦除器的创建封装到简单工厂WiperFactory中,这样以后添加新的节点擦除器时修改也能更集中,提供代码的易扩展性和易维护性。

3.3 实现
  • 模板抽象类-Wiper

    public abstract class Wiper {
        
        protected static final String ID = "id";
        protected static final String EDGE_SOURCENODEID = "sourceNodeId";
        protected static final String EDGE_TARGETNODEID = "targetNodeId";
        private static final String AUTOSELECTION = "autoSelection";
        private static final String ISSKIPNODE = "isSkipNode";
        
        
        public static FontViewDataDto wipe(WiperParam wiperParam) {
            Boolean needWipe = isNeedWipe(wiperParam);
            if (!needWipe) {
                return wiperParam.getFontViewDataDto();
            }
            Wiper wiper = getWiper(wiperParam);
            FontViewDataDto ret = wiper.reallyWipe(wiperParam);
            return ret;
        }
    
        /抽象方法/
    
        
        protected abstract FontViewDataDto reallyWipe(WiperParam wiperParam);
    
        /私有方法/
    
        
        private static Boolean isNeedWipe(WiperParam wiperParam) {
            PartFlowDto partFlowDto = wiperParam.getPartFlowDto();
            Object autoSelection = partFlowDto.getAutoSelection();
            Map partData = partFlowDto.getPartData();
            Object isSkipNode = partData.get(ISSKIPNODE);
            Boolean b1 = autoSelection == null ? false : (Boolean) autoSelection;
            Boolean b2 = isSkipNode == null ? false : (Boolean) isSkipNode;
            return (b1 && b2);
        }
    
        private static Wiper getWiper(WiperParam wiperParam) {
            Wiper wiper = WiperFactory.getInstance(wiperParam);
            return wiper;
        }
        
    }
    
  • 实现子类-SerialNodeWiper

    @Slf4j
    public class SerialNodeWiper extends Wiper {
        
        @Override
        protected FontViewDataDto reallyWipe(WiperParam wiperParam) {
            // 组织日志打印信息
            String nodeId = wiperParam.getPartFlowDto().getNodeId();
            List> nodes = wiperParam.getFontViewDataDto().getNodes();
            for (Map map : nodes) {
                String id = (String) map.get(ID);
                if (nodeId.equals(id)) {
                    Map text = (Map) map.get("text");
                    if (text != null) {
                        Object value = text.get("value");
                        log.info("串行节点({})暂不支持擦除", value);
                    } else {
                        log.info("串行节点(nodeId={})暂不支持擦除", nodeId);
                    }
                } 
            }
            // 不擦除,直接返回
            return wiperParam.getFontViewDataDto();
        }
    }
    
  • 实现子类-ParallelNodeWiper

    @Slf4j
    public class ParallelNodeWiper extends Wiper {
        
        @Override
        protected FontViewDataDto reallyWipe(WiperParam wiperParam) {
            String nodeId = wiperParam.getPartFlowDto().getNodeId();
            FontViewDataDto fontViewDataDto = wiperParam.getFontViewDataDto();
            wipeNodeAndEdges(nodeId, fontViewDataDto);
            return fontViewDataDto;
        }
    
        private void wipeNodeAndEdges(String nodeId, FontViewDataDto fontViewDataDto) {
            List> nodes = fontViewDataDto.getNodes();
            List> edges = fontViewDataDto.getEdges();
            wipeNode(nodeId, nodes);
            wipeEdges(nodeId, edges);
        }
    
        
        private void wipeNode(String nodeId, List> nodes) {
            Iterator> iterator = nodes.iterator();
            while (iterator.hasNext()) {
                Map map = iterator.next();
                String id = (String) map.get(ID);
                if (nodeId.equals(id)) {
                    Map text = (Map) map.get("text");
                    if (text != null) {
                        log.info("擦除节点:{}", text.get("value"));    
                    } else {
                        log.info("擦除节点,nodeId={}", nodeId);
                    }
                    iterator.remove();
                }
            }
        }
    
        
        private void wipeEdges(String nodeId, List> edges) {
            Iterator> iterator = edges.iterator();
            while (iterator.hasNext()) {
                Map map = iterator.next();
                String sourceNodeId = (String) map.get(EDGE_SOURCENODEID);
                String targetNodeId = (String) map.get(EDGE_TARGETNODEID);
                if (nodeId.equals(sourceNodeId) || nodeId.equals(targetNodeId)) {
                    Object id = map.get(ID);
                    log.info("擦除边:id={}", id);
                    iterator.remove();
                }
            }
        }
    }
    
  • 简单工厂-WiperFactory

    @Service
    public class WiperFactory {
        
        private static final Map wipers = new HashMap<>(2);
        
        private static CustomerProcessor customerProcessor;
    
        static {
            wipers.put(WiperType.PARALLELNODE_WIPER, new ParallelNodeWiper());
            wipers.put(WiperType.SERIALNODE_WIPER, new SerialNodeWiper());
        }
    
        @Autowired
        public WiperFactory(CustomerProcessor customerProcessor) {
            WiperFactory.customerProcessor = customerProcessor;
        }
    
        
        public static Wiper getInstance(WiperParam wiperParam) {
            PartFlowDto partFlowDto = wiperParam.getPartFlowDto();
            String flowModuleId = partFlowDto.getFlowModuleId();
            String nodeId = partFlowDto.getNodeId();
            Boolean parallelNode = customerProcessor.isParallelNode(flowModuleId, nodeId);
            if (parallelNode) {
                return wipers.get(WiperType.PARALLELNODE_WIPER);
            } else {
                return wipers.get(WiperType.SERIALNODE_WIPER);
            }
        }
        
    }
    
3.4 扩展

在面对以后节点擦除功能的需求扩展时,只需要新增一个具体的节点擦除器,继承Wiper并覆写其抽象方法,然后在WiperFactory的缓存map中添加新的节点擦除器,即可完成功能的扩展。不同节点之间的擦除逻辑互不影响,逻辑清晰,改动集中,有利于扩展和维护。

四、总结

本案例简要介绍了简单工厂模式和模板模式的基本结构和适用场景,并以实际开发场景为例,阐述了如何运用简单工厂+模板模式提高代码的可扩展性和可维护性,具有一定借鉴意义。

参考文献

王争——设计模式之美

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1013278.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号