专栏首页求道设计原则之开闭原则

设计原则之开闭原则

基本介绍:对扩展开放对修改关闭,用抽象构建框架 用实现扩展细节!

所谓对扩展开放,对修改关闭,其实是设计模式里面所重点提倡的,后续所有涉及模式的介绍其实都是为了程序能够更好的扩展,提倡设计模式的本质就是为了减少 增加一点功能而修改系统的主要逻辑!

用抽象构建框架 用实现扩展细节! 尽量通过扩展类的操作来实现行为变化,而不是通过修改已有代码来实现修改

错误的示范

下面还是老规矩,我们先看一段错误的代码!

需求1:我需要一个过滤特殊字符的功能

package principle.opencloeds.error;

/**
 * 字符串格式化
 * 规则:
 *  1.剔除 html标签
 * @author huangfu
 */
public class StringFormat {

    /**
     * 格式化字符串
     * 剔除 html标签
     * @param data 格式化的元数据
     * @return 格式化后的数据
     */
    public String stringFormat(String data) {
        if (data != null) {
            return return data.replaceAll("</?[^>]+>", "").replaceAll("\\s*|\t|\r|\n", "");
        }
        return null;
    }
}

测试代码

package principle.opencloeds.error.test;

import principle.opencloeds.error.StringFormat;

/**
 * 数据格式化测试
 * @author huangfu
 */
public class TestStringFormat {
    private static final String MSG = "<span>程序必须首先让人类理解,然后顺便让机器能执行</span>/96";
    public static void main(String[] args) {
        StringFormat stringFormat = new StringFormat();
        System.out.println(stringFormat.stringFormat(MSG));
    }
}

测试结果

程序必须首先让人类理解,然后顺便让机器能执行/96

Process finished with exit code 0

这样写乍一看没有任何问题,功能也实现了,但是突然有一天,领导需要你将一些特殊字符替换成特定的字符比如:/96替换成 ^_^

那么苦逼的程序员又要去更改整个代码逻辑,于是代码被改成这样!

package principle.opencloeds.error;

/**
 * 字符串格式化
 * 规则:
 *  1.剔除  < 或者 >  字符
 * @author huangfu
 */
public class StringFormat {

    /**
     * 格式化字符串
     * 剔除  < 或者 >  字符
     * @param data 格式化的元数据
     * @return 格式化后的数据
     */
    public String stringFormat(String data) {
        if (data != null) {
            String specialCharactersFormat = data.replace("/96", "^_^");
            return specialCharactersFormat.replaceAll("</?[^>]+>", "").replaceAll("\\s*|\t|\r|\n", "");
        }
        return null;
    }
}

测试结果

程序必须首先让人类理解,然后顺便让机器能执行^_^

Process finished with exit code 0

开发中唯一不变的就是变化,所以我们不敢保证那一次领导又有新的需求来让你修改,每一次都修改代码是很伤的,其实上述例子还好,只是修改代码逻辑,如果有时候连参数都要改变的话,你一旦修改参数,对上层系统很不友好,对于参数的改变有两种解决方案:

  • 方法重载
  • 将参数封装为一个对象,每个方法只获取自己感兴趣的参数

参数替换的方法我不做过多讲解,上面的两种方案其实说的很明白,有兴趣的读者可以自己实现一下试试,我们继续聊现有的一段逻辑!

还记得我开头说的一句话吗?

用抽象构建框架 用实现扩展细节!

对,我们需要将经常变化的逻辑抽取出来,作为实现使用,这些经常改变的代码就是我们代码里面的一些扩展点!我们不妨尝试一下,抽象一下代码,让他的扩展性更好!

正确的代码

抽象出来一个接口,用于构建整个程序的框架!

package principle.opencloeds.correct;

/**
 * 字符串格式化接口
 * @author huangfu
 */
public interface StringFormat {
    /**
     * 是否生效
     * @return true生效反之不生效
     */
    boolean enable();

    /**
     * 字符串格式化逻辑
     * @param data 元数据
     * @return 格式化后的数据
     */
    String stringFormat(String data);
}

定制接口的实现,通过扩展类的操作来实现行为变化!

HTML标签过滤

package principle.opencloeds.correct;

/**
 * html的格式化
 */
public class HtmlStringFormat implements StringFormat {
    @Override
    public boolean enable() {
        return true;
    }

    @Override
    public String stringFormat(String data) {
        return data.replaceAll("</?[^>]+>", "").replaceAll("\\s*|\t|\r|\n", "");
    }
}
package principle.opencloeds.correct;

/**
 * 特殊字符格式化
 * @author huangfu
 */
public class SpecialStringFormat implements StringFormat {

    @Override
    public boolean enable() {
        return true;
    }

    @Override
    public String stringFormat(String data) {
        return data.replace("/96", "^_^");
    }
}

定义一个使用的工具类

package principle.opencloeds.correct;

import java.util.ArrayList;
import java.util.List;

/**
 * 格式化上下文
 * @author huangfu
 */
public class StringFormatUtil {
    private static final List<StringFormat> stringFormats = new ArrayList<>(8);

    public static void addFormatSpecification(StringFormat StringFormat) {
        if (StringFormat != null) {
            stringFormats.add(StringFormat);
        }
    }

    public static String stringFormat(String data) {
        for (StringFormat stringFormat : stringFormats) {
            if (stringFormat.enable()) {
                data = stringFormat.stringFormat(data);
            }
        }
        return data;
    }
}

