【面向对象设计原则】之开闭原则(OCP)
2017-05-23 16:21 蓝之风 阅读(3866) 评论(1) 编辑 收藏 举报开闭原则是面向对象设计的一个重要原则,其定义如下:
开闭原则(Open-Closed Principle, OCP):一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。
在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要对原有代码进行重新测试。那么势必会对软件的开发带来额外的风险和成本, 这是OCP原则要规范设计的一个主要原因,所有的设计原则都是对软件开发,设计以及维护带来好处的,OCP也不例外。
OCP原则是面形对象软件设计的基本原则,其旨在指导如何构建稳定的,灵活的,易于维护的软件。其实这个原则也是我们面向对象设计的一个终极要求,更是一个引导,在软件设计过程中要达到OCP原则其实你需要很好的遵守其他设计原则,换句话说如果其它的设计原则都达标了那么OCP原则自然就达标了,也就是说OCP原则是其他原则综合使用的一个考量和检验。
假如我们要设计一个叫做动物的类(Animal)在这个类中我们有一个方法叫Sound, Sound 方法主要用于发出动物的叫声,通常我们的设计代码如下:
public class Animal { public void Sound(string animal) { switch (animal) { case "dog": System.Console.WriteLine("woof woof woof..."); break; case "cat": Console.WriteLine("miaow miaow miaow..."); break; } } }
客户端的调用代码如下:
class Program { static void Main(string[] args) { Animal animal=new Animal(); animal.Sound("dog"); Console.ReadKey(); } }
调用返回的结果:
这样看起来似乎很完美,如果想听什么动物的声音客户端就传入该动物的名字然后调用Sound方法就可以了。 客户今天只养了两种动物,狗和猫,如果有一天他再养一头羊,他想听到羊的叫声怎么办呢? 直接的想法是在Sound的方法中加一个case子句,写上羊的叫声如下:
public class Animal { public void Sound(string animal) { switch (animal) { case "dog": System.Console.WriteLine("woof woof woof..."); break; case "cat": Console.WriteLine("miaow miaow miaow..."); break; case "sheep": Console.WriteLine("mee-mee mee-mee mee-mee..."); break; } } }
客户端调用如下:
static void Main(string[] args) { Animal animal=new Animal(); animal.Sound("sheep"); Console.ReadKey(); }
输出:
这看起来似乎是很完美,但是我们回过头想一下,好像哪里不对劲,如果后面客户需要加更多的动物该怎么办呢?,是不是这个case要写很长很长,Sound方法每次都要修改,每次都要全部编译整个工程还要重新部署所有的代码,这中间的风险太大,很容易出现操作上的失误,或者代码修改出现bug,要命的是每次都要把整个代码重新测试一遍,给升级带来了很多的工作量,以及潜在的风险。其实再回头看看,我们这个设计是违反OCP原则的, OCP告诉我们对“修改关闭,对扩展开放“,很显然我们这里修改了代码。同时也违背了SRP原则“一个类或方法只负责干一件事情“,显然Sound 方法的职责太多。那么我们有没有办法来重构代码,让其遵守这些原则,每次修改最少的代码即尽可能的减少工作量呢? 答案是肯定的。
我们抽取一个接口叫IAnimal:
public interface IAnimal { void Sound(); }
再分别定义三个类 Dog, Cate 和Sheep 并继承IAnimal 接口:
public class Dog : IAnimal { public void Sound() { Console.WriteLine("woof woof woof..."); } } public class Cat : IAnimal { public void Sound() { Console.WriteLine("miaow miaow miaow..."); } } public class Sheep:IAnimal { public void Sound() { Console.WriteLine("mee-mee mee-mee mee-mee..."); } }
客户端如果想听到狗的叫声的代码调用如下:
static void Main(string[] args) { IAnimal animal=new Dog(); animal.Sound(); Console.ReadKey(); }
输出:
这下是不是比开始好了很多,并且他还很好的满足了单一职责原则(SRP),每个类只负责一种动物发出的声音,职责变得单一了, 但是我们发现如果我们想听到猫的叫声还是要修改Main方法中的调用代码, 还要编译部署,风险还是有点大,工作量还是有点大,那么我们能不能不修改代码只需要改个配置来达到修改Main方法调用的结果呢?这样每次就不用编译只需要修改一下配置就好了呢? 答案是肯定的, 我们利用反射加配置就可以了。 这里我们先加一个工具类用于反射。代码如下:
public class ObjectBuildFactory<T> { public static T Instance(string key) { Type obj = Type.GetType(key); if (obj == null) return default(T); T factory = (T)obj.Assembly.CreateInstance(obj.FullName); return factory; } }
写配置文件如下:
<appSettings> <add key="Animal" value="ConsoleApp1.Dog"/> </appSettings>
调用并通过反射创建对象,调用Dog的叫声如下:
static void Main(string[] args) { string key = ConfigurationManager.AppSettings["Animal"]; IAnimal animal = ObjectBuildFactory<IAnimal>.Instance(key); animal.Sound(); Console.ReadKey(); }
输出:
好了如果希望听到羊的叫声,只需要改一下我们的配置文件就可以了:
<appSettings> <add key="Animal" value="ConsoleApp1.Sheep"/> </appSettings>
其它的代码不需要任何修改直接运行输出如下:
好了这回满足OCP了。
那么好了如果客户期望在增加一种动物,我们应该怎么办呢? 这下就变得非常简单了,我们需要如下两个步骤来完成:
1.增加一个类继承IAnimal接口并实现Sound方法。
2.修改配置文件。
例如我们增加一个动物鸭子代码如下:
public class Duck : IAnimal { public void Sound() { Console.WriteLine("quack quack quack..."); } }
配置:
<appSettings> <add key="Animal" value="ConsoleApp1.Duck"/> </appSettings>
输出:
很简单达到了我们的设计目的。
总结:开闭原则(OCP)是我们在面向对象设计过程中必须注入潜意识的一个原则,在设计的过程中要时时刻刻,如影随形,一旦发现违背就要立即重构,不然代码就会变的越来越不易于理解,越来越不易于维护了。
作者:蓝之风
出处:http://www.cnblogs.com/vaiyanzi/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。