image.png
核心思想:不要存在多于一个导致类变更的原因,通俗的说,即一个类只负责一个功能。如果一个类里面添加了多个功能,当其中有一个功能发生了变化,就会有多种原因引起这个类的变更,从而导致这个类的维护变得困难。在真实的开发中,不仅仅是类,函数也要遵循单一职责原则。即:一个函数负责一个功能。如果一个函数里面有不同的功能,则需要将不同的功能的函数分离出去。
优点:如果一个类或者一个函数的功能划分清晰,只负责某一项具体的功能,不但可以降低类或者函数的复杂度,而且还可以提高代码的可读性、应用程序的可维护性。
例如,需求上指出用一个类描述食肉和食草动物:
//================== Animal.h ==================
@interface Animal : NSObject
- (void)eatWithAnimalName:(NSString *)animalName;
@end
运行结果:
2018-10-27 17:55:25.775317+0800 DesignPatterns[54087:24701786] 狼 吃肉
2018-10-27 17:55:25.775689+0800 DesignPatterns[54087:24701786] 豹 吃肉
2018-10-27 17:55:25.775721+0800 DesignPatterns[54087:24701786] 虎 吃肉
上线后,发现问题了,并不是所有的动物都是吃肉的,比如羊就是吃草的。修改时如果遵循单一职责原则,需要将 Animal
类细分为食草动物类 Herbivore
,食肉动物 Carnivore
,代码如下:
//================== Herbivore.h ==================
@interface Herbivore : Animal
@end
@implementation Herbivore
- (void)eatWithAnimalName:(NSString *)animalName {
NSLog(@"%@ 吃草", animalName);
}
@end
//================== Carnivore.h ==================
@interface Carnivore : Animal
@end
@implementation Carnivore
- (void)eatWithAnimalName:(NSString *)animalName {
NSLog(@"%@ 吃肉", animalName);
}
@end
//================== main 函数 ==================
Animal *carnivore = [Carnivore new];
[carnivore eatWithAnimalName:@"狼"];
[carnivore eatWithAnimalName:@"豹"];
[carnivore eatWithAnimalName:@"虎"];
NSLog(@"\n");
Animal *herbivore = [Herbivore new];
[herbivore eatWithAnimalName:@"羊"];
在子类里面重写父类的 eatWithAnimalName
函数,运行结果:
2018-10-27 18:04:49.189722+0800 DesignPatterns[54422:24725132] 狼 吃肉
2018-10-27 18:04:49.190450+0800 DesignPatterns[54422:24725132] 豹 吃肉
2018-10-27 18:04:49.190482+0800 DesignPatterns[54422:24725132] 虎 吃肉
2018-10-27 18:04:49.190498+0800 DesignPatterns[54422:24725132]
2018-10-27 18:04:49.190530+0800 DesignPatterns[54422:24725132] 羊 吃草
这样一来,不仅仅在此次新需求中满足了单一职责原则,以后如果还要增加食肉动物和食草动物的其他功能,就可以直接在这两个类里面添加即可。但是,有一点,修改花销是很大的,除了将原来的类分解之外,还需要修改 main
函数 。而直接修改类 Animal
来达成目的虽然违背了单一职责原则,但花销却小的多,代码如下:
//================== Animal.h ==================
@interface Animal : NSObject
- (void)eatWithAnimalName:(NSString *)animalName;
@end
@implementation Animal
- (void)eatWithAnimalName:(NSString *)animalName {
if ([@"羊" isEqualToString:animalName]) {
NSLog(@"%@ 吃草", animalName);
} else {
NSLog(@"%@ 吃肉", animalName);
}
}
@end
//================== main 函数 ==================
Animal *animal = [Animal new];
[animal eatWithAnimalName:@"狼"];
[animal eatWithAnimalName:@"豹"];
[animal eatWithAnimalName:@"虎"];
[animal eatWithAnimalName:@"羊"];
运行结果:
2018-10-27 18:16:10.910397+0800 DesignPatterns[54677:24751636] 狼 吃肉
2018-10-27 18:16:10.911105+0800 DesignPatterns[54677:24751636] 豹 吃肉
2018-10-27 18:16:10.911138+0800 DesignPatterns[54677:24751636] 虎 吃肉
2018-10-27 18:16:10.911160+0800 DesignPatterns[54677:24751636] 羊 吃草
可以看到,这种修改方式要简单的多。
但是却存在着隐患:有一天需求上增加牛和马也需要吃草,则又需要修改 Animal
类的 eatWithAnimalName
函数,而对原有代码的修改会对调用狼、豹和虎吃肉等功能带来风险,也许某一天你会发现运行结果变为虎也吃草了。这种修改方式直接在代码级别上违背了单一职责原则,虽然修改起来最简单,但隐患却是最大的。还有一种修改方式:
//================== Animal.h ==================
@interface Animal : NSObject
/**
* 吃草
*/
- (void)eatGrassWithAnimalName:(NSString *)animalName;
/**
* 吃肉
*/
- (void)eatMeatWithAnimalName:(NSString *)animalName;
@end
@implementation Animal
- (void)eatGrassWithAnimalName:(NSString *)animalName {
NSLog(@"%@ 吃草", animalName);
}
- (void)eatMeatWithAnimalName:(NSString *)animalName {
NSLog(@"%@ 吃肉", animalName);
}
@end
//================== main 函数 ==================
Animal *animal = [Animal new];
[animal eatMeatWithAnimalName:@"狼"];
[animal eatMeatWithAnimalName:@"豹"];
[animal eatMeatWithAnimalName:@"虎"];
[animal eatGrassWithAnimalName:@"羊"];
运行结果:
2018-10-27 18:31:30.321473+0800 DesignPatterns[55048:24787008] 狼 吃肉
2018-10-27 18:31:30.321884+0800 DesignPatterns[55048:24787008] 豹 吃肉
2018-10-27 18:31:30.321922+0800 DesignPatterns[55048:24787008] 虎 吃肉
2018-10-27 18:31:30.321939+0800 DesignPatterns[55048:24787008] 羊 吃草
通过运行结果可以看到,这种修改方式没有改动原来的函数,而是在类中新加了一个函数,这样虽然也违背了类单一职责原则,但在函数级别上却是符合单一职责原则的,因为它并没有动原来函数的代码。
在实际的开发应用中,有很多复杂的场景,怎么设计一个类或者一个函数,让应用程序更加灵活,是更多程序员们值得思考的,需要结合特定的需求场景,有可能有些类里面有很多的功能,但是切记不要将不属于这个类本身的功能也强加进来,这样不仅带来不必要的维护成本,也违反了单一职责的设计原则。
它的定义是:程序中的对象应该对应扩展是开放的,对于修改是封闭的。
这个意思就是说,当一个类实现了一个功能的时候,如果想要改变这个功能不是去修改代码,而是通过扩展的方式去实现。实现该类提供的接口函数,然后注入到该类中,通过这种函数去实现功能的改变。
它的定义是:所有引用基类的地方必须能透明的使用其子类对象。
说白了就是,只要父类能出现的地方子类就可以出现,替换为子类也不会产生任何的错误。开闭原则一般可以通过里氏替换实现对扩展开放,对修改关闭的效果。
它指的是:模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的。即依赖抽象,而不依赖具体的实现。
它的定义是:客户端不应该依赖它不需要的接口。
它的目的是:解开系统的耦合,从而容易重构更改。
它的定义是:一个对象应该对其他对象有最少的了解
通俗的说,一个类应该对自己需要耦合或调用的类知道的越少越好,类的内部如何实现与调用者或依赖者没关系。