【面向对象设计原则】之开闭原则(OCP)

开闭原则是面向对象设计的一个重要原则,其定义以下:app

开闭原则(Open-Closed Principle, OCP):一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽可能在不修改原有代码的状况下进行扩展。工具

在软件的生命周期内,由于变化、升级和维护等缘由须要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使咱们不得不对整个功能进行重构,而且须要原有代码通过从新测试。那么势必会对软件的开发带来额外的风险和成本, 这是OCP原则要规范设计的一个主要缘由,全部的设计原则都是对软件的开发,设计以及维护带来好处的,OCP也不例外。测试

OCP原则是面形对象软件设计的基本原则,其旨在指导如何构建稳定的,灵活的,易于维护的软件。其实这个原则也是咱们面向对象设计的一个终极要求,更是一个引导,在软件设计过程当中要达到OCP原则其实你须要很好的遵照其余的设计原则,换句话说若是其它的设计原则都达到了那么OCP原则天然就符合了,也就是说OCP原则是其余原则综合使用的一个考量,一个检验。ui

假如咱们要设计一个叫作动物的类(Animal)在这个类中咱们有一个方法叫Sound, Sound 方法主要用于发出动物的叫声,一般咱们的设计代码以下:spa

    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();
        }
    }

 

调用返回的结果:code

image

这样看起来彷佛很完美,若是想要什么动物发生客户端就传入该动物的名字而后调用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;
            }
        }
    }

客户端调用以下:blog

        static void Main(string[] args)
        {
            Animal animal=new Animal();
            animal.Sound("sheep");
            Console.ReadKey();
        }

输出:继承

image

这看起来彷佛是很完美,可是咱们回过头想一下,好像哪里不对劲,若是后面客户须要加更多的动物怎么办呢?,是否是这个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();
        }

输出:

image

这下是否是比开始好了不少,而且他还很好的知足了单一职责原则(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();
        }

输出:

image

好了若是但愿听到羊的叫声,只须要改一下咱们的配置文件就能够了:

    <appSettings>
        <add key="Animal" value="ConsoleApp1.Sheep"/>
    </appSettings>

image

其它的代码不须要任何修改直接运行输出以下:

image

好了这回知足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>

输出:

image

很简单达到了咱们的设计目的。

总结:开闭原则(OCP)是咱们在面向对象设计过程当中必须注入潜意识的一个原则,在设计的过程当中要时时刻刻,如影随形,一旦发现违背就要当即重构,否则代码就会变的愈来愈不易于理解,愈来愈不易于维护了。