设计模式6大原则-开闭原则

设计模式6大原则-开闭原则

定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。

开闭原则理念就是当需求改变时,希望在不改变原有代码的前提下,通过扩展模块、函数来满足新需求。

开闭原则是其他五大原则的实现,也是面向对象程序设计的终极目标,它使软件实体具有一定的适应性、灵活性的同时具备稳定性和扩展性。

为什么要采用开闭原则?
(1) 开闭原则对测试的影响
已经投入使用的代码都是有意义的,需要经过 高压力、异常、错误 等严苛条件测试,换句话说,已有代码很多都是通过验证的代码,因此当提出新需求或者需求变化时,如果要修改原有代码,则所有关联模块都要重新进行单元测试、功能测试、集成测试以及验收测试,对测试是一种浪费也增加毫无意义的测试成本,如果是通过扩展添加模块或者函数来满足需求,只需要对新添加代码进行测试即可

(2) 提高代码复用性
在程序设计时尽量缩小模块、类、函数的粒度,粒度越小,被复用的可能性就越大,复用的好处?减少代码量、易于维护,避免相同的逻辑代码分散在多处(容易出错,即便是复制代码也很难保证每一处都完全相同),避免后期需求修改、bug修改时要在多处改动代码(更难以保证所有地方都能做到完全一致的修改)

(3) 提高软件的可维护性
遵守开闭原则的软件,稳定性高、扩展性好,易于维护。需求变换、增加需求时如果是通过扩展来满足需求,则维护人员无需完全读懂、理解之前功能模块的代码(有的代码有多优秀,有的代码就有多糟糕),所以相对于修改原有代码,维护人员更加希望是扩展。

如何使用开闭原则
(1) 抽象约束
抽象是对一组事务的通用描述,也就表示他可以有很多的可能性,可以跟随需求的变化而变化,因此通过接口或抽象类可以约束一组可能变化的行为,并且可以实现对扩展开放。

  • 通过接口或抽象类约束扩展,对扩展进行边界限定,不允许出现在接口或抽象类中不存在的 public
  • 参数类型、引用对象尽量使用接口或抽象类,而不是实现类
  • 抽象层尽量保持稳定,一旦确定就不要修改

(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("猫砂盆供猫上厕所用");
        }
    }
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值