OOP和设计原则
搭建代码框架 --> 软件设计/软件工程.
面向对象编程有三大特性: 封装、继承、多态 这三者是递进关系的
-1- 封装: 有两方面, 一方面是 将属性和方法放到类里; 另一方面是 __
双下划线开头,表示私有.
-2- 继承.
-3- 多态: 在python中不用care它, 因为python的语言特性就已经决定了它是多态语言了.
# OOP接口
接口: 若干抽象方法的集合.
# 为什么要接口?
现有两个类,表示两种支付方式,阿里支付和微信支付
class AliPay:
def pay(self, money):
pass
class WechatPay:
def pay(self, money):
pass
2
3
4
5
6
7
8
我们很容易想到通过类实例化得到对象,再调用各自的pay方法完成支付.
a = AliPay()
a.pay(100)
b = WechatPay()
b.pay(100)
2
3
4
而在开发时, 我们往往会将 对支付方法pay的调用 封装到一个函数中
# - 高层代码/客户端
def finish_pay(p, money):
p.pay(money)
# - 客户端使用过程,也可看作是高层代码的一部分
p1 = AliPay()
finish_pay(p1, 100)
p2 = WechatPay()
finish_pay(p2, 100)
2
3
4
5
6
7
8
9
进行到这一步,看似没有问题, 实则有个致命的隐患.. 我们得保证 阿里支付和微信支付里的支付方法都是pay!!
如何实现呢? 通过接口来实现.
写了接口后, 在多人协作开发代码时, 一人开发一个支付, 大家都按照接口的定义去写, 即支付方法都是pay..
这样就保证了 [高层] 在调用支付方法时不会出错, 完成了支付方法调用的统一.
# 接口的实现
接口的实现有两种方法, 一种是raise, 一种是抽象类, 我们一般选择后者.
class PayMent:
def pay(self, money):
raise NotImplementedError
class AliPay(PayMent):
pass
2
3
4
5
6
7
第一种raise的方式有个问题, p=AliPay() 只要p对象不调用pay方法, 就不会报错..
第二种不实现接口方法的话, p=AliPay() 类实例化时就会报错.. 注: 实现接口时,方法名, 方法参数 都得一样!
from abc import ABCMeta, abstractmethod
class PayMent(metaclass=ABCMeta): # - 抽象类
@abstractmethod # - 约束子类必须重写这个抽象方法,专业点: ★★★ AliPay实现了PayMent的接口!!
def pay(self, money):
pass
class AliPay(PayMent): # - 不能说是继承接口类,应该说是实现接口
# - 不重写该方法的话,a = AliPay()类实例化时就会报错:
# TypeError: Can't instantiate abstract class AliPay with abstract method pay
def pay(self, money):
pass
a = AliPay()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 接口的作用
★ 接口保证了 [高层] 在调用 接口方法时的统一 / 父类强制的规范子类必须要有哪些方法, ★ 还有这么一句话: 接口对高层模块隐藏了类的内部实现.
理解下何为高层模块?
- 针对Django源码而言,我们使用Django的人写的代码就是高层模块.
Django底层使用了大量设计模式,写了很多接口,让你调用的很舒服
- 针对我们写的支付案例, 对pay接口的调用代码就是高层代码.(简单这样理解就行
2
3
4
接口案例完整代码:
from abc import ABCMeta, abstractmethod
class PayMent(metaclass=ABCMeta):
@abstractmethod
def pay(self, money): # 这是接口. 接口对高层模块隐藏了类的内部实现,接口就方法名、方法参数、返回值类型
pass
class AliPay(PayMent):
def pay(self, money): # 这是实现接口.
pass
class WechatPay(PayMent):
def pay(self, money): # 这是实现接口.
pass
# - 高层代码/高层模块/客户端
def finish_pay(p, money):
p.pay(money)
# - 客户端使用过程,也可看作是高层代码的一部分
p1 = AliPay()
finish_pay(p1, 100) # 在调用的时候,看不到底层类的内部实现/★ 不用你调用pay方法,finish_pay函数里已经写了.
p2 = WechatPay()
finish_pay(p2, 100)
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
# SOLID原则
面向对象设计的SOLID原则, 5个.
# 开放封闭原则
开闭原则: 对扩展开放,对修改关闭. 即软件实体尽量在不修改原有代码的情况下进行扩展.
# 里氏替换原则
所有引用父类的地方必须能透明的使用其子类对象.
class User:
def show_name(self):
pass
class VIPUser(User):
# 1.应该保证方法名、方法参数、方法的返回值类型与父类是一样的,与父类方法不同的仅是方法里的逻辑
def show_name(self):
pass
# - 高层代码/客户端
def show_user(u): # u可能是User的对象,亦可能是VIPUser的对象
# 2.进而保证此处写的u.show_name()是不会出错的,eg:不能User的show_name有3个参数,VIPUser的show_name有4个参数.
return u.show_name()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 依赖倒置原则
-1- 高层模块不应该依赖底层模块..<若依赖,那么底层模块的变动会导致高层模块的代码也改变>
-2- 高层和底层二者应该依赖其抽象..<接口就是抽象,这些接口表明了高层代码需要哪些函数!>
-3- 抽象不应该依赖细节.. <抽象类里接口方法的方法体是pass>
-4- 细节应该依赖抽象.. <实现接口方法, 其方法体里是具体的逻辑>
换言之, 要针对接口编程, 而不是针对实现编程..
from abc import ABCMeta, abstractmethod
class Payment(metaclass=ABCMeta): # 抽象类
@abstractmethod
def pay(self, money): # 接口
pass
# - Alipay和WechatPay 都是底层模块
class Alipay(Payment):
def pay(self, money): # 实现接口 / 细节
print(f"支付宝支付了{money}元!")
class WechatPay(Payment):
def pay(self, money): # 实现接口 / 细节
print(f"微信支付了{money}元!")
# - 高层模块/客户端 (上面oop接口案例那,用的是函数)
class ShoppingCart:
def __init__(self, payment: Payment):
self.payment = payment
def checkout(self, amount):
self.payment.pay(amount)
# - 客户端使用过程,也可看作是高层代码的一部分
a = Alipay()
cart1 = ShoppingCart(a)
cart1.checkout(100.0)
w = WechatPay()
cart2 = ShoppingCart(w)
cart2.checkout(200.0)
"""
---------------
| 高层模块 |
| ShoppingCart |
---------------
|
▼
---------------
| 抽象层 |
| Payment |
---------------
▲ ▲
| |
---------------- ----------------
| 底层模块 | | 底层模块 |
| Alipay | | WechatPay |
---------------- ----------------
"""
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
# 接口隔离原则
使用多个专门的接口,而不使用单一的总接口, 即 客户端/高层代码 不应该依赖那些它不需要的接口.
现有这样一个动物相关的抽象类, 里面有接口 走、游泳和飞.
from abc import ABCMeta, abstractmethod
class Animal(metaclass=ABCMeta):
@abstractmethod
def walk(self):
pass
@abstractmethod
def swim(self):
pass
@abstractmethod
def fly(self):
pass
2
3
4
5
6
7
8
9
10
11
12
13
14
15
老虎类继承了Animal类, 需实现动物这个抽象类的所有接口, 但老虎不会飞呀, 所以这样的设计不是很合理.
那如何改进呢? 使用接口隔离原则来实现.
from abc import ABCMeta, abstractmethod
class LandAnimal(metaclass=ABCMeta):
@abstractmethod
def walk(self):
pass
class WaterAnimal(metaclass=ABCMeta):
@abstractmethod
def swim(self):
pass
class SkyAnimal(metaclass=ABCMeta):
@abstractmethod
def fly(self):
pass
class Tiger(LandAnimal): # Tiger只会走路,不会游泳和飞
def walk(self):
pass
class Frog(LandAnimal, WaterAnimal): # 继承了两个抽象类,实现了里面的游泳和走路的接口!
def swim(self):
pass
def walk(self):
pass
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
# 单一职责原则
不要存在多于一个导致类变更的原因, 通俗来说, 即一个类只负责一项职责/一个类只做一件事.
举个极端的例子:
拿到需求后,用面向过程编程写了一遍; 然后在外面套了一个class, 那导致这个class 进行变更的原因就很多了.
# 设计模式
设计模式分为3类, 共23种.. 我们不会全学 因为有些用不到了,比如原型模式; 有些python本身就帮我们实现了, 比如迭代器模式..
-1- 创建型模式(5): 聚焦对象的创建.
-2- 结构型模式(7): 聚焦类之间的工作结构. 即 几个类组成一个什么结构? 这几个类如何在一起协同工作?
-3- 行为型模式(11): 聚焦类的方法/行为.