其他分享
首页 > 其他分享> > 浅谈设计模式-开闭原则

浅谈设计模式-开闭原则

作者:互联网

书接上回,本篇继续讲一下设计模式六大原则(有些书认为是7大原则)

原则定义

开闭原则(Open Closed Principle,OCP),

原话:Software entities should be open for extension,but closed for modification

翻译:软件实体应当对扩展开放,对修改关闭。

大白话:当项目需求变动时,在不修改源代码前提下,通过增加新类/新方法/新模块等方式满足新的需求。

开闭原则算是编程中最基本,最重要的设计原则,前面讲的6个设计原则,跟后面讲设计模式目的都是为让程序(架构)能遵循开闭原则。

开闭原则实现

开闭原则实现方式大同小异,但基本都是围绕着 抽象约束,封装变化 这个思想展开的。怎么理解?即通过接口/抽象类来定义一个统一的规范,对功能做约束,而需要变动的因素或个体差异通过具体实现类/子类体现。

注意,不仅限于接口,抽象类了,组合方式也可以。

案例分析

需求:饲养员喂动物

public class Dog {
    public void dogEat(){
        System.out.println("狗吃骨头...");
    }
}
public class Cat{
    public void catEat() {
        System.out.println("猫吃鱼...");
    }
}
/**
 * 饲养员
 */
public class Keeper {
    public void feedDog(Dog dog){
        dog.dogEat();
    }
    public void feedCat(Cat cat){
        cat.catEat();
    }
}

测试:

public class App {
    public static void main(String[] args) {
        Cat cat = new Cat();
        Dog dog = new Dog();
        Keeper keeper = new Keeper();
        keeper.feedCat(cat);
        keeper.feedDog(dog);
    }
}
结果:
狗吃骨头...
猫吃鱼...

解析

案例是一个很简单的例子,定义猫狗类跟饲养员类,饲养员养猫(feedCat),养狗(feedDog)貌似没多大问题,如果后续添加了养猪,养鱼养其他动物呢?那此时就需要改动饲养员(Keeper)这个类的代码啦。如下:

public class Fish {
    public void fishEat() {
        System.out.println("鱼吃虾米...");
    }
}
public class Keeper {
    public void feedDog(Dog dog){
        dog.dogEat();
    }
    public void feedCat(Cat cat){
        cat.catEat();
    }
    public void feedFish(Fish fish){
        fish.fishEat();
    }
}

类比开发,饲养员养猫,养狗就是目前现有系统,继续养猪,养鱼,养其他动物,那就是需求变化,而改动饲养员(Keeper)这个类就是对原来代码的变动。这设计就违背开闭原则了。

开:对拓展开放, 养其他动物,需要改动keeper类,不符合

闭:对修改闭合, 动物怎么吃饲料,饲养员怎么喂动物,这个代码完成之后就固定了,即使后续业务发生变化,只要方法前面不变动,对App类,keeper类来说,感知不到,代码不需要变动。符合

就上面的问题,如何调整代码能满足开闭原则呢?如下:

改进

/**
 * 动物
 */
public abstract class Animal {
    /**
     * 吃
     */
    public abstract  void eat();
}
public class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("猫吃鱼...");
    }
}
public class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("狗吃骨头...");
    }
}
/**
 * 饲养员
 */
public class Keeper {
    public void feed(Animal animal){
        animal.eat();
    }
}

测试:

public class App {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();
        Keeper keeper = new Keeper();
        keeper.feed(dog);
        keeper.feed(cat);
    }
}

解析

多定义一个类:Animal类,里面声明一个抽象方法:eat,作为 吃 这个行为动作的约束,子类可以根据需求拓展该行为。饲养员则使用继承多态的方式,喂养各种类型动物。

当需要喂养的动物不仅限于猫狗时,此时只需要再拓展一个新的Animal子类即可,现有的类不需要在做任何变动。

public class Fish extends Animal {
    @Override
    public void eat() {
        System.out.println("鱼吃虾米...");
    }
}
/**
 * 饲养员
 */
public class Keeper {
    public void feed(Animal animal){
        animal.eat();
    }
}

这就是我们说的当需求需要变动时,维护旧代码不变,增加新代码就可以满足需求拓展,而Fish就是新增的代码。

开闭原则的作用

开闭原则是面向对象程序设计的终极目标,要求程序(架构)既要拥有一定的适应性和灵活性,又要具备稳定性和延续性。

方便测试

遵循开闭原则的程序模块基本不需要进行测试,只需要独立测试拓展模块即可,回归测试都可以省了。

提高复用性

符合开闭原则的组件设计,一般都是高内聚低耦合的设计。

提高可维护性 对拓展开发,对修改关闭,旧代码,新代码分开维护即可,出问题那肯定是新代码问题。

运用

接着我们从JDK里面找案例:IO流

jdk中IO流使用了经典设计模式:装饰模式,这里我们先不展开讲这个模式,单纯说代码如何体现开闭原则

IO体系分:InputStream体系 跟 OutputStream体系,以inputStream为例子

InputStream 体系以InputStream 为父类,对输入动作做规定

public abstract int read();

其子类分2种:

普通子类

ByteArrayInputStream:拓展了InputStream read方法,实现字节数组读取

public synchronized int read() {
    return (pos < count) ? (buf[pos++] & 0xff) : -1;
}

FileInputStream:拓展了InputStream read方法,实现文件对象读取

public int read() throws IOException {
    return read0();
}

private native int read0() throws IOException;

....(还是很多,不列举了)

装饰子类

装饰子类,是在普通子类基础上再进一步做功能增强,代表类有:

BufferedInputStream: 在FileInputStream 的基础上做缓存读取操作。

BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(new File("文件路径")));

原理非常简单:

BufferedInputStream 定义一个InputStream 接受要装饰的输入流对象

protected volatile InputStream in;
public BufferedInputStream(InputStream in) {
    this(in, DEFAULT_BUFFER_SIZE);
}
public BufferedInputStream(InputStream in, int size) {
    super(in);
    if (size <= 0) {
        throw new IllegalArgumentException("Buffer size <= 0");
    }
    buf = new byte[size];
}
protected FilterInputStream(InputStream in) {
    this.in = in;
}

执行读取操作时,使用原装的输入流进行输入

public int read() throws IOException {
    return in.read();
}

分析

IO输入流程的设计跟开闭原则有啥关系呢?细细品一下,如果JDK要增加一个新的读取流要改变原先代码么?不用,比如增加一个读取压缩文件的流:ZipFileInputStream, 直接拓展即可。那不就是对修改关闭,对拓展开发么?

再比如FilterInputStream类的功能不足,使用装饰模式(简单可以理解组合方式)一样拓展原有的类功能:BufferedInputStream也一样,没有涉及到原有代码修改,直接加新代码。这就是开闭原则。

总结

开闭原则原则使用过程中要明确的:

开:强调拓展开发,增加新代码方式

闭:强调旧代码不变(或者内部改动,对外部透明)

这里强调开闭原则,不仅限于接口,抽象类了,其他方式也可以,比如组合方式。

标签:代码,浅谈,原则,void,class,设计模式,public,开闭
来源: https://blog.csdn.net/langfeiyes/article/details/121353663