精华内容
下载资源
问答
  • 开闭原则

    2019-01-09 22:35:09
    设计模式六大原则之六:开闭原则

    前方高能《一故事一设计模式》PDF 电子书已经上线,关注公众号即可获取。

    个人博客原文:
    开闭原则

    景

    设计模式六大原则之六:开闭原则。

    简介

    姓名 :开闭原则

    英文名 :Open Closed Principle

    价值观 :老顽童就是我,休想改变我

    个人介绍

    Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.(软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的)
    (来自维基百科)

    停更了三四天了,这几天比较忙,不仅仅是工作上,更多是精神上。周日突然老胃病又复发了,一直疼到凌晨 4,5 点。因为这次疼得蛮厉害的,所以准备去医院看一下医生,这时候才体验到大城市就医之苦。周日晚下载了微医 App (不是做广告哈),也不知道哪家医院好,在深圳两年半还没去过医院,随便选个三甲医院:北京大学深圳医院,看了消化内科门诊的医生列表,整整这一周主任医生都预约满了,顿时很崩溃,打电话给医院预约,最快只能预约 17 号,are you kidding?App 上有个 『立即问诊』功能,在线把状况告诉医生,医生一天之内接诊,需要花 60 块,我就尝试一下,没想到第二天医生回复后,说可以下午去医院看,他可以临时加号。就这样跳过了预约,直接看病,不知道你是否也苦于看病烦,可以尝试这个方法,当然,如果你有更好的方法,可以留言让更多的人了解到。

    跑题了跑题了,今天是想和大家分享设计模式最后一个原则:开闭原则。这个原则要求就是允许扩展,拒绝修改。既然上面讲到看医生,那就用一个跟看病有关的例子。

    故事从这里开始

    小明去医院看病,医生开了阿司匹林药,小明去了收费台,付了钱,总共 20 块钱。例子的代码如下:

    public class OcpTest {
    
        public static void main(String[] args) {
            Hospital hospital = new Hospital();
            IPatient xiaoMing = new Patient("小明");
            hospital.sellMedicine(xiaoMing);
        }
    
    }
    
    
    class Medicine {
        private String name;
        private BigDecimal price;
    
        public Medicine(String name, BigDecimal price) {
            this.name = name;
            this.price = price;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public BigDecimal getPrice() {
            return price;
        }
    
        public void setPrice(BigDecimal price) {
            this.price = price;
        }
    }
    
    class Hospital {
    
        private Medicine medicine = new Medicine("阿司匹林", new BigDecimal(20));
    
        public void sellMedicine(IPatient patient) {
            BigDecimal money = patient.pay(medicine);
            System.out.println(patient.getName() + " 花了 " + money.setScale(2, BigDecimal.ROUND_UP) + " 块钱买了药:" + medicine.getName());
        }
    
    }
    
    interface IPatient {
        String getName();
        BigDecimal pay(Medicine medicine);
    }
    
    class Patient implements IPatient{
    
        private String name;
    
        public Patient(String name) {
            this.name = name;
        }
    
        @Override
        public BigDecimal pay(Medicine medicines) {
            return medicines.getPrice();
        }
    
        @Override
        public String getName() {
            return name;
        }
    
    }
    

    第二天和朋友聚会聊起这事,小红说道:不对呀,前几天我在医院也拿了阿司匹林药,才 14 块钱呢。小花说:奇怪了,我买的是 16 块钱。小杰回应:怎么我买的是 18 块。怎么这药这么多个价格。小明 Google 搜了一下,发现价格跟社保有关,几个人便发现,原来他们都是“不同人”:小明没有社保,小红社保是一档,小花社保是二挡,小杰社保是三挡。(假设社保一档打 7 折,社保二挡打 8 折,社保三挡打 9 折,虚拟的哈)
    发现了这秘密后,作为和 IT 工作相关的人,便讨论起医院系统具体实现是怎么实现的。小红说:这很简单呢,药品给不同人提供不同的价格。代码如下:

    class Medicine {
        private String name;
        private BigDecimal price;
    
        public Medicine(String name, BigDecimal price) {
            this.name = name;
            this.price = price;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public BigDecimal getPrice() {
            return price;
        }
        
        public BigDecimal getPrice1() {
            return price.multiply(new BigDecimal(0.7));
        }
        
        public BigDecimal getPrice2() {
            return price.multiply(new BigDecimal(0.8));
        }
        
        public BigDecimal getPrice3() {
            return price.multiply(new BigDecimal(0.9));
        }
    
        public void setPrice(BigDecimal price) {
            this.price = price;
        }
    }
    

    小花说:药片本身的价格是不会变的,只是给不同人不同价格,所以可以在病人获取价钱的时候去区分。代码如下:

    class Patient implements IPatient{
    
        private String name;
        private int level;
    
        public Patient(String name) {
            this.name = name;
        }
    
        @Override
        public BigDecimal pay(Medicine medicines) {
            if (level == 1) {
                return medicines.getPrice().multiply(new BigDecimal(0.7));
            } else if (level == 2) {
                return medicines.getPrice().multiply(new BigDecimal(0.8));
            } else if (level == 3) {
                return medicines.getPrice().multiply(new BigDecimal(0.9));
            }
            return medicines.getPrice();
        }
    
        @Override
        public String getName() {
            return name;
        }
    
    }
    

    小杰陷入了沉思。。。
    小明发话:你们说的方法都可以实现,但是总感觉不对劲,如果以后有社保四挡,还是要修改原来的代码,前 2 天设计模式老师讲的开闭原则忘记了么?里面说要对扩展开放,对修改封闭。我觉得这个药片价格是因为我们人而变的,那是不是我们可以把没社保的归为一类人,一档社保的也为一类,以此类推。我觉得这样实现更好,增加多 3 类病人,分别是一档社保、二挡社保、三挡社保。代码如下:

    class OneLevelSocialSecurityPatient implements IPatient {
    
        private String name;
    
        public OneLevelSocialSecurityPatient(String name) {
            this.name = name;
        }
    
        @Override
        public BigDecimal pay(Medicine medicine) {
            return medicine.getPrice().multiply(new BigDecimal(0.7));
        }
    
        @Override
        public String getName() {
            return this.name;
        }
    }
    
    class TwoLevelSocialSecurityPatient implements IPatient {
    
        private String name;
    
        public TwoLevelSocialSecurityPatient(String name) {
            this.name = name;
        }
    
        @Override
        public BigDecimal pay(Medicine medicine) {
            return medicine.getPrice().multiply(new BigDecimal("0.8"));
        }
    
        @Override
        public String getName() {
            return this.name;
        }
    }
    
    class ThreeLevelSocialSecurityPatient implements IPatient {
    
        private String name;
    
        public ThreeLevelSocialSecurityPatient(String name) {
            this.name = name;
        }
    
        @Override
        public BigDecimal pay(Medicine medicine) {
            return medicine.getPrice().multiply(new BigDecimal("0.9"));
        }
    
        @Override
        public String getName() {
            return this.name;
        }
    }
    
    // 测试代码
    public static void main(String[] args) {
    	Hospital hospital = new Hospital();
    	IPatient xiaoMing = new Patient("小明");
    	hospital.sellMedicine(xiaoMing);
    
    	IPatient xiaoHong = new OneLevelSocialSecurityPatient("小红");
    	hospital.sellMedicine(xiaoHong);
    
    	IPatient xiaoHua = new TwoLevelSocialSecurityPatient("小花");
    	hospital.sellMedicine(xiaoHua);
    
    	IPatient xiaoJie = new ThreeLevelSocialSecurityPatient("小杰");
    	hospital.sellMedicine(xiaoJie);
    }
    

    代码:
    OcpTest.java

    看了他们的对话和代码,是不是能知道哪种方式更好了?对于小红来说,她没理清价格变化的原因,价格变化不在于药片;小花理清了,但是实现方式差了点,以后如果新增了四挡社保,她的实现要修改原有的代码,不符合开闭原则;小明的方法就符合开闭原则,如果新增四挡社保人员,他的方法只需要再额外扩展一个四挡社保人员就可以,不用动用其他代码。

    用了这个大家可能不太喜欢的看病的场景来描述这个开闭原则,不要忌讳哈,希望大家都健健康康,远离医院。

    总结

    重申一下:对扩展开放,对修改封闭。如果有同学经常看一些开源框架源码就会发现,有很多很多抽象类和接口,debug 进去很绕,其实这些抽象类和接口很多都是为了扩展用,因为作为开源框架,不得不实现各种可想象到的方案,而这些都基于开闭原则来实现的。以后有机会也可以写一下源码的文章分享给大家。

    参考资料:《大话设计模式》、《Java设计模式》、《设计模式之禅》、《研磨设计模式》、《Head First 设计模式》

    这周事情比较多,更新会不及时,周五还要出差去一趟上海,周六回深圳,周日回一趟老家,各种奔波。。。

    希望文章对您有所帮助,设计模式系列会持续更新,感兴趣的同学可以关注公众号,第一时间获取文章推送阅读,也可以一起交流,交个朋友。

    公众号之设计模式系列文章

    公众号

    展开全文
  • 设计模式六大原则(6):开闭原则

    万次阅读 多人点赞 2012-02-27 08:48:41
    定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。 问题由来:在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,... 开闭原则是面向对象设计中

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

    问题由来:在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。

    解决方案:当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。

             开闭原则是面向对象设计中最基础的设计原则,它指导我们如何建立稳定灵活的系统。开闭原则可能是设计模式六项原则中定义最模糊的一个了,它只告诉我们对扩展开放,对修改关闭,可是到底如何才能做到对扩展开放,对修改关闭,并没有明确的告诉我们。以前,如果有人告诉我“你进行设计的时候一定要遵守开闭原则”,我会觉的他什么都没说,但貌似又什么都说了。因为开闭原则真的太虚了。

             在仔细思考以及仔细阅读很多设计模式的文章后,终于对开闭原则有了一点认识。其实,我们遵循设计模式前面5大原则,以及使用23种设计模式的目的就是遵循开闭原则。也就是说,只要我们对前面5项原则遵守的好了,设计出的软件自然是符合开闭原则的,这个开闭原则更像是前面五项原则遵守程度的“平均得分”,前面5项原则遵守的好,平均分自然就高,说明软件设计开闭原则遵守的好;如果前面5项原则遵守的不好,则说明开闭原则遵守的不好。

             其实笔者认为,开闭原则无非就是想表达这样一层意思:用抽象构建框架,用实现扩展细节。因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节,我们用从抽象派生的实现类来进行扩展,当软件需要发生变化时,我们只需要根据需求重新派生一个实现类来扩展就可以了。当然前提是我们的抽象要合理,要对需求的变更有前瞻性和预见性才行。

             说到这里,再回想一下前面说的5项原则,恰恰是告诉我们用抽象构建框架,用实现扩展细节的注意事项而已:单一职责原则告诉我们实现类要职责单一;里氏替换原则告诉我们不要破坏继承体系;依赖倒置原则告诉我们要面向接口编程;接口隔离原则告诉我们在设计接口的时候要精简单一;迪米特法则告诉我们要降低耦合。而开闭原则是总纲,他告诉我们要对扩展开放,对修改关闭。

             最后说明一下如何去遵守这六个原则。对这六个原则的遵守并不是是和否的问题,而是多和少的问题,也就是说,我们一般不会说有没有遵守,而是说遵守程度的多少。任何事都是过犹不及,设计模式的六个设计原则也是一样,制定这六个原则的目的并不是要我们刻板的遵守他们,而需要根据实际情况灵活运用。对他们的遵守程度只要在一个合理的范围内,就算是良好的设计。我们用一幅图来说明一下。

     

     

            图中的每一条维度各代表一项原则,我们依据对这项原则的遵守程度在维度上画一个点,则如果对这项原则遵守的合理的话,这个点应该落在红色的同心圆内部;如果遵守的差,点将会在小圆内部;如果过度遵守,点将会落在大圆外部。一个良好的设计体现在图中,应该是六个顶点都在同心圆中的六边形。

     

            在上图中,设计1、设计2属于良好的设计,他们对六项原则的遵守程度都在合理的范围内;设计3、设计4设计虽然有些不足,但也基本可以接受;设计5则严重不足,对各项原则都没有很好的遵守;而设计6则遵守过渡了,设计5和设计6都是迫切需要重构的设计。

             到这里,设计模式的六大原则就写完了。主要参考书籍有《设计模式》《设计模式之禅》《大话设计模式》以及网上一些零散的文章,但主要内容主要还是我本人对这六个原则的感悟。写出来的目的一方面是对这六项原则系统地整理一下,一方面也与广大的网友分享,因为设计模式对编程人员来说,的确非常重要。正如有句话叫做一千个读者眼中有一千个哈姆雷特,如果大家对这六项原则的理解跟我有所不同,欢迎留言,大家共同探讨。

     下面是前面5项设计原则的链接

    1.  单一职责原则(Single Responsibility Principle)

    2.  里氏替换原则(Liskov Substitution Principle)

    3.  依赖倒置原则(Dependence Inversion Principle)

    4.  接口隔离原则(Interface Segregation Principle)

    5.  迪米特法则(Law Of Demeter)

    同时为了方便想收藏的朋友,下面给出word版本的下载。

    word版本下载链接:设计模式六大原则

     

     

    展开全文

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 12,415
精华内容 4,966
热门标签
关键字:

开闭原则