ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

浅谈设计模式-开闭原则

2021-11-16 13:34:27  阅读:183  来源: 互联网

标签:代码 浅谈 原则 void class 设计模式 public 开闭


书接上回,本篇继续讲一下设计模式六大原则(有些书认为是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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有