设计模式原则之开闭原则

定义

Software entities like classes,modules and functions should be open for extension but closed for modifications.

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


概念理解

开闭原则明确的告诉我们:软件实现应该对扩展开放,对修改关闭,其含义是说一个软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化的。

那什么是软件实体呢?

1.项目或软件产品中按照一定的逻辑规则划分的模块

2.抽象和类

3.方法

一个软件产品只要在生命周期内,都会发生变化,即然变化是一个事实,我们就应该在设计时尽量适应这些变化,以提高项目的稳定性和灵活性,真正实现“拥抱变化”。开闭原则告诉我们应尽量通过扩展软件实体的行为来实现变化,而不是通过修改现有代码来完成变化,它是为软件实体的未来事件而制定的对现行开发设计进行约束的一个原则。


模拟场景

假设你是一个书店老板,你平时正常销售书籍


场景代码

@protocol Book

-(NSString *)getName;

-(float)getPrice;

-(NSString *)getAuthor;

@end

#import "Book.h"

@interface BookObject : NSObject

- (instancetype)initWithName:(NSString *)name price:(float)price author:(NSString *)author NS_DESIGNATED_INITIALIZER;

@end

#import "BookObject.h"

@interface BookObject()

@property (nonatomic,strong) NSString * name;

@property (nonatomic,strong) NSString * author;

@property (nonatomic,assign) float price;

@end

@implementation BookObject

-(instancetype)initWithName:(NSString *)name price:(float)price author:(NSString *)author{

    if (self = [super init]) {

        self.name = name;

        self.price = price;

        self.author = author;

    }

    return self;

}

-(NSString *)getName{

    return self.name;

}

-(float)getPrice{

    return self.price;

}

-(NSString *)getAuthor{

    return self.author;

}

-(NSString *)description{

    NSLog(@"书的名字 %@,当前买卖价格:%f,书的作者%@",[self getName],[self getPrice],[self getAuthor]);

    return nil;

}

@end

测试

///正常卖书

id book = [[BookObject alloc]initWithName:@"《笑傲江湖》" price:50 author:@"金庸"];

    [book description];

结果

2018-04-03 14:14:17.995418+0800 设计模式原则[26260:5566097] 书的名字 《笑傲江湖》,当前买卖价格:50.000000,书的作者金庸


场景变更

项目投入使用的时候,每逢节假日要对商品进行打折销售。如何应该这样的变化呢?

方案一

修改接口

在Book协议中增加一个-getOffPrice 方法,专门处理打折。但是所有的实现该协议的BookObject中都要修改,并且Book协议应该稳定并且可靠,不应该经常发生变化,否则就失去了接口的契约作用。因此放弃

方案二

修改实现类

直接修改BookObject 对象的-getPrice 方法。这个 方法一看就有问题,这样就没有办法处理不打折商品了。方案不好

方案三

通过扩展实现变化

我们可以通过增加一个子类BookDiscountObject,覆盖-getPrice 方法。这种方法对现有的代码没有影响,风险小,是个好办法。


开闭原则UML图

设计模式原则之开闭原则_第1张图片
开闭原则UML图

场景变更新增代码

#import "BookObject.h"

@interface BookDiscountObject : BookObject

@end

#import "BookDiscountObject.h"

@implementation BookDiscountObject

-(float)getPrice{

    float price =[super getPrice];

    if (price>40) {

        price = 0.8*40;

    }

    return price;

}

@end

测试代码

///正常卖书 id book = [[BookObject alloc]initWithName:@"《笑傲江湖》" price:50 author:@"金庸"];

    [book description];

    ///打折销售

    book = [[BookDiscountObject alloc]initWithName:@"《笑傲江湖》" price:50 author:@"金庸"];

    [book description];

结果

2018-04-03 14:14:17.995418+0800 设计模式原则[26260:5566097] 书的名字 《笑傲江湖》,当前买卖价格:50.000000,书的作者金庸