测试结果

package principle.opencloeds.correct;

/**
 * @author huangfu
 */
public class TestStringFormat {
    private static final String MSG = "<span>程序必须首先让人类理解,然后顺便让机器能执行</span>/96<div>脏话</div>";
    public static void main(String[] args) {
        StringFormatUtil.addFormatSpecification(new HtmlStringFormat());
        StringFormatUtil.addFormatSpecification(new SpecialStringFormat());
        System.out.println(StringFormatUtil.stringFormat(MSG));
    }
}
程序必须首先让人类理解,然后顺便让机器能执行^_^脏话

Process finished with exit code 0

这样写咋一看比之前增加了很多的类,逻辑也变多了,但是如果上级要求将一些敏感字眼用xxx替换,我们再也不需要修改代码原有的逻辑只需要这样!

增加敏感词处理类

package principle.opencloeds.correct;

/**
 * 敏感词过滤替换
 * @author huangfu
 */
public class SensitiveStringFormat implements StringFormat {
    @Override
    public boolean enable() {
        return true;
    }

    @Override
    public String stringFormat(String data) {
        if (data != null) {
            return data.replace("脏话","xxxx");
        }
        return null;
    }
}

测试使用

package principle.opencloeds.correct;

/**
 * @author huangfu
 */
public class TestStringFormat {
    private static final String MSG = "<span>程序必须首先让人类理解,然后顺便让机器能执行</span>/96<div>脏话</div>";
    public static void main(String[] args) {
        StringFormatUtil.addFormatSpecification(new HtmlStringFormat());
        StringFormatUtil.addFormatSpecification(new SpecialStringFormat());
        StringFormatUtil.addFormatSpecification(new SensitiveStringFormat());
        System.out.println(StringFormatUtil.stringFormat(MSG));
    }
}
程序必须首先让人类理解,然后顺便让机器能执行^_^xxxx

Process finished with exit code 0

总结

通过上述代码可以知道,开闭原则是是为了避免过多的修改原有的代码逻辑,用扩展代替修改而衍生的一个原则,不可否认,他也是所有的设计模式都想表达的一个结果,高扩展性。

对拓展开放是为了应对变化(需求),对修改关闭是为了保证已有代码的稳定性;最终结果是为了让系统更有弹性!

开闭原则也并不是免费的。有些情况下,代码的扩展性会跟可读性相冲突。比如,我们之前举的 StringFormat格式化的例子。为了更好地支持扩展性,我们对代码进行了重构,重构之后的代码要比之前的代码复杂很多,理解起来也更加有难度。很多时候,我们都需要在扩展性和可读性之间做权衡。在某些场景下,代码的扩展性很重要,我们就可以适当地牺牲一些代码的可读性;在另一些场景下,代码的可读性更加重要,那我们就适当地牺牲一些代码的可扩展性。

本文分享自微信公众号 - JAVA程序狗(javacxg),作者:皇甫嗷嗷叫

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-06-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 设计原则之单一职责

    上述就违反了单一职责原则,对于不同的交通工具,代码逻辑完全耦合在一起,我们无论修改那一类的交通工具,都会影响其他两种数据

    止术
  • SpringBoot对全局异常的处理封装

    springMvc为我们提供的全局异常处理很好的解决了我们在开发中对于异常的捕获与控制,他能够监控所有的@Controller注解类,并提供异常捕捉与处理!

    止术
  • 设计模式之单例模式

    小弟最近在研究设计模式,准备边学边发博客,与众多大佬们交流学习,希望各位能够指出不足之处(废话不多说了,直接开花)** **

    止术
  • 如何基于SpringBoot返回一个json对象

    开发过程中,接口是必不可少的,那么提前约定数据格式就成了必不可少的步骤。一般情况下大家都是用json格式来传递数据,今天就用spring boot来实现一下返回...

    用户1956326
  • Java泛型你看这篇文章就对了

    泛型是Java中一个非常重要的知识点,在Java集合类框架中泛型被广泛应用。本文我们将从零开始来看一下Java泛型的设计,将会涉及到通配符处理,以及让人苦恼的类...

    JAVA葵花宝典
  • 切面编程(环绕通知与前后置通知区别)

    1、拥有前置通知和后置通知的功能,并能解决前置通知和后置通知在共享信息方面的不足(例如:统计切点方法执行时间);

    yaphetsfang
  • 关于Socket高并发的原理介绍及使用Apache Mina带来线上的问题分析

    上周在线上出现了一个很低级的问题,但是正是这个低级的问题引起了我的兴趣,其实所谓的低级是因为配置文件配置错了,原本线上是为每个客户端设置了一个席位,就说是客户端...

    用户1257215
  • 设计模式(终)

    “ 这一篇文章把剩下剩下的行为型设计模式全部讲完,然后设计模式这一个模块就算结束了,在后面抽出时间再整理一下”

    每天学Java
  • 猿思考系列5——一文明白java和微商那点儿事儿

    看完上一个章节,相信你已经掌握了一些编写并发代码编写的要领了。今天我们来聊一个新的话题。另外真的很感谢大家的支持,和巨兽的斗争暂时进入僵持阶段,猿人工厂君已经说...

    山旮旯的胖子
  • java — 设计模式

    Mister24

扫码关注云+社区

领取腾讯云代金券