# 设计模式
# 设计模式
对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案。每一个设计模式系统的命名、解释和评价了面向对象系统中一个重要的和重复出现的设计。
- "四人帮" (Gang of Four, GoF)
- <<设计模式: 可复用面向对象软件的基础>>
# 面向对象
设计模式是面向对象的设计
封装
- 把属性和方法封装到类里,体现出类里面和类外面
- Python 私有 __ _类里能访问,类外不能访问概念
继承
- 类之间的复用代码
多态
- Python是多态语言,程序员可不用多关注
# 接口
接口:若干抽象方法的集合
作用:限制实现接口的类必须按照接口给定的调用方式实现这些方法;使用者通过了解接口就可以明白实现类的使用方式
class Payment(object):
def pay(self, money):
raise NotImplementedError
class AliPay(Payment):
pass
class WechatPay(Payment):
def pay(self, money):
pass
a = AliPay()
# a.pay(1000) # 不调用,不抛出异常
w = WechatPay()
w.pay(1000) # 已实现,调用不抛出异常
from abc import ABCMeta, abstractmethod
class Payment(metaclass=ABCMeta):
@abstractmethod
def pay(self, money):
pass
class AliPay(Payment):
pass
a = AliPay() # AliPay必须实现抽象方法,否则会抛出异常
# 面向对象设计原则
SOLID原则
- 开放封闭原则:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。即软件实体尽量在不修改原有代码的情况下进行扩展;
- 里式替换原则:所有引用父类的地方必须能透明地使用其子类的对象;
- 依赖倒置原则:高层模块不应该依赖底层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。换言之,要针对接口编程,而不是针对实现编程;
- 接口隔离原则:使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口;
- 单一职责原则:不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责
# 设计模式分类
- 创建型模式:怎样创建对象,目的隐藏底层逻辑
- 结构型模式:几个类之间协同工作
- 行为型模式:侧重方法
# 简单工厂模式
不直接向客户端暴露对象创建的实现细节,而是通过一个工厂类来负责创建产品类的实例
from abc import ABCMeta, abstractmethod
# 抽象产品角色(Product)
class Payment(metaclass=ABCMeta):
@abstractmethod
def pay(self, money):
pass
# 具体产品角色(Concrete Product)
class AliPay(Payment):
def __init__(self, use_huabei=False):
self.use_huabei = use_huabei
def pay(self, money):
if self.use_huabei:
print('花呗支付%d元' % money)
else:
print('支付宝余额支付%d元' % money)
# 具体产品角色(Concrete Product)
class WechatPay(Payment):
def pay(self, money):
print('微信支付%d元' % money)
# 工厂角色(Creator)
class PaymentFactory(object):
def create_payment(self, method):
# 隐藏细节
if method == 'ali':
# 用户完全不用传递的参数,在工厂里传入
return AliPay()
elif method == 'wechat':
# 或 ali 与 wechat 相同功能 不同格式的参数,如json,xml
return WechatPay()
# 新增一种选项,调用时只是选项值增加,而不是参数增加 ********
elif method == 'huabei':
return AliPay(use_huabei=True)
else:
return None
pf = PaymentFactory()
p = pf.create_payment('huabei')
p.pay(1000)
优点:
- 隐藏了对象创建的实现细节
- 客户端不需要修改代码
缺点:
- 违反了单一职责原则,将创建逻辑集中到一个工厂类里
- 当添加新产品时,需要修改工厂类代码,违反了开闭原则
# 工厂方法模式
定义一个用于创建对象的接口(工厂接口),让子类决定实例化哪一个产品类
from abc import ABCMeta, abstractmethod
# 抽象产品角色(Product)
class Payment(metaclass=ABCMeta):
@abstractmethod
def pay(self, money):
pass
# 具体产品角色(Concrete Product)
class AliPay(Payment):
def __init__(self, use_huabei=False):
self.use_huabei = use_huabei
def pay(self, money):
if self.use_huabei:
print('花呗支付%d元' % money)
else:
print('支付宝余额支付%d元' % money)
# 具体产品角色(Concrete Product)
class WechatPay(Payment):
def pay(self, money):
print('微信支付%d元' % money)
class BankPay(Payment):
def pay(self, money):
print('银行卡支付%d元' % money)
# 抽象工厂角色(Creator)
class PaymentFactory(metaclass=ABCMeta):
@abstractmethod
def create_payment(self):
pass
# 具体工厂角色(Concrete Creator)
class AlipayFactory(PaymentFactory):
def create_payment(self):
return AliPay()
class WechatFactory(PaymentFactory):
def create_payment(self):
return WechatPay()
class BankFactory(PaymentFactory):
def create_payment(self):
return BankPay()
ali_factory = AlipayFactory()
p1 = ali_factory.create_payment()
p1.pay(1000)
we_factory = WechatFactory()
p2 = we_factory.create_payment()
p2.pay(2000)
bank_factory = BankFactory()
p3 = bank_factory.create_payment()
p3.pay(3000)
优点
- 每个具体产品都对应一个具体工厂类,不需要修改原有工厂类代码
- 隐藏了对象创建的实现细节
缺点
- 每增加一个具体产品类,就必须增加一个相应的具体工厂类
# 抽象工厂模式
定义一个工厂类接口,让工厂子类来创建一系列相关或相互依赖的对象
!> 例如:成产一部手机,需要手机壳、CPU、操作系统三类对象进行组装,其中每类对象都有不同的种类。对每个具体工厂,分别生产一部手机所需要的三个对象。
相比工厂方法模式,抽象工厂模式中的每个具体工厂都生产一套产品
from abc import ABCMeta, abstractmethod
# --- 抽象产品 ---
class PhoneShell(metaclass=ABCMeta):
@abstractmethod
def show_shell(self):
pass
class CPU(metaclass=ABCMeta):
@abstractmethod
def show_cpu(self):
pass
class OS(metaclass=ABCMeta):
@abstractmethod
def show_os(self):
pass
# --- 抽象工厂 ---
class PhoneFactory(metaclass=ABCMeta):
@abstractmethod
def make_shell(self):
pass
@abstractmethod
def make_cpu(self):
pass
@abstractmethod
def make_os(self):
pass
# --- 具体产品 ---
class SmallShell(PhoneShell):
def show_shell(self):
print('普通手机小手机壳')
class BigShell(PhoneShell):
def show_shell(self):
print('普通手机大手机壳')
class AppleShell(PhoneShell):
def show_shell(self):
print('苹果手机壳')
class SnapDragonCPU(CPU):
def show_cpu(self):
print('骁龙CPU')
class MediaTekCPU(CPU):
def show_cpu(self):
print('联发科CPU')
class AppleCPU(CPU):
def show_cpu(self):
print('评估CPU')
class Android(OS):
def show_os(self):
print('Android系统')
class IOS(OS):
def show_os(self):
print('IOS系统')
# --- 具体工厂 ---
class MiFactory(PhoneFactory):
def make_cpu(self):
return SnapDragonCPU()
def make_os(self):
return Android()
def make_shell(self):
return BigShell()
class HuaweiFactory(PhoneFactory):
def make_cpu(self):
return MediaTekCPU()
def make_os(self):
return Android()
def make_shell(self):
return SmallShell()
class IPhoneFactory(PhoneFactory):
def make_cpu(self):
return AppleCPU()
def make_os(self):
return IOS()
def make_shell(self):
return AppleCPU()
# --- 客户端 ---
class Phone(object):
def __init__(self, cpu, os, shell):
self.cpu = cpu
self.os = os
self.shell = shell
def show_info(self):
print('手机信息:')
self.cpu.show_cpu()
self.os.show_os()
self.shell.show_shell()
def make_phone(factory):
cpu = factory.make_cpu()
os = factory.make_os()
shell = factory.make_shell()
return Phone(cpu, os, shell)
p1 = make_phone(MiFactory())
p1.show_info()
优点:
- 将客户端与类的具体实现相分离
- 每个工厂创建了一个完整的产品系列,使得易于交换产品系列
- 有利于产品的一致性(即产品之间的约束关系)
缺点:
- 难以支持新种类的(抽象)产品
# 单例模式
保证一个类只有一个实例,并提供一个访问它的全局访问点。
如 模块全局对象,其他场景如日志对象,数据库连接池
class Singleton(object):
def __new__(cls, *args, **kwargs):
if not hasattr(cls, '_instance'):
cls._instance = super(Singleton, cls).__new__(cls)
return cls._instance
class MyClass(Singleton):
def __init__(self, value):
self.value = value
a = MyClass(10)
b = MyClass(20)
print(a.value) # 20
print(b.value) # 20
print(a is b) # True
优点:
- 对唯一实例的受控访问
- 单例相当于全局变量,但防止了命名空间被污染
# 适配器模式
将一个类的接口转换成客户希望的另一个接口
适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作
实现方式:
- 类适配器:使用多继承
- 对象适配器:使用组合
from abc import ABCMeta, abstractmethod
class Payment(metaclass=ABCMeta):
@abstractmethod
def pay(self, money):
raise NotImplementedError
class AliPay(Payment):
def pay(self, money):
print('阿里支付%d' % money)
class WechatPay(Payment):
def pay(self, money):
print('微信支付%d' % money)
# 不接口规范, cost --> pay
class BankPay(object):
def cost(self, money):
print('银行卡支付%d' % money)
# 类适配器
class NewBankPay(Payment, BankPay):
def pay(self, money):
self.cost(money)
p = NewBankPay()
p.pay(1000)
# 对象适配器,通过组合完成
class PaymentAdapter(Payment):
def __init__(self, payment):
self.payment = payment
def pay(self, money):
self.payment.cost(money)
p1 = PaymentAdapter(BankPay())
p1.pay(2000)
适用场景:
- 想使用一个已经存在的类,而它的接口不符合你的要求
思考:
- 继承需要指定类BankPay,所以适用情况与BankPay绑定
- 对象组合payment作为参数,适用范围就扩大了
# 桥模式
将一个事物的两个维度分离,使其都可以独立地变化,如画图程序形状和颜色
from abc import ABCMeta, abstractmethod
class Shape(metaclass=ABCMeta):
# 组合方式进行耦合 - 松耦合
def __init__(self, color):
self.color = color
@abstractmethod
def draw(self):
pass
class Color(metaclass=ABCMeta):
@abstractmethod
def paint(self, shape):
pass
class Rectangle(Shape):
name = '长方形'
def draw(self):
self.color.paint(self)
class Circle(Shape):
name = '圆形'
def draw(self):
self.color.paint(self)
class Red(Color):
def paint(self, shape):
print('红色的%s' % shape.name)
class Green(Color):
def paint(self, shape):
print('绿色的%s' % shape.name)
shape = Rectangle(Red())
shape.draw()
shape2 = Circle(Green())
shape2.draw()
应用场景:
当事物有两个维度上的表现,两个维度都可能扩展
优点:
- 抽象与实现分离
- 优秀的扩展能力
# 组合模式
将对象组合成树形结构以表示"部分-整体"的层次结构
组合模式使得用户对单个对象和组合对象的使用具有一致性,如例子中draw方法
from abc import ABCMeta, abstractmethod
# 抽象组件
class Graphic(metaclass=ABCMeta):
@abstractmethod
def draw(self):
pass
# 叶子组件
class Point(Graphic):
def __init__(self, x, y):
self.x = x
self.y = y
def draw(self):
print(str(self))
def __str__(self):
return '点(%s, %s)' % (self.x, self.y)
# 叶子组件
class Line(Graphic):
def __init__(self, p1, p2):
self.p1 = p1
self.p2 = p2
def __str__(self):
return '线段[%s, %s]' % (self.p1, self.p2)
def draw(self):
print(str(self))
# 复合组件
class Picture(Graphic):
def __init__(self, iterable):
self.children = []
for g in iterable:
self.add(g)
def add(self, graphic):
self.children.append(graphic)
def draw(self):
print('---- 复合图形 ----')
for g in self.children:
g.draw()
print('---- 复合图形 ----')
# 客户端
# p1 = Point(1, 1)
# p2 = Point(2, 2)
# l1 = Line(p1, p2)
# l1.draw()
p1 = Point(2, 3)
l1 = Line(Point(3, 4), Point(6, 7))
l2 = Line(Point(1, 5), Point(2, 8))
pic1 = Picture([p1, l1, l2])
p2 = Point(4, 4)
l3 = Line(Point(1, 1), Point(2, 2))
pic2 = Picture([p2, l3])
pic = Picture([pic1, pic2]) # 先总体,再细节,即先理解就是两个复合图形组成的更复杂的复合图形
pic.draw()
适用场景:
- 表示对象的"部分-整体"层次结构(特别结构是递归的)
- 希望用户忽略组合对象与单个对象的不同,用户统一地使用组合结构中的所有对象
优点:
- 定义了包含基本对象和组合对象的类层次结构
- 简化客户端代码,即客户端可以一致地使用组合对象和单个对象
- 更容易增加新类型组合,如 圆 叶子组件
# 外观模式
为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口
这个接口使得这一子系统更加容易使用
# 子系统类
class CPU(object):
def run(self):
print('CPU 开始运行')
def stop(self):
print('CPU 结束运行')
class Disk(object):
def run(self):
print('磁盘 启动')
def stop(self):
print('磁盘 制动')
class Memory(object):
def run(self):
print('内存 通电')
def stop(self):
print('内存 断电')
# 外观(facade)
class Computer(object):
def __init__(self):
self.cpu = CPU()
self.disk = Disk()
self.memory = Memory()
def run(self):
self.cpu.run()
self.disk.run()
self.memory.run()
def stop(self):
self.cpu.stop()
self.disk.stop()
self.memory.stop()
c = Computer()
c.run()
c.stop()
优点:
- 减少系统相互依赖
- 提高了灵活性
- 提高了安全性
# 代理模式
为其他对象提供一种代理以控制这个对象的访问
from abc import ABCMeta, abstractmethod
# 抽象实体: 规定RealSubject, VirtualSubject, ProtectedProxy对外具有一致方法
class Subject(metaclass=ABCMeta):
@abstractmethod
def get_content(self):
pass
@abstractmethod
def set_content(self, content):
pass
# 实体
class RealSubject(Subject):
def __init__(self, filename):
self.filename = filename
with open(filename, mode='r', encoding='utf-8') as f:
self.content = f.read()
def get_content(self):
return self.content
def set_content(self, content):
with open(self.filename, mode='w', encoding='utf-8') as f:
f.write(content)
subject = RealSubject('text.txt') # 初始化就读取内容了,若文件较大会占用内存
subject.get_content()
# 代理
class VirtualSubject(Subject):
def __init__(self, filename):
self.filename = filename
self.subject = None
def get_content(self):
if not self.subject:
self.subject = RealSubject(self.filename)
return self.subject.get_content()
def set_content(self, content):
if not self.subject:
self.subject = RealSubject(self.filename)
self.subject.set_content(content)
sub = VirtualSubject('text.txt') # 没有读取内容
sub.get_content() # 需要的时候,才读取内容
class ProtectedProxy(Subject):
def __init__(self, filename):
self.subject = RealSubject(filename)
def get_content(self):
return self.subject.get_content()
def set_content(self, content):
raise PermissionError('无写入权限')
应用场景:
- 远程代理:为远程的对象提供代理
- 虚代理:根据需要创建很大的对象
- 保护代理:控制对原始对象的访问,用于对象有不同访问权限时
优点:
- 远程代理:可以隐藏对象位于远程地址空间的事实
- 虚代理:可以进行优化,例如根据要求创建对象
- 保护代理:允许在访问一个对象时有一些附加的内务处理
# 责任链模式
使多个对象都要机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。
将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
from abc import ABCMeta, abstractmethod
# 抽象处理者
class Handler(metaclass=ABCMeta):
@abstractmethod
def handle_leave(self, day):
pass
# 具体处理者
class GeneralManager(Handler):
def handle_leave(self, day):
if day < 10:
print('总经理准假%d' % day)
else:
print('你还是辞职吧')
# 具体处理者
class DepartmentManager(Handler):
def __init__(self):
self.next = GeneralManager()
def handle_leave(self, day):
if day <= 5:
print('部门经理准假%d天' % day)
else:
print('部门经理职权不足')
self.next.handle_leave(day)
# 具体处理者
class ProjectDirector(Handler):
def __init__(self):
self.next = DepartmentManager()
def handle_leave(self, day):
if day <= 3:
print('项目主管准假%d天' % day)
else:
print('项目主管职权不足')
self.next.handle_leave(day)
# 客户端
day = 10
p = ProjectDirector()
p.handle_leave(day)
适用场景:
- 有多个请求可以处理一个请求,哪个对象处理由运行时决定
- 在不明确接收者的情况下,向多个对象中的一个提交一个请求
优点:
- 降低耦合度:一个对象无需知道是其他哪一个对象处理其请求
# 观察者模式
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,
所有依赖于它的对象都得到通知并被自动更新。观察者模式又称"发布-订阅"模式
# -*- coding:utf-8 -*-
from abc import ABCMeta, abstractmethod
# 抽象观察者
class Observer(metaclass=ABCMeta):
@abstractmethod
def update(self, notice): # notice 是 Notice类的对象
pass
# 抽象主题
class Notice(object):
def __init__(self):
self.observers = []
def attach(self, observer):
self.observers.append(observer)
def detach(self, observer):
self.observers.remove(observer)
def notify(self):
for observer in self.observers:
observer.update(self)
# 具体主题,即发布者
class StaffNotice(Notice):
def __init__(self, company_info):
super(StaffNotice, self).__init__()
self.__company_info = company_info
@property
def company_info(self):
return self.__company_info
@company_info.setter
def company_info(self, info):
self.__company_info = info
self.notify() # 推送
# 具体观察者,即订阅者
class Staff(Observer):
def __init__(self):
self.company_info = None
def update(self, notice):
self.company_info = notice.company_info
# Client
notice = StaffNotice('初始公司信息')
s1 = Staff()
s2 = Staff()
notice.attach(s1)
notice.attach(s2)
print(s1.company_info)
print(s2.company_info)
notice.company_info = '公司今年业绩非常行好,给大家发奖金!!!'
print(s1.company_info)
print(s2.company_info)
notice.detach(s2)
notice.company_info = '明天公司放假!'
print(s1.company_info)
print(s2.company_info)
适用场景:
- 当一个抽象模型有两方面,其中一个方面依赖于另一方面,将这两者封装在独立对象中以使它们可以各自独立地改变和复用
- 当一个对象的改变需要同时改变其他对象,而不知道具体有多少对象有待改变
- 当一个对象必须通知其他对象,而它又不能假定其他对象是谁,换言之,你不希望这些对象是紧密耦合的
优点:
- 目标和观察者之间的抽象耦合最小
- 支持广播通信
# 策略模式
定义了一些列的算法,把它们一个个封装起来,并且使它们可相互替换
本模式使得算法可独立于使用它的客户端而变化
from abc import ABCMeta, abstractmethod
# 抽象策略
class Strategy(metaclass=ABCMeta):
@abstractmethod
def execute(self, data):
pass
# 具体策略
class FastStrategy(Strategy):
def execute(self, data):
print('用较快的策略处理: %s' % data)
# 具体策略
class SlowStrategy(Strategy):
def execute(self, data):
print('用较慢的策略处理: %s' % data)
# 上下文
class Context(object):
def __init__(self, strategy, data):
self.strategy = strategy
self.data = data
def set_strategy(self, strategy):
self.strategy = strategy
def do_strategy(self):
self.strategy.execute(self.data)
# Client
data = ['user1', 'user2', 'user3']
s = FastStrategy()
context = Context(s, data)
context.do_strategy()
s2 = SlowStrategy()
context.set_strategy(s2)
context.do_strategy()
优点:
- 定义了一系列可重用的算法和行为
- 消除了一些条件语句
- 可以提供相同行为的不同实现
缺点:
- 客户必须了解不同的策略
# 模板方法模式
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中
模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤
import time
from abc import ABCMeta, abstractmethod
class Window(metaclass=ABCMeta):
@abstractmethod
def start(self):
pass
@abstractmethod
def repaint(self):
pass
@abstractmethod
def stop(self): # 原子操作/钩子操作
pass
def run(self):
self.start()
while True:
try:
self.repaint()
time.sleep(1)
except KeyboardInterrupt:
break
self.stop()
class MyWindow(Window):
def __init__(self, msg):
self.msg = msg
def start(self):
print('窗口开始运行')
def stop(self):
print('窗口结束运行')
def repaint(self):
print(self.msg)
MyWindow('msg').run()
适用场景:
- 一次性实现一个算法的不变的部分
- 各个子类中的公共行为应该被提取出来并集中到一个公共父类中以避免代码重复
- 控制子类扩展
# 建造者模式
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示
from abc import ABCMeta, abstractmethod
class Player(object):
def __init__(self, face=None, body=None, arm=None, leg=None):
self.face = face
self.body = body
self.arm = arm
self.leg = leg
def __str__(self):
return "%s, %s, %s, %s" % (self.face, self.body, self.arm, self.leg)
class PlayerBuilder(metaclass=ABCMeta):
@abstractmethod
def build_face(self):
pass
@abstractmethod
def build_body(self):
pass
@abstractmethod
def build_arm(self):
pass
@abstractmethod
def build_leg(self):
pass
# 控制组装顺序
class PlayerDirector(object):
@staticmethod
def build_player(builder):
builder.build_body()
builder.build_face()
builder.build_arm()
builder.build_leg()
return builder.player
class SexyGirl(PlayerBuilder):
def __init__(self):
self.player = Player()
def build_face(self):
self.player.face = '漂亮脸蛋'
def build_body(self):
self.player.body = '苗条'
def build_arm(self):
self.player.arm = '细长'
def build_leg(self):
self.player.leg = '大长腿'
builder = SexyGirl()
director = PlayerDirector()
player = director.build_player(builder)
print(player)
建造者模式与抽象工厂模式相似,也用来创建复杂对象。 主要区别是建造者模式着重一步步构造一个复杂对象,而抽象工厂模式着重于多个系列产品对象。
优点:
- 隐藏了一个产品的内部结构和装配过程
- 将构造代码与表示代码分开
- 可以对构造过程进行更精细的控制
算法入门 →