一.单例模式
单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在。当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场。
比如,某个服务器程序的配置信息存放在一个文件中,客户端通过一个 AppConfig 的类来读取配置文件的信息。如果在程序运行期间,有很多地方都需要使用配置文件的内容,也就是说,很多地方都需要创建 AppConfig 对象的实例,这就导致系统中存在多个 AppConfig 的实例对象,而这样会严重浪费内存资源,尤其是在配置文件内容很多的情况下。事实上,类似 AppConfig 这样的类,我们希望在程序运行期间只存在一个实例对象。
1.使用模块
其实,Python 的模块就是天然的单例模式,因为模块在第一次导入时,会生成 .pyc
文件,当第二次导入时,就会直接加载 .pyc
文件,而不会再次执行模块代码。因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。
class Singlen(object): def foo(self): pass sing = Singlen()
将上面的代码写入到singlen文件中,使用的时候直接导入对象就可以了。
2.使用装饰器
def Singleton(cls): _instance = {} # 创建一个存储对象的字典 def _singleton(*args, **kargs): # 判断对象是否已经被创建 if cls not in _instance: _instance[cls] = cls(*args, **kargs) return _instance[cls] return _singleton @Singleton class TestDemo(object): a = 1 def __init__(self, x=0): self.x = x a1 = TestDemo(2) print(id(a1)) # 22331520 a2 = TestDemo(3) print(id(a2)) # 22331520
3.使用类
import time import threading class Singleton(object): _instance_lock = threading.Lock() def __init__(self): time.sleep(1) @classmethod def instance(cls, *args, **kwargs): if not hasattr(Singleton, "_instance"): with Singleton._instance_lock: if not hasattr(Singleton, "_instance"): Singleton._instance = Singleton(*args, **kwargs) return Singleton._instance def task(arg): obj = Singleton.instance() print(obj) for i in range(10): t = threading.Thread(target=task,args=[i,]) t.start() time.sleep(20) obj = Singleton.instance() print(obj)
4.基于__new__()方法实现
import threading class Singleton(object): _instance_lock = threading.Lock() def __init__(self): pass def __new__(cls, *args, **kwargs): # 判断类是否有_instance属性 if not hasattr(Singleton, "_instance"): with Singleton._instance_lock: if not hasattr(Singleton, "_instance"): # 创建实例 Singleton._instance = object.__new__(cls) return Singleton._instance obj1 = Singleton() obj2 = Singleton() print(obj1,obj2) def task(arg): obj = Singleton() print(obj) for i in range(10): t = threading.Thread(target=task,args=[i,]) t.start()
5.使用元类
- 类由type创建,创建类时,type的__init__方法自动执行,类() 执行type的 __call__方法(类的__new__方法,类的__init__方法)
- 对象由类创建,创建对象时,类的__init__方法自动执行,对象()执行类的 __call__ 方法
5.1元类例子
class Foo: def __init__(self): pass def __call__(self, *args, **kwargs): pass obj = Foo() # 执行type的 __call__ 方法,调用 Foo类(是type的对象)的 __new__方法,用于创建对象,然后调用 Foo类(是type的对象)的 __init__方法,用于对对象初始化。 obj() # 执行Foo的 __call__ 方法
5.2 实现单例模式
import threading class SingletonType(type): _instance_lock = threading.Lock() def __call__(cls, *args, **kwargs): if not hasattr(cls, "_instance"): with SingletonType._instance_lock: if not hasattr(cls, "_instance"): cls._instance = super(SingletonType,cls).__call__(*args, **kwargs) return cls._instance class Foo(metaclass=SingletonType): def __init__(self,name): self.name = name obj1 = Foo('name') obj2 = Foo('name') print(obj1,obj2)
二.工厂模式
说道工厂,第一反应是可以生产东西,也就是我们可以通过工厂类创建产品。一个函数,传入需要创建的产品类型,输出的结果是我们需要的类型。
import random import abc # 两种类型的课程 class BasicCourse(object): """ 基础课程 """ def get_labs(self): return "basic_course: labs" def __str__(self): return "BasicCourse" class ProjectCourse(object): """ 项目课 """ def get_labs(self): return "project_course: labs" def __str__(self): return "ProjectCourse" # 两种类型的虚拟机 class LinuxVm(object): """ Linux 虚拟机 """ def start(self): return "Linux vm running" class MacVm(object): """ Mac OSX 虚拟机 """ def start(self): return "Mac OSX vm running" class Factory(metaclass=abc.ABCMeta): """ 抽象工厂类, 现在工厂类不仅能创建课程,还能创建虚拟机了 """ @abc.abstractmethod def create_course(self): pass @abc.abstractmethod def create_vm(self): pass class BasicCourseLinuxFactory(Factory): """ 基础课程工厂类 """ def create_course(self): return BasicCourse() def create_vm(self): return LinuxVm() class ProjectCourseMacFactory(Factory): """ 项目课程工厂类 """ def create_course(self): return ProjectCourse() def create_vm(self): return MacVm() def get_factory(): """ 随机获取一个工厂类 """ return random.choice([BasicCourseLinuxFactory, ProjectCourseMacFactory])() if __name__ == '__main__': factory = get_factory() course = factory.create_course() vm = factory.create_vm() print(course.get_labs()) print(vm.start())
三.策略模式
在现实中,一个问题可能有多种显示,比如一个商场的商品做活动,可能有各种可能,如原价,8折,满100送10快等。策略模式它定义了算法家族,分别封装起来,让他们之间可以相互替换,此模式让算法的变化,不会影响到使用算法的客户.
class CashSuper(object): ''' 现金收费抽象类 ''' def accept_cash(self,money): pass class CashNormal(CashSuper): ''' 正常收费子类 ''' def accept_cash(self,money): return money class CashRebate(CashSuper): ''' 打折收费子类 ''' def __init__(self,discount=1): self.discount = discount # 折扣 def accept_cash(self,money): return money * self.discount class CashReturn(CashSuper): ''' 返利收费子类 ''' def __init__(self,money_condition=0,money_return=0): self.money_condition = money_condition self.money_return = money_return def accept_cash(self,money): if money >= self.money_condition: return money - (money / self.money_condition) * self.money_return return money #具体策略类 class Context(object): def __init__(self,csuper): self.csuper = csuper def GetResult(self,money): return self.csuper.accept_cash(money) if __name__ == '__main__': money = input("原价: ") strategy = {} strategy[1] = Context(CashNormal()) strategy[2] = Context(CashRebate(0.8)) strategy[3] = Context(CashReturn(100,10)) mode = int(input("选择折扣方式: 1) 原价 2) 8折 3) 满100减10: ") if mode in strategy: csuper = strategy[mode] else: print("不存在的折扣方式") csuper = strategy[1] print("需要支付: ",csuper.GetResult(money))
四.观察者模式
所谓的观察者模式,就是说当一个对象发生变化时,观察者能及时得到通知并且更新。观察者模式在许多的地方都很有用。定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象都得到通知并被自动更新。发生改变的对象称为观察目标,被通知的对象称为观察者 。一个观察目标可以对应多个观察者
import abc class Subject(object): ''' 被观察对象的基类 ''' def __init__(self): # 观察者列表 self._observers = [] def attach(self,observer): ''' z注册一个观察者 :param observer: :return: ''' if observer not in self._observers: self._observers.append(observer) def detach(self,observer): ''' 删除一个观察者 :param observer: :return: ''' try: self._observers.remove(observer) except ValueError: pass def notify(self): ''' 让所有的观察者执行观察者的更新方法 :return: ''' for observer in self._observers: observer.update(self) class Course(Subject): ''' 课程对象,被观察者 ''' def __init__(self): super(Course, self).__init__() self._message = None @property def message(self): return self._message @message.setter def message(self,msg): ''' 设置message属性 :param msg: :return: ''' self._message = msg self.notify() class Observer(object): """ 观察者抽象类 """ __metaclass__ = abc.ABCMeta @abc.abstractmethod def update(self, subject): pass class UserObserver(Observer): """ 用户观察者 """ def update(self, subject): print("User observer: %s" % subject.message) class OrgObserver(Observer): """ 机构观察者 """ def update(self, subject): print("Organization observer: %s" % subject.message) if __name__ == '__main__': # 初始化一个用户观察者 user = UserObserver() # 初始化一个机构观察者 org = OrgObserver() # 初始化一个课程 course = Course() # 注册观察者 course.attach(user) course.attach(org) # 设置course.message,这时观察者会收到通知 course.message = "two observers" # 注销一个观察者 course.detach(user) course.message = "single observer"
在上面的代码中,最重要的就是Subject类了,它实现了观察者模式中大部分功能。作为一个被观察的对象,Subject实现了注册观察者,注销观察者和通知观察者的功能。接着我们基于Subject创建了我们的课程Course类,并且当我们设置Course.message属性时,Course对象会通知到所有观察者。可以看出,观察者模式使被观察的对象(主题)和观察者之间解耦了。
五.命令模式
顾名思义,命令模式就是对命令的封装。所谓封装命令,就是将一系列操作封装到命令类中,并且命令类只需要对外公开一个执行方法execute,调用此命令的对象只需要执行命令的execute方法就可以完成所有的操作。这样调用此命令的对象就和命令具体操作之间解耦了。更进一步,通过命令模式我们可以抽象出调用者,接收者和命令三个对象。调用者就是简单的调用命令,然后将命令发送给接收者,而接收者则接收并执行命令,执行命令的方式也是简单的调用命令的execute方法就可以了。发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。下面让我们使用 Python 来实现命令模式。
import abc class VmReceiver(object): """ 命令接收者,真正执行命令的地方 """ def start(self): print("Virtual machine start") def stop(self): print("Virtual machine stop") class Command(object): """ 命令抽象类 """ __metaclass__ = abc.ABCMeta @abc.abstractmethod def execute(self): """ 命令对象对外只提供 execute 方法 """ pass class StartVmCommand(Command): """ 开启虚拟机的命令 """ def __init__(self, recevier): """ 使用一个命令接收者初始化 """ self.recevier = recevier def execute(self): """ 真正执行命令的时候命令接收者开启虚拟机 """ self.recevier.start() class StopVmCommand(Command): """ 停止虚拟机的命令 """ def __init__(self, recevier): """ 使用一个命令接收者初始化 """ self.recevier = recevier def execute(self): """ 真正执行命令的时候命令接收者关闭虚拟机 """ self.recevier.stop() class ClientInvoker(object): """ 命令调用者 """ def __init__(self, command): self.command = command def do(self): self.command.execute() if __name__ == '__main__': recevier = VmReceiver() # 创建命令接收者对象 # 开启虚拟机对象的创建 start_command = StartVmCommand(recevier) # 执行命令对象 client = ClientInvoker(start_command) client.do() # 执行命令 # 停止虚拟机对象的创建 stop_command = StopVmCommand(recevier) client.command = stop_command client.do()
六.模板方法模式
提到模板,不难想到文档模板、简历模板等。其实模板方法模式中的模板就是这个意思,在模板方法模式中,我们先定义一个类模板,在这个类中,我们定义了各种操作的顺序(轮毂或者说是骨架),但是并不实现这些操作,这些操作由子类来操作。
import abc class Eating(object): ''' 吃饭模板基类 ''' __metaclass__ = abc.ABCMeta def eating(self): ''' 吃饭方法中,确定要执行哪些操作 :return: ''' self.xicai() self.aoxifan() self.chaocai() print('开始吃饭!!') @abc.abstractmethod def xicai(self): '''定义洗菜类的抽象基类''' pass @abc.abstractmethod def aoxifan(self): '''熬稀饭基类''' pass @abc.abstractmethod def chaocai(self): pass class TomEating(Eating): ''' tom想要吃饭,必须要实现吃饭的步骤 ''' def xicai(self): print('tom is xicai') def aoxifan(self): print('tom is aoxifan') def chaocai(self): print('tom is chaocai') class JohnEating(Eating): ''' tom想要吃饭,必须要实现吃饭的步骤 ''' def xicai(self): print('John is xicai') def aoxifan(self): print('John is aoxifan') def chaocai(self): print('John is chaocai') if __name__ == '__main__': john = JohnEating() john.eating() tom = TomEating() tom.eating()
七.代理模式
代理模式在生活中比比皆是。比如你通过代理上网,比如你不会去华西牛奶生产地直接买牛奶,而是到超市这个代理购买牛奶,这些例子中都存在着代理模式。所谓代理模式就是给一个对象提供一个代理,并由代理对象控制对原对象的访问。通过代理,我们可以对访问做一些控制。在开发网站的过程中,针对一些频繁访问的资源,可以使用缓存。
from time import sleep class Redis(object): """ 用于模拟 redis 服务 """ def __init__(self): """ 使用字典存储数据 """ self.cache = dict() def get(self, key): """ 获取数据 """ return self.cache.get(key) def set(self, key, value): """ 设置数据 """ self.cache[key] = value class Image(object): """ 图片对象,图片存在七牛云存储中,我们只保存了一个地址 """ def __init__(self, name): self.name = name @property def url(self): sleep(2) return "https://dn-syl-static.qbox.me/img/logo-transparent.png" class Page(object): """ 用于显示图片 """ def __init__(self, image): """ 需要图片进行初始化 """ self.image = image def render(self): """ 显示图片 """ print(self.image.url) redis = Redis() class ImageProxy(object): """ 图片代理,首次访问会从真正的图片对象中获取地址,以后都从 Redis 缓存中获取 """ def __init__(self, image): self.image = image @property def url(self): addr = redis.get(self.image.name) if not addr: addr = self.image.url print("Set url in redis cache!") redis.set(self.image.name, addr) else: print("Get url from redis cache!") return addr if __name__ == '__main__': img = Image(name="logo") proxy = ImageProxy(img) page = Page(proxy) # 首次访问 page.render() print("") # 第二次访问 page.render()
八.组合模式
什么是组合模式?按照定义来说,组合模式是将对象组合成树形结构表示,使得客户端对单个对象和组合对象的使用具有一致性。组合模式的使用通常会生成一颗对象树,对象树中的叶子结点代表单个对象,其他节点代表组合对象。调用某一组合对象的方法,其实会迭代调用所有其叶子对象的方法。
使用组合模式的经典例子是 Linux 系统内的树形菜单和文件系统。在树形菜单中,每一项菜单可能是一个组合对象,其包含了菜单项和子菜单,这样就形成了一棵对象树。在文件系统中,叶子对象就是文件,而文件夹就是组合对象,文件夹可以包含文件夹和文件,同样又形成了一棵对象树。同样的例子还有员工和领导之间的关系
import abc class Worker(object): """ 工作抽象类 """ __metaclass__ = abc.ABCMeta def __init__(self, name): self.name = name @abc.abstractmethod def work(self): # 工作的抽象基类 pass class Employe(Worker): """ 员工类 """ __metaclass__ = abc.ABCMeta def work(self): print("Employ: %s start to work " % self.name) class Leader(Worker): """ 领导类 """ def __init__(self, name): self.members = [] super(Leader, self).__init__(name) def add_member(self, employe): if employe not in self.members: self.members.append(employe) def remove_member(self, employe): if employe in self.members: self.members.remove(employe) def work(self): print("Leader: %s start to work" % self.name) for employe in self.members: employe.work() if __name__ == '__main__': employe_1 = Employe("employe_1") employe_2 = Employe("employe_2") leader_1 = Leader("leader_1") leader_1.add_member(employe_1) leader_1.add_member(employe_2) employe_3 = Employe("employe_3") leader_2 = Leader("leader_2") leader_2.add_member(employe_3) leader_2.add_member(leader_1) leader_2.work()
九.外观模式
所谓外观模式,就是将各种子系统的复杂操作通过外观模式简化,让客户端使用起来更方便简洁。比如你夏天晚上出门时,要关闭电灯,关闭电视机,关闭空调,如果有了一个总开关,通过它可以关闭电灯,电视机和空调,你出门的时候关闭总开关就行了。
class User(object): """ 用户类 """ def is_login(self): return True def has_privilege(self, privilege): return True class Course(object): """ 课程类 """ def can_be_learned(self): return True class Lab(object): """ 实验类 """ def can_be_started(self): return True class Client(object): """ 客户类,用于开始一个实验 """ def __init__(self, user, course, lab): self.user = user self.course = course self.lab = lab def start_lab(self): """ 开始实验,需要一系列的判断:用户是否登陆,课程是否可以学习,实验是否可以开始。判断非常繁琐! """ if self.user.is_login() and self.course.can_be_learned() and self.lab.can_be_started(): print("start lab") else: print("can not start lab") class FacadeLab(object): """ 新的Lab类,应用了面向对象模式 """ def __init__(self, user, course, lab): self.user = user self.course = course self.lab = lab def can_be_started(self): if self.user.is_login() and self.course.can_be_learned() and self.lab.can_be_started(): return True else: return False class NewClient(object): """ 新的客户类,使用外观模式 """ def __init__(self, facade_lab): self.lab = facade_lab def start_lab(self): """ 开始实验,只需要判断 FacadeLab 是否可以开始 """ if self.lab.can_be_started: print("start lab") else: print("can not start lab") if __name__ == '__main__': user = User() course = Course() lab = Lab() client = Client(user, course, lab) client.start_lab() print("Use Facade Pattern:") facade_lab = FacadeLab(user, course, lab) facade_client = NewClient(facade_lab) facade_client.start_lab()
十.6大设计原则
10.1 单一职责原则 (Single Responsibility Principle)
顾名思义,单一职责的原则是说一个类直负责一项职责(操作)。如果一个类负责多个职责,其中一项职责发生变化就需要修改整个类,这可能会导致其他的职责运行错误。一个类,只应该有一个引起它变化的原因。
其优点有:
- 可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;
- 提高类的可读性,提高系统的可维护性;
- 变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。
10.2 里氏替换原则 (Liskov Substitution Principle)
里氏替换的意思是说所有引用基类的地方必须能透明地使用其子类的对象。这种情况在代码中随处可以,我们在类中使用基类进行定义,而在运行时使用子类对象,为了确保代码运行正常,在实现子类时要注意以下一些地方:
- 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法;
- 子类中可以增加自己特有的方法;
- 当子类的方法重载父类的方法时,子类方法的输入参数要比父类方法的输入参数更宽松;
10.3 依赖倒置原则 (Dependence Inversion Principle)
定义:抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。依赖倒置原则要求我们在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不要用具体类来做这些事情。依赖倒置原则的本质就是通过抽象(接口或抽象类)使各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合。在编写代码中落到实处,需要注意以下一些地方:
- 每个类尽量都有接口或抽象类,或者抽象类和接口两者都具备;
- 变量的表名类型尽量是接口或者抽象类;
- 尽量不要覆写基类的方法;
- 结合里氏替换原则使用。
由于 Python 是一门动态语言,在传递参数时不需要定义具体类型,所以依赖倒置原则其实一定程度上已经内嵌在了 Python 语言中。
10.4 接口隔离原则 (Interface Segregation Principle)
接口隔离原则提示我们客户端不应该依赖它不需要的接口,一个类对另一个类的依赖应该建立在最小的接口上。根据接口隔离原则,当一个接口太大时,我们需要将它分割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可。每一个接口应该承担一种相对独立的角色,不干不该干的事,该干的事都要干。
看到这里你们或许认为接口隔离原则与单一职责原则是相同的。其实接口隔离原则与单一职责原则的审视角度是不相同的,单一职责原则要求的是类和接口职责单一,注重的是职责,这是业务逻辑上的划分,而接口隔离原则要求接口的方法尽量少。在使用接口隔离原则时,我们需要注意控制接口的粒度,接口不能太小,如果太小会导致系统中接口泛滥,不利于维护;接口也不能太大,太大的接口将违背接口隔离原则,灵活性较差,使用起来很不方便。一般而言,接口中仅包含为某一类用户定制的方法即可,不应该强迫客户依赖于那些它们不用的方法。
10.5 迪米特原则 (Law of Demeter)
定义:一个对象应该对其他对象有最少的了解。通俗地讲,一个类应该对自己需要耦合或调用的类知道得最少,你(被耦合或调用的类)的内部是如何复杂都和我没关系,那是你的事情,我就知道你提供的公开方法,我就调用这么多,其他的我一概不关心。迪米特法则指导我们在设计系统时,应该尽量减少对象之间的交互,如果两个对象之间不必彼此直接通信,那么这两个对象就不应当发生任何直接的相互作用,如果其中的一个对象需要调用另一个对象的某一个方法的话,可以通过第三者转发这个调用。简言之,就是通过引入一个合理的第三者来降低现有对象之间的耦合度。可以看到迪米特原则在代理模式和外观模式中都有被使用。
10.6 开闭原则 (Open Closed Principle)
软件实体应该对扩展开放,对修改关闭,其含义是说一个软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化。根据开闭原则,在设计一个软件系统模块(类,方法)的时候,应该可以在不修改原有的模块(修改关闭)的基础上,能扩展其功能(扩展开放)。遵循开闭原则的系统设计,可以让软件系统可复用,并且易于维护。这也是系统设计需要遵循开闭原则的原因:
- 稳定性:开闭原则要求扩展功能不修改原来的代码,这可以让软件系统在变化中保持稳定。
- 扩展性:开闭原则要求对扩展开放,通过扩展提供新的或改变原有的功能,让软件系统具有灵活的可扩展性。