设计模式混合篇(策略模式、模板模式、工厂模式、建造者模式)

一、前言

在学习设计模式的时候,大家可能只会去关心每一种设计模式的单独使用,而忘记了设计模式的本质,设计模式是从多种混杂的设计中拆离出来供大家学习理解的,每种设计模式其实都是有多多少少的关联,我们要结合将多种设计模式结合灵活使用,才能擦除美妙的火花,本文将结合自己的实战经历带大家理解多种设计模式的结合使用。

推荐阅读
1、建造者模式
2、策略模式
3、单例模式

二、什么是设计模式 ?

在这里插入图片描述

从这段话可以看出,设计模式是问题的解决方法,而不是具体的实现,这句话很明显的告诉了我们,设计模式是没有语言的边界的,任何语言都可以运用。设计模式是解决一类问题的方法,所以大家在了解了问题的本质后,再去选择设计模式才是最佳的。

三、为什么要使用设计模式 ?

大家可能对设计模式是只闻其名,不见其人。可能在日常的编码中也不会去考虑使用设计模式,就像算法一样,在日常的编码中也不怎么去考虑算法,但是某一天你遇到了一个问题,别人说用这个算法可以很好的解决,用那个设计模式可以轻松化解的时候,你的心情又是怎样的呢?这并不代表这我们为了虚荣心去学习,而是当别人写出了这种代码,别人提出了这种观点,你能 get 到,而不是一脸迷惑的样子,当遇到了真正使用设计模式去更好的解决问题时你能做出最优方案,其实设计模式也是对面向对象语言的灵活运用,面向对象和设计模式是相辅相成的。

四、怎么使用设计模式 ?

在这里我将使用一个 Email 解析,翻译的案例,来综合使用一下,策略模式,模板模式,工厂模式,建造者模式。

在这里由于为了文件的安全性,我需要把段落进行拆分翻译,所以只能通过 手动创建 文档节点的方式进行。

1、执行流程

首先,介绍一下执行流程:

在这里插入图片描述

→ 读取 Email 文件到系统中去 。
→ 使用解析 Email 的库进行 Email 的解析,并且将同一个 Email 中的文件存放到对应的文件目录中 。
→ 将所有的 Email 文件中包含的内容读取到内存中,并且进行语句的拆分,以便分开翻译,保证数据的安全 。
→ 将翻译好的语句进行构建文档节点 。
→ 最终将翻译的结果输出到指定目录。

在这个流程中,我主要使用了三种设计模式(策略模式用来处理不同翻译平台的对接,模板模式主要用来进行翻译流程化控制,简单工厂模式用来进行翻译策略和模板的管理)

2、策略模式

我们需要定义一个策略模式的接口,将其他语言翻译成中文。

public interface TranslateStrategy {
    /**
     * 翻译
     * @param source 其他语言
     * @return 返回中文
     */
    String translate(String source) throws IOException;
}

结合简单工厂模式,对接口进行实现并使用工厂进行管理。

public class TranslateStrategyFactory {

    public static final int BAIDU = 1;
    public static final int FUXIN = 2;

    private static Map<Integer, TranslateStrategy> strategies = new HashMap<>(6);

    private static String APP_ID;
    private static String SECURITY_KEY;

    static {
        strategies.put(BAIDU, baiDu());
        strategies.put(FUXIN, fuXin());
    }

    public static TranslateStrategy getStrategy() {
        return strategies.get(TYPE);
    }

    public static TranslateStrategy baiDu() {
        return source -> {
            TransApi transApi = new TransApi(APP_ID, SECURITY_KEY);
            // 发送请求
            String transResult = transApi.getTransResult(source, "auto", "zh");

            // 解析结果
            StringBuilder stringBuilder = new StringBuilder();

            // 解析返回的字符串
            JSONObject object = (JSONObject) JSON.parse(transResult);
            Object transStringResult = object.get("trans_result");
            JSONArray transArray = (JSONArray) transStringResult;

            // 将输出的结果进行拼装
            transArray.forEach(result -> {
                JSONObject toResult = (JSONObject) result;
                stringBuilder.append(toResult.get("dst"));
            });
            System.out.println("百度翻译结果:" + stringBuilder);
            return stringBuilder.toString();
        };
    }

    public static TranslateStrategy fuXin() {
        return source -> {

            // 构建jsonString
            Map<String, String> paramMap = new RequestParamBuilder()
                    .add("content", source)
                    .build();

            FuXinResult fuXinResult = OkHttpClientUtil.doPostHttpRequest("http://api.pdf365.cn/translate/text", paramMap);
            System.out.println("福昕翻译结果:" + fuXinResult.data.result);
            return fuXinResult.data.result;
        };
    }

