定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
开闭原则理念就是当需求改变时,希望在不改变原有代码的前提下,通过扩展模块、函数来满足新需求。
开闭原则是其他五大原则的实现,也是面向对象程序设计的终极目标,它使软件实体具有一定的适应性、灵活性的同时具备稳定性和扩展性。
为什么要采用开闭原则?
(1) 开闭原则对测试的影响
已经投入使用的代码都是有意义的,需要经过 高压力、异常、错误 等严苛条件测试,换句话说,已有代码很多都是通过验证的代码,因此当提出新需求或者需求变化时,如果要修改原有代码,则所有关联模块都要重新进行单元测试、功能测试、集成测试以及验收测试,对测试是一种浪费也增加毫无意义的测试成本,如果是通过扩展添加模块或者函数来满足需求,只需要对新添加代码进行测试即可
(2) 提高代码复用性
在程序设计时尽量缩小模块、类、函数的粒度,粒度越小,被复用的可能性就越大,复用的好处?减少代码量、易于维护,避免相同的逻辑代码分散在多处(容易出错,即便是复制代码也很难保证每一处都完全相同),避免后期需求修改、bug修改时要在多处改动代码(更难以保证所有地方都能做到完全一致的修改)
(3) 提高软件的可维护性
遵守开闭原则的软件,稳定性高、扩展性好,易于维护。需求变换、增加需求时如果是通过扩展来满足需求,则维护人员无需完全读懂、理解之前功能模块的代码(有的代码有多优秀,有的代码就有多糟糕),所以相对于修改原有代码,维护人员更加希望是扩展。
如何使用开闭原则
(1) 抽象约束
抽象是对一组事务的通用描述,也就表示他可以有很多的可能性,可以跟随需求的变化而变化,因此通过接口或抽象类可以约束一组可能变化的行为,并且可以实现对扩展开放。
(2) 元数据控制模块行为
元数据:用来描述环境和数据的数据,通俗的说就是配置参数
通过扩展一个子类,修改配置文件,完成业务的变化。
(3) 制定项目章程
规定项目中所有人员必须遵守的约定。
(4) 封装变化
实例
以养宠物为例,养宠物需要喂养、需要陪宠物玩,喂养不同宠物的方式是不同的。
养宠物狗,需要带它出去溜,需要喂食狗粮,
养宠物猫,需要用戏猫玩具逗它,需要喂食猫粮
下面看不遵循开闭原则的实现
假如刚开始只考虑养宠物狗,定义一个宠物类
// 宠物类
public class Pet
{
public Pet(string type)
{ }
// 陪宠物玩
public void PlayWithPet()
{
Console.WriteLine("陪宠物狗玩,需要带它出去溜溜");
}
// 喂食
public void Feed()
{
Console.WriteLine("宠物狗需要用狗粮喂养");
}
}
现在还想再养一只宠物猫,修改宠物类代码
// 宠物类
public class Pet
{
// 宠物类型
private string _type;
// 构造函数传入宠物类型
public Pet(string type)
{
_type = type;
}
// 陪宠物玩
public void PlayWithPet()
{
if (_type.CompareTo("Dog") == 0)
{
Console.WriteLine("陪宠物狗玩,需要带它出去溜溜");
}
else if (_type.CompareTo("Cat") == 0)
{
Console.WriteLine("陪宠物猫玩,需要拿戏猫玩具逗猫玩");
}
}
// 喂食
public void Feed()
{
if (_type.CompareTo("Dog") == 0)
{
Console.WriteLine("宠物狗需要用狗粮喂养");
}
else if (_type.CompareTo("Cat") == 0)
{
Console.WriteLine("宠物猫需要用猫粮喂养");
}
}
}
虽然实现了新需求,但是原有的两个方法 PlayWithPet() 和 Feed() 都进行了修改,并且多了一些不友好的 if else if 代码,可能导致方法修改错误,并且如果再添加其他宠物类型,依然要修改原有正常运行的代码。
由于宠物猫需要买猫砂盆供其上厕所使用,宠物狗不需要,在 宠物类中再添加一个猫砂盆的方法如下
// 猫砂盆
public void CatLitterBox()
{
if (_type.CompareTo("Cat") == 0)
{
Console.WriteLine("猫砂盆供猫上厕所用");
}
}
这样的代码设计上是不好的,那么下面看遵循开闭原则的宠物类如何设计
考虑到不同宠物用到的方法大致相同,但是具体实现各不相同,所以就将宠物类抽象出来,定义一个宠物接口 IPet ,宠物猫、宠物狗具体类分别继承该接口,根据自身特殊性,添加不同的方法,类图如下
代码实现如下
// 宠物类接口
public interface IPet
{
// 陪宠物玩
void PlayWithPet();
// 喂食
void Feed();
}
宠物狗具体类
// 宠物狗
public class Dog : IPet
{
// 陪宠物玩
public void PlayWithPet()
{
Console.WriteLine("陪宠物狗玩,需要带它出去溜溜");
}
// 喂食
public void Feed()
{
Console.WriteLine("宠物狗需要用狗粮喂养");
}
}
宠物猫具体类
// 宠物猫
public class Cat : IPet
{
// 陪宠物玩
public void PlayWithPet()
{
Console.WriteLine("陪宠物猫玩,需要拿戏猫玩具逗猫玩");
}
// 喂食
public void Feed()
{
Console.WriteLine("宠物猫需要用猫粮喂养");
}
// 猫砂盆
public void CatLitterBox()
{
Console.WriteLine("猫砂盆供猫上厕所用");
}
}