2018-04-03 14:14:17.995599+0800 设计模式原则[26260:5566097] 书的名字 《笑傲江湖》,当前买卖价格:32.000000,书的作者金庸


变化分类

逻辑变化 

只变化了一个逻辑,而不涉及其他模块,比如一个算法是a*b*c,现在需要修改为a+b+c,可以直接通过修改原有类中的方法的方式来完成,前提条件是所有依赖或关联类都按照相同的逻辑处理

子模块变化 

一个模块变化,会对其它的模块产生影响,特别是一个低层次的模块变化必然引起高层模块的变化,因此在通过扩展完成变化


使用开闭原则的理由

第一:开闭原则非常有名,只要是面向对象编程,在开发时都会强调开闭原则

第二:开闭原则是最基础的设计原则,其它的五个设计原则都是开闭原则的具体形态,也就是说其它的五个设计原则是指导设计的工具和方法,而开闭原则才是其精神领袖。依照java语言的称谓,开闭原则是抽象类,而其它的五个原则是具体的实现类。

第三:开闭原则可以提高复用性 

在面向对象的设计中,所有的逻辑都是从原子逻辑组合而来,不是在一个类中独立实现一个业务逻辑。只有这样的代码才可以复用,粒度越小,被复用的可能性越大。那为什么要复用呢?减少代码的重复,避免相同的逻辑分散在多个角落,减少维护人员的工作量。那怎么才能提高复用率呢?缩小逻辑粒度,直到一个逻辑不可以分为止。

第四:开闭原则可以提高维护性 

一款软件量产后,维护人员的工作不仅仅对数据进行维护,还可能要对程序进行扩展,维护人员最乐意的事是扩展一个类,而不是修改一个类。让维护人员读懂原有代码,再进行修改,是一件非常痛苦的事情,不要让他在原有的代码海洋中游荡后再修改,那是对维护人员的折磨和摧残。

第五:面向对象开发的要求 

万物皆对象,我们要把所有的事物抽象成对象,然后针对对象进行操作,但是万物皆发展变化,有变化就要有策略去应对,怎么快速应对呢?这就需要在设计之初考虑到所有可能变化的因素,然后留下接口,等待“可能”转变为“现实”。


开闭原则是使用

第一:抽象约束 

抽象是对一组事物的通用描述,没有具体的实现,也就表示它可以有非常多的可能性,可以跟随需求的变化而变化。因此,通过接口或抽象类可以约束一组可能变化的行为,并且能够实现对扩展开放,其包含三层含义:

通过接口或抽象类约束扩散,对扩展进行边界限定,不允许出现在接口或抽象类中不存在的public方法。

参数类型,引用对象尽量使用接口或抽象类,而不是实现类,这主要是实现里氏替换原则的一个要求

抽象层尽量保持稳定,一旦确定就不要修改

第二:元数据(metadata)控件模块行为 

编程是一个很苦很累的活,那怎么才能减轻压力呢?答案是尽量使用元数据来控制程序的行为,减少重复开发。什么是元数据?用来描述环境和数据的数据,通俗的说就是配置参数,参数可以从文件中获得,也可以从数据库中获得。

第三:制定项目章程 

在一个团队中,建立项目章程是非常重要的,因为章程是所有人员都必须遵守的约定,对项目来说,约定优于配置。这比通过接口或抽象类进行约束效率更高,而扩展性一点也没有减少。

第四:封装变化 

对变化封装包含两层含义: 

(1)将相同的变化封装到一个接口或抽象类中 

(2)将不同的变化封装到不同的接口或抽象类中,不应该有两个不同的变化出现在同一个接口或抽象类中。 

封装变化,也就是受保护的变化,找出预计有变化或不稳定的点,我们为这些变化点创建稳定的接口。

参考博客

六大设计原则之开闭原则

源代码地址

下一篇博客讲解

设计模式原则之里氏替换原则

你可能感兴趣的:(设计模式原则之开闭原则)