    /**
     * 构建 request 请求参数
     */
    static class RequestParamBuilder {
        Map<String, String> jsonMap = new HashMap<>();

        public RequestParamBuilder() {
            jsonMap.put("from", "auto");
            jsonMap.put("to", "zh");
            jsonMap.put("token", "cKS7YdMeu1NcDO2A1xfIX24b19lzsio0");
        }

        public RequestParamBuilder add(String key, String value) {
            jsonMap.put(key, value);
            return this;
        }

        public Map<String, String> build() {
            return jsonMap;
        }
    }

}

在这里我使用读取配置文件的方式进行选择,这样可以保证用户只需要进行翻译类型的配置,无需进行代码的修改。

/**
 * 读取配置文件
 */
private static void readBaiduConfig() {
    String projectPath = System.getProperty("user.dir");
    File file = new File(projectPath + File.separator + "app.txt");
    try {
        if (StringUtil.isEmpty(APP_ID) || StringUtil.isEmpty(SECURITY_KEY)) {
            try (FileInputStream fileInputStream = new FileInputStream(file); BufferedReader ris = new BufferedReader(new InputStreamReader(fileInputStream))) {
                // 读取百度配置
                String config = ris.readLine();
                List<String> list = StringUtil.splitStr(config, ":", String::new);
                APP_ID = list.get(0);
                SECURITY_KEY = list.get(1);
                // 翻译类型
                String typeLine = ris.readLine();
                String[] split = typeLine.split(":");
                String s = split[1];
                Integer type = Integer.valueOf(s.trim());
                // 配置类型
                TYPE = type;
            }
        }

    } catch (Exception e) {
    } finally {
        // 兜底操作
        APP_ID = "20220713001271424";
        SECURITY_KEY = "fPkw5j2Wt6KBnMJieGyq";
    }
}

对于请求参数的处理,我也使用简单的建造者模式进行了封装。

/**
     * 构建 request 请求参数
     */
    static class RequestParamBuilder {
        Map<String, String> jsonMap = new HashMap<>();

        public RequestParamBuilder() {
            jsonMap.put("from", "auto");
            jsonMap.put("to", "zh");
            jsonMap.put("token", "cKS7YdMeu1NcDO2A1xfIX24b19lzsio0");
        }

        public RequestParamBuilder add(String key, String value) {
            jsonMap.put(key, value);
            return this;
        }

        public Map<String, String> build() {
            return jsonMap;
        }
    }

3、模板模式

在这里我使用 模板模式 对整个流程进行封装,抽取通用的方法,让子类实现父类抽象的方法

public abstract class FileTranslateToFolderTemplate {

    protected File file;

    public void setFile(File file) {
        this.file = file;
    }

    public FileTranslateToFolderTemplate(File file) {
        this.file = file;
    }

    public FileTranslateToFolderTemplate() {
    }

