基本介绍
设计模式可以认为是面向对象编程的编程思想,设计原则即这些编程思想的指导总纲。 SOLID是面向对象设计(OOD)的五大基本原则的首字母缩写:S-单一职责原则;O-开放封闭原则;L-里氏替换原则;I-接口隔离原则;D-依赖倒置原则
SOLID原则
单一职责原则
一个类应该有且只有一个原因引起它的变更
,通俗地讲:一个类只负责一项功能或一类相似的功能。 如以下设计动物行为的代码,显然不符合单一职责原则,如果需要添加其他场景则需要修改runnning方法,会对之前的逻辑造成隐患:
1
2
3
4
5
6
7
8
9
10
class Animal():
def __init__(self, name, type):
self.__name = name
self.__type = type
def running(self):
if self.__type == "水生":
print(self.__name + "在水里游")
else:
print(self.__name + "在陆上跑")
比较好的设计,需要对类进行提取:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class TerrestrialAnimal():
def __init__(self, name):
self.__name = name
def running(self):
print(self.__name + "在陆上跑")
class AquaticAnimal():
def __init__(self, name):
self.__name = name
def running(self):
print(self.__name + "在水里游")
开放封闭原则
OCP(Open Close Priciple),软件实体(如类、模块、函数等)应该对拓展开放,对修改封闭
,即设计代码的时候需要考虑后续的变化,在增加一个功能的时候,尽可能不修改原始代码,当修改一个模块时,不能影响到其他模块。 如在前文的动物的基础上添加一个观察动物的接口,如下写法不符合OCP原则,因为添加类的时候需要修改display_activity方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
class Zoo():
def __init__(self):
self.__animals = [
TerrestrialAnimal("狗"),
AquaticAnimal("鱼")
]
def display_activity(self):
for animal in self.__animals:
if isinstance(animal, TerrestrialAnimal):
animal.running()
else:
animal.swimming()
合理的修改如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class BirdAnimal(Animal):
def __init__(self, name):
super().__init__(name)
def moving(self):
# 需要定义一致的接口
print(self.__name + "在天上飞")
class Zoo():
def __init__(self):
self.__animals = []
def add_animal(self, animal):
self.__animals.append(animal)
def display_activity(self):
for animal in self.__animals:
animal.moving()
里氏替换原则
LSP(Liskou Substitution Principle),所有能引用基类的地方必须能透明地使用子类
,即父类能出现的地方子类都能替换实现相同的功能,反之则不成立。 如下,建立陆上动物的子类:猴子,猴子类能替换陆生动物类,但是陆生动物类无法替换猴子类功能:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Monkey(TerrestrialAnimal):
def __init__(self, name):
super().__init__(name)
def climbing(self):
print(self._name + '在爬树,动作灵活轻盈')
class Zoo():
def __init__(self):
self.__animals = []
def add_animal(self, animal):
self.__animals.append(animal)
def display_activity(self):
for animal in self.__animals:
animal.moving()
def mokey_climbing(self, monkey):
# 子类特有的功能
monkey.climbing()
依赖倒置原则
DIP(Dependence Inversion Principle),高层模块不应该依赖低层模块,二者都应该依赖抽象。抽象不应该依赖细节,细节应该依赖抽象。
,即需要将具有相同/相似功能的类,抽象成接口或者抽象类,让具体的实现类继承这个抽象类。 如需要给前文的动物添加一个吃东西的接口,简单的设计如下,存在的问题是:食物没有和动物隔离,导致添加食物类型等场景时修改代码变得困难。
1
2
3
4
5
6
7
8
class Dog():
def eat(self, meat):
pass
class Fish():
def eat(self, grass):
pass
比较好的设计是:创建动物和食物的抽象类,动物进食实现应该依赖抽象食物类,而不是具体的食物实例。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from abc import ABCMeta, abstractmethod
class Animal(metaclass=ABCMeta):
def __init__(self, name):
self._name = name
def eat(self, food):
if self.check_food(food):
print(self._name + "eat" + food.get_name())
else:
print(self._name + "doesn't eat" + food.get_name())
@abstractmethod
def check_food(self, food):
"""检查食物是否能吃"""
pass
class Food(metaclass=ABCMeta):
def __init__(self, name):
self._name = name
def get_name(self):
return self._name
@abstractmethod
def category(self):
"""食物类别"""
pass
class Dog(Animal):
def __init__(self):
super().__init__("Dog")
def check_food(self, food):
return food.category() == "Meat"
接口隔离原则
ISP(Interface Segregation Principle),客户端不应该依赖它不需要的接口。用多个细粒度的接口来替代多个方法组成的复杂接口,每个接口服务于一个子模块。
,即尽可能细化接口,接口方法尽可能小(适度)。 如对动物的行为描述设计接口,动物本身具备比较多的特性,需要对这些特征进行细粒度切分,如:奔跑,游泳,飞行等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
from abc import ABCMeta, abstractmethod
class Animal(metaclass=ABCMeta):
def __init__(self, name):
self._name = name
def get_name(self):
return self._name
@abstractmethod
def feature(self):
pass
@abstractmethod
def moving(self):
pass
class IRunnable(metaclass=ABCMeta):
"""奔跑接口"""
@abstractmethod
def running(self):
pass
class IFlyable(metaclass=ABCMeta):
"""飞行接口"""
@abstractmethod
def flying(self):
pass
class INataable(metaclass=ABCMeta):
"""游泳接口"""
@abstractmethod
def swimming(self):
pass
class MammalAnimal(Animal, IRunable):
"""哺乳动物"""
def __init__(self, name):
super().__init__(name)
def feature(self):
print(self._name + "的生理特征:恒温,胎生,哺乳。")
def running(self):
print("在陆上跑")
def moving(self):
print(self._name + "的活动方式:", end="")
self.running()
class Bat(MammalAnimal, IFlyable):
"""蝙蝠:哺乳动物,但是不会走,会飞"""
def __init__(self, name):
super().__init__(name)
def running(self):
print("行走功能已经退化")
def flying(self):
print("在天空飞...", end="")
def moving(self):
print(self._name, "的活动方式:", end="")
self.flying()
self.runing()
其他原则
LoD原则
每一个逻辑单元应该对其他逻辑单元有最少的了解;也就是说只亲近当前的对象。只和直接亲近的对象说话,“不和陌生人说话”
,该原则也被称为迪米特原则。 典型场景:类A中有类B的对象,类B中又有C的对象。现在A需要访问C的属性,写法如下:
1
2
3
4
# 不合理写法
a.get_B().get_C().get_properties()
# 合理写法
a.get_C_properties()
KISS原则
Keep It Simple and Stupid
,代码设计应该尽可能解耦,不要过度设计,让代码简单易懂。
DRY原则
Don't reapeat yourself
,多次遇到相同问题,需要提取共同的解决方法,提高代码复用率,实现方式:
- 函数级别的封装
- 类级别的抽象
- 泛型设计
YAGBI原则
You aren't gonna need it, don't implement something until it is neccessary.
,只考虑和设计必需的功能,避免过度设计。
Rule Of Three原则
三次原则:当一个功能第三次出现的时候,在进行抽象化。即第一次实现,大胆去做;第二次实现,虽然反感,但是还是去做;第三次实现,则要去审视将重复/相似的代码进行抽象,包装为通用接口。 目的:省事,循序渐进发现模式,防止过度冗余
CQS原则
Command-Query Separation
,查询(Query)即返回一个值来回应某个问题,命令(Command)即一个方法改变了对象的状态。两者需要尽可能分离,有利于提高系统的性能,也有利于增强系统的安全性。