设计原则之开闭原则

时间:2022-07-24
本文章向大家介绍设计原则之开闭原则,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

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

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

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

错误的示范

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

需求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格式化的例子。为了更好地支持扩展性,我们对代码进行了重构,重构之后的代码要比之前的代码复杂很多,理解起来也更加有难度。很多时候,我们都需要在扩展性和可读性之间做权衡。在某些场景下,代码的扩展性很重要,我们就可以适当地牺牲一些代码的可读性;在另一些场景下,代码的可读性更加重要,那我们就适当地牺牲一些代码的可扩展性。