    /**
     * 执行
     */
    public void execute() {
        try {
            Attachment attachment = parserFile();
            translateAttachment(attachment);
            attachmentToSameFolder(attachment);
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    /**
     * 翻译附件
     *
     * @param attachment
     */
    protected void translateAttachment(Attachment attachment) throws Exception {
        // 翻译 word 文档
        List<AttachmentParagraph> paragraphs = attachment.getParagraphs();
        for (AttachmentParagraph paragraph : paragraphs) {
            paragraph.setTranslationContext(translateStringMethod(paragraph.getOriginalContext()));
        }
    }

    /**
     * 将文件写入到相同的目录
     *
     * @param attachment
     */
    protected void attachmentToSameFolder(Attachment attachment) throws Exception {
        File outPutFile = getOutPutFile();
        writeByWay(outPutFile, attachment);
    }

    /**
     * 根据word和pdf不同的方式写
     *
     * @param outPutFile
     * @param attachment
     */
    protected abstract void writeByWay(File outPutFile, Attachment attachment) throws Exception;

    /**
     * 解析文件
     *
     * @return
     */
    protected Attachment parserFile() throws FileNotFoundException {
        FileInputStream fileInputStream = new FileInputStream(file);
        return parserFile(fileInputStream);
    }

    protected abstract Attachment parserFile(FileInputStream fileInputStream);

    /**
     * 获取输出到的文件
     *
     * @return
     */
    protected File getOutPutFile() throws FileNotFoundException {
        File parent = new File(this.file.getParent());
        File outToFile = new File(parent, "【译文】" + file.getName());
        return outToFile;
    }
}

可以看到 execute() 方法,定义了统一的执行的流程,该执行流程是固定的,大家只需要实现抽象方法,对解析的方法进行编写即可,这样比如我想添加一个新的文件类型的解析,只需要按照这个规范进行即可,只需要实现 parserFile() 和 writeByWay() ,无需重复编写大量的代码,使代码的复用性大大提高。

对 PDF 解析的实现

public class PdfTranslateToFolder extends FileTranslateToFolderTemplate {
    public PdfTranslateToFolder() {
    }

    public PdfTranslateToFolder(File file) {
        super(file);
    }

    @Override
    protected void writeByWay(File outPutFile, Attachment attachment) throws Exception {
        attachment.setName(outPutFile.getName());
        PDFDocumentBuilder builder = new PDFDocumentBuilder();
        builder.setAttachment(attachment);
        builder.setFatherFile(new File(outPutFile.getParent()));
        builder.build();
    }

    @Override
    protected Attachment parserFile(FileInputStream fileInputStream) {
        return ParserPDFUtils.parser(fileInputStream);
    }
}

对 Word 解析的实现

public class WordDocxTranslateToFolder extends FileTranslateToFolderTemplate {
    public WordDocxTranslateToFolder() {
    }

    public WordDocxTranslateToFolder(File file) {
        super(file);
    }

    @Override
    protected void writeByWay(File outPutFile, Attachment attachment) throws IOException {

        SimpleShowMailOriginalBuilder builder = new SimpleShowMailOriginalBuilder();
        builder.add(SimpleShowMailOriginalBuilder.ContextTitle.ORIGINAL, attachment.getParagraphs());

        try (FileOutputStream fileOutputStream = new FileOutputStream(outPutFile);
             XWPFDocument document = builder.build()) {
            document.write(fileOutputStream);
        }

    }

    @Override
    protected Attachment parserFile(FileInputStream fileInputStream) {
        return ParserWordUtils.parserDocx(fileInputStream);
    }
}

五、滥用的后果

在进行处理创建节点的模块时,我确实是为了使用设计模式而使用设计模式,出现了编写时确实思路清晰,但是在修改的时候造成了,本来的一处修改,现在变成了多处修改,这样就增加了代码维护的成本。
在这里插入图片描述

从代码的文件目录接口可以看出,我把每一个节点都抽取出为一个策略,并且使用建造者模式进行构建,可后来修改的时候发现,维护成本实在是太高了。更何况节点的变动是非常大的。

我也希望通过这个案例,向大家说明,不是说任何情况设计模式都是可取的,要根据适当的场景去选择。

六、总结

  1. 设计模式在日常的开发中并不是必须的,当时设计模式能够提升代码的维护性,他也是一种通用的规范,能够帮助到大家在团队里面进行沟通。
  2. 设计模式的学习并被一日之功,需要大家慢慢消化理解,一次一次的尝试,直到找到最适合的解决方案。
  3. 同时我也推荐大家去阅读《设计模式之禅》和《重学设计模式》
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
单例模式是一种常用的设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问该实例。这对于需要共享资源或控制一个唯一对象的情况非常有用。实现单例模式可以通过在类中维护一个私有静态实例变量,并提供一个公有的静态方法来获取该实例。 工厂模式是另一种常用的设计模式,它用于创建对象而不必暴露创建对象的逻辑。该模式通过定义一个工厂类,该类负责创建和返回对象的实例。工厂类可以有多个方法用于创建不同类型的对象,这样可以根据不同的条件和参数来创建不同的对象。 建造者模式也是一种常用的设计模式,它用于创建复杂对象。该模式通过将对象的构造过程分解为一系列步骤来创建对象。每个步骤由一个具体的建造者类负责实现,最终由一个指导者类来指导建造过程。通过使用建造者模式,可以将对象的构造过程和表示细节与具体的客户代码解耦,使得对象的构造更加灵活。 这些常用的设计模式在软件开发中起到了至关重要的作用。单例模式可以确保在整个应用程序中只有一个实例,并保证该实例的访问是线程安全的。工厂模式可以帮助我们创建不同类型的对象,将具体对象的创建逻辑封装在工厂类中,提高了代码的复用性和可维护性。建造者模式可以将对象的创建过程与表示细节解耦,使得代码更加灵活和可拓展。 总之,这些常用的设计模式在软件开发中发挥着重要的作用,可以提高代码的可读性、可维护性和可拓展性。熟练应用这些设计模式可以帮助我们更好地组织和设计代码,提高开发效率和代码质量。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

库里的球衣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值