浅谈设计模式-开闭原则
作者:互联网
书接上回,本篇继续讲一下设计模式六大原则(有些书认为是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