开闭原则
手机用户请
横屏
获取最佳阅读体验,REFRENCES
中是本文参考的链接,如需要链接和更多资源,可百度”Yiyuery”获取,多处同步更新:
定义
开闭原则
是java世界里最基础的设计原则,它指导我们如何建立一个稳定,灵活的系统。开闭原则定义如下:
Software entities like classes,modules and functions should be open for extension but closed for modifications.
一个软件实体如类,模块和函数应该对扩展开放,对修改关闭。
软件实体应该是可以扩展的,但不能因修改而改变它在抽象层次上的确定性。
开闭原则明确的告诉我们:软件实现应该对扩展开放,对修改关闭,其含义是说一个软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化的。那什么是软件实体呢?软件实体包括以下几个部分:
一个软件产品只要在生命周期内,都会发生变化,即然变化是一个事实,我们就应该在设计时尽量适应这些变化,以提高项目的稳定性和灵活性,真正实现“拥抱变化”。开闭原则告诉我们应尽量通过扩展软件实体的行为来实现变化,而不是通过修改现有代码来完成变化,它是为软件实体的未来事件而制定的对现行开发设计进行约束的一个原则。
我们举例说明什么是开闭原则,以书店销售书籍为例,其类图如下:
书籍接口
/*
* @ProjectName: 编程学习
* @Copyright: 2018 HangZhou xiazhaoyang Dev, Ltd. All Right Reserved.
* @address: http://xiazhaoyang.tech
* @date: 2018/11/28 22:34
* @email: xiazhaoyang@live.com
* @description: 本内容仅限于编程技术学习使用,转发请注明出处.
*/package com.example.chapter3.design.ocp;/**
* <p>
*
* </p>
*
* @author xiazhaoyang
* @version V1.0
* @date 2018/11/28 22:34
* @modificationHistory=========================逻辑或功能性重大变更记录
* @modify By: {修改人} 2018/11/28
* @modify reason: {方法名}:{原因}
* ...
*/public interface IBook { /**
* 出售
* @return 收益
*/
double doSell();
}
小说类书籍
/*
* @ProjectName: 编程学习
* @Copyright: 2018 HangZhou xiazhaoyang Dev, Ltd. All Right Reserved.
* @address: http://xiazhaoyang.tech
* @date: 2018/11/28 22:42
* @email: xiazhaoyang@live.com
* @description: 本内容仅限于编程技术学习使用,转发请注明出处.
*/package com.example.chapter3.design.ocp;import lombok.AllArgsConstructor;import lombok.Data;/**
* <p>
*
* </p>
*
* @author xiazhaoyang
* @version V1.0
* @date 2018/11/28 22:42
* @modificationHistory=========================逻辑或功能性重大变更记录
* @modify By: {修改人} 2018/11/28
* @modify reason: {方法名}:{原因}
* ...
*/@Data@AllArgsConstructorpublic class NovelBook implements IBook { private double price; private String name; private String author; @Override
public double doSell() { return getPrice();
} public static void main(String[] args) {
System.out.println(new NovelBook(10.5,"天龙八部","金庸").doSell()); //10.5
}
}
项目投产生,书籍正常销售,但是我们经常因为各种原因,要打折来销售书籍,这是一个变化,我们要如何应对这样一个需求变化呢?
我们有下面三种方法可以解决此问题:
下面是修改后的类图:
打折类
/*
* @ProjectName: 编程学习
* @Copyright: 2018 HangZhou xiazhaoyang Dev, Ltd. All Right Reserved.
* @address: http://xiazhaoyang.tech
* @date: 2018/11/28 22:44
* @email: xiazhaoyang@live.com
* @description: 本内容仅限于编程技术学习使用,转发请注明出处.
*/package com.example.chapter3.design.ocp;import lombok.AllArgsConstructor;import lombok.Data;import lombok.EqualsAndHashCode;/**
* <p>
*
* </p>
*
* @author xiazhaoyang
* @version V1.0
* @date 2018/11/28 22:44
* @modificationHistory=========================逻辑或功能性重大变更记录
* @modify By: {修改人} 2018/11/28
* @modify reason: {方法名}:{原因}
* ...
*/@EqualsAndHashCode(callSuper = true)@Datapublic class DiscountNovelBook extends NovelBook { private double discount; public DiscountNovelBook(double discount) { super(12.5,"天龙八部","金庸"); this.discount = discount;
} @Override
public double doSell() { return super.doSell() * discount;
} public static void main(String[] args) {
DiscountNovelBook discountNovelBook = new DiscountNovelBook(0.5);
System.out.println(discountNovelBook.doSell());//6.25
}
}
现在打折销售开发完成了,我们只是增加了一个DiscountNovelBook类,我们修改的代码都是高层次的模块,没有修改底层模块,代码改变量少,可以有效的防止风险的扩散。 我们可以把变化归纳为二种类型:
抽象是对一组事物的通用描述,没有具体的实现,也就表示它可以有非常多的可能性,可以跟随需求的变化而变化。因此,通过接口或抽象类可以约束一组可能变化的行为,并且能够实现对扩展开放,其包含三层含义:
编程是一个很苦很累的活,那怎么才能减轻压力呢?答案是尽量使用元数据来控制程序的行为,减少重复开发。什么是元数据?用来描述环境和数据的数据,通俗的说就是配置参数,参数可以从文件中获得,也可以从数据库中获得。
在一个团队中,建立项目章程是非常重要的,因为章程是所有人员都必须遵守的约定,对项目来说,约定优于配置。这比通过接口或抽象类进行约束效率更高,而扩展性一点也没有减少。
对变化封装包含两层含义: (1)将相同的变化封装到一个接口或抽象类中 (2)将不同的变化封装到不同的接口或抽象类中,不应该有两个不同的变化出现在同一个接口或抽象类中。 封装变化,也就是受保护的变化,找出预计有变化或不稳定的点,我们为这些变化点创建稳定的接口。
问题由来
解决方案
当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
开闭原则是面向对象设计中最基础的设计原则,它指导我们如何建立稳定灵活的系统。开闭原则可能是设计模式六项原则中定义最模糊的一个了,它只告诉我们对扩展开放,对修改关闭,可是到底如何才能做到对扩展开放,对修改关闭,并没有明确的告诉我们。以前,如果有人告诉我”你进行设计的时候一定要遵守开闭原则”,我会觉的他什么都没说,但貌似又什么都说了。因为开闭原则真的太虚了。
其实开闭原则
无非就是想表达这样一层意思:用抽象构建框架,用实现扩展细节。因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节,我们用从抽象派生的实现类来进行扩展,当软件需要发生变化时,我们只需要根据需求重新派生一个实现类来扩展就可以了。当然前提是我们的抽象要合理,要对需求的变更有前瞻性和预见性才行。
至此开闭原则的示例就基本完了。细心的朋友可能觉察到了,以面向抽象编程的方式去达到对扩展开放的目的,这和我们的依赖倒置原则(高层模块不应该依赖低层模块,两者都应该依赖其抽象)怎么这么像。可能有朋友就要不满了,不就是一个面向抽象编程,玩这么多花样干嘛,将简单的问题复杂化。博主的理解是这些设计原则没有绝对的界限,它们只是从不同的侧重点去约束我们的软件架构,使其能使用各种不同的需求。
说到这里,再简要介绍下其他5项原则,它们主要是告诉我们用抽象构建框架,用实现扩展细节的注意事项而已:单一职责原则告诉我们实现类要职责单一;里氏替换原则告诉我们不要破坏继承体系;依赖倒置原则告诉我们要面向接口编程;接口隔离原则告诉我们在设计接口的时候要精简单一;迪米特法则告诉我们要降低耦合。而开闭原则是总纲,他告诉我们要对扩展开放,对修改关闭。
最后说明一下如何去遵守这六个原则。对这六个原则的遵守并不是是和否的问题,而是多和少的问题,也就是说,我们一般不会说有没有遵守,而是说遵守程度的多少。任何事都是过犹不及,设计模式的六个设计原则也是一样,制定这六个原则的目的并不是要我们刻板的遵守他们,而需要根据实际情况灵活运用。对他们的遵守程度只要在一个合理的范围内,就算是良好的设计。