学习设计模式,更应该领悟其设计思想,不应该局限于代码层面。
No1 观察者模式
定义了对象之间的一对多依赖。当被观察者对象改变状态时,它的所有依赖者(观察者)都会收到通知并更新状态。
应用场景:
a 对一个对象状态或数据的更新需要其他对象同步更新,或者一个对象的更新需要另一个对象的更新。
b 对象仅需要将自己的更新通知给其他对象而不需要知道其他对象的细节,如消息推送。
监听模式还可以用于网络中客户端和服务器,比如手机中的各种APP消息推送,服务端是被观察者,各个手机App是观察者,一旦服务器上的数据(如APP升级信息)有更新,就会被推送到手机的客户端。在这个应用中你会发现服务器代码和APP客户端代码其实是两个完全不一样的代码,他们是通过网络接口进行通信的,所以如果你只是停留在代码层面是完全无法理解的。
#可以继承一个抽象类 class Observer(): """观察者基类""" # @abstractmethod def update(self, observable, object): pass class Observable: """被观察者的基类""" def __init__(self): self.__observers = [] def addObserver(self, observer): self.__observers.append(observer) def removeObserver(self, observer): self.__observers.remove(observer) def notifyObservers(self, object): for obj in self.__observers: obj.update(self, object) ''' 举例:异地登录提醒 ''' import time class Account(Observable): def __init__(self): super().__init__() self.__lastIp = {} self.__lastRegion = {} def __getRegion(self, ip): #由IP地址获取地区信息。这里只是模拟,真实项目中应该调用IP地址解析服务。 ipRegions = { "101.47.18.9":"浙江杭州", "67.218.147.69":"美国洛杉矶", } # region = ipRegions[ip] region = ipRegions.get(ip) return "" if region is None else region def __isLongDistance(self, name, region): # 计算本次登录与最近几次登录的距离 # 真实项目中调用地理信息相关服务 lastRegion = self.__lastRegion.get(name) return lastRegion is not None and lastRegion != region def login(self, name, ip, time): region = self.__getRegion(ip) if self.__isLongDistance(name, region): self.notifyObservers({"name":name,"ip":ip,"region":region,"time":time}) self.__lastRegion[name] = region self.__lastIp[name] = ip class SmsSender(Observer): def update(self, observable, object): print("[短信发送] " + object["name"] + "您好!检测到您的账户可能登录异常。最近一次登录信息: " + "登录地区:" + object["region"] + " 登录ip:" + object["ip"] + " 登录时间:" + time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(object["time"]))) class MailSender(Observer): """邮件发送器""" def update(self, observable, object): print("[邮件发送] " + object["name"] + "您好!检测到您的账户可能登录异常。最近一次登录信息: " + "登录地区:" + object["region"] + " 登录ip:" + object["ip"] + " 登录时间:" + time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(object["time"]))) if "__main__" == __name__: accout = Account() accout.addObserver(SmsSender()) accout.addObserver(MailSender()) accout.login("Tony", "101.47.18.9", time.time()) accout.login("Tony", "67.218.147.69", time.time())
No5 单例模式
确保一个类只有一个实例,并且提供一个访问它的全局方法。
应用场景:你希望这个类只有一个实例,例如项目中全局管理类。
最常用的单例模式为第三种,装饰器实现的模式。
class Singleton1(object): __instance = None __initfirst = False def __new__(cls,*args,**kwargs): if not cls.__instance: cls.__instance = super().__new__(cls,*args,**kwargs) return cls.__instance def __init__(self): if not Singleton1.__initfirst: #类属性影响所有实例 # if not self.__initfirst: #实例属性会覆盖类属性 Singleton1.__initfirst = True print("init") def __del__(self): #这两个属性在del 实例对象后没有变化(垃圾回收机制) Singleton1.__instance = None Singleton1.__initfirst = False a = Singleton1() b = Singleton1() print(Singleton1._Singleton1__instance) print(Singleton1._Singleton1__initfirst) del a print(Singleton1._Singleton1__instance) print(Singleton1._Singleton1__initfirst) class Singleton2(type): """单例实现方式二""" def __init__(cls, what, bases=None, dict=None): super().__init__(what, bases, dict) cls._instance = None # 初始化全局变量cls._instance为None def __call__(cls, *args, **kwargs): # 控制对象的创建过程,如果cls._instance为None则创建,否则直接返回 if cls._instance is None: cls._instance = super().__call__(*args, **kwargs) return cls._instance class CustomClass(metaclass=Singleton2): """用户自定义的类""" def __init__(self, name): self.__name = name def getName(self): return self.__name tony = CustomClass("Tony") karry = CustomClass("Karry") print(tony.getName(), karry.getName()) print("id(tony):", id(tony), "id(karry):", id(karry)) print("tony == karry:", tony == karry) def singletonDecorator(cls, *args, **kwargs): """定义一个单例装饰器""" instance = {} def wrapperSingleton(*args, **kwargs): if cls not in instance: instance[cls] = cls(*args, **kwargs) return instance[cls] return wrapperSingleton @singletonDecorator class Singleton3: """使用单例装饰器修饰一个类""" def __init__(self, name): self.__name = name def getName(self): return self.__name # tony = Singleton3("Tony") # print(tony.getName()) # karry = Singleton3("Karry") # print(karry.getName()) # print("tony == karry:",tony == karry)
No6 原型模式(克隆模式)
用原型实例指定要创建对象的种类,并通过拷贝这些模型的属性来创建新的对象。
其核心就是一个clone方法:clone方法的功能就是拷贝父类的所有属性。主要包括两个过程:
a 分配一块新的内存空间给新的对象
b 拷贝父类对象的所有属性
设计克隆模式时,区分浅拷贝和深拷贝,除非本身要求两个类一起改变,尽量使用深拷贝方式。
优点:
1)克隆模式通过内存拷贝方式进行复制,比new的方式创建对象的性能更好。
2)通过深拷贝的方式,可以方便地创建一个具有相同属性和行为的另一个对象,特别是对于复杂对象。(这个本来就是该模型存在的意义,不能算优点)
缺点:
克隆方式创建的对象,不会执行类的初始化函数__init__),不是缺点,但请牢记。
# 代码框架 from copy import copy, deepcopy class Clone: """克隆的基类""" def clone(self): """浅拷贝的方式克隆对象""" return copy(self) def deepClone(self): """深拷贝的方式克隆对象""" return deepcopy(self)
举例:clone默认的配置文件并设置修改属性
class AppConfig(Clone): """应用程序功能配置""" def __init__(self, configName): self.__configName = configName self.parseFromFile("./config/default.xml") def parseFromFile(self, filePath): """ 从配置文件中解析配置项 真实项目中通过会将配置保存到配置文件中,保证下次开启时依然能够生效; 这里为简单起见,不从文件中读取,以初始化的方式来模拟。 """ self.__fontType = "宋体" self.__fontSize = 14 self.__language = "中文" self.__logPath = "./logs/appException.log" def saveToFile(self, filePath): """ 将配置保存到配置文件中 这里为简单起见,不再实现 """ pass def copyConfig(self, configName): """创建一个配置的副本""" config = self.deepClone() config.__configName = configName return config def showInfo(self): print("%s 的配置信息如下:" % self.__configName) print("字体:", self.__fontType) print("字号:", self.__fontSize) print("语言:", self.__language) print("异常文件的路径:", self.__logPath) def setFontType(self, fontType): self.__fontType = fontType def setFontSize(self, fontSize): self.__fontSize = fontSize def setLanguage(self, language): self.__language = language def setLogPath(self, logPath): self.__logPath = logPath def testAppConfig(): defaultConfig = AppConfig("default") defaultConfig.showInfo() print() newConfig = defaultConfig.copyConfig("tonyConfig") newConfig.setFontType("雅黑") newConfig.setFontSize(18) newConfig.setLanguage("English") newConfig.showInfo()
No14 策略模式
定义一系列算法,将每个算法都封装起来,并且使它们之间可以相互替换。策略模式使算法可以独立于使用它的用户而变化。
设计要点
策略模式中主要有三个角色,在设计策略模式时要找到并区分这些角色。
上下文(Context)用一个 ConcreteStrategy 对象来装配,维护一个 Strategy 对象的引用,可定义一个接口让 Strategy 访问它的数据
策略(Strategy) 定义所有支持的算法的公共接口。 Context 使用这个接口来调用某 ConcreteStrategy 定义的算法。
策略实现(ConcreteStrategy)以 Strategy 接口实现某具体算法
优点:
算法规则可以自由切换
避免使用多重条件判断
方便拓展和增加新的算法(规则)
缺点:
所有的策略类都要对外暴露
# Version 1.0 #======================================================================================================================= from abc import ABCMeta, abstractmethod # 引入ABCMeta和abstractmethod来定义抽象类和抽象方法 class IVehicle(metaclass=ABCMeta): """交通工具的抽象类""" @abstractmethod def running(self): pass class SharedBicycle(IVehicle): """共享单车""" def running(self): print("骑共享单车(轻快便捷)", end='') class ExpressBus(IVehicle): """快速公交""" def running(self): print("坐快速公交(经济绿色)", end='') class Express(IVehicle): """快车""" def running(self): print("打快车(快速方便)", end='') class Subway(IVehicle): """地铁""" def running(self): print("坐地铁(高效安全)", end='') class Classmate: """参加聚餐的同学""" def __init__(self, name, vechicle): self.__name = name self.__vechicle = vechicle def attendTheDinner(self): print(self.__name + " ", end='') self.__vechicle.running() print(" 来参加聚餐!") # Version 2.0 #======================================================================================================================= # 代码框架 #============================== from abc import ABCMeta, abstractmethod # 引入ABCMeta和abstractmethod来定义抽象类和抽象方法 class Person: """人类""" def __init__(self, name, age, weight, height): self.name = name self.age = age self.weight = weight self.height = height def showMysef(self): print("%s 年龄:%d岁,体重:%0.2fkg,身高:%0.2fm" % (self.name, self.age, self.weight, self.height) ) class ICompare(metaclass=ABCMeta): """比较算法""" @abstractmethod def comparable(self, person1, person2): "person1 > person2 返回值>0,person1 == person2 返回0, person1 < person2 返回值小于0" pass class CompareByAge(ICompare): """通过年龄排序""" def comparable(self, person1, person2): return person1.age - person2.age class CompareByHeight(ICompare): """通过身高进行排序""" def comparable(self, person1, person2): return person1.height - person2.height class CompareByHeightAndWeight(ICompare): """根据身高和体重的综合情况来排序 (身高和体重的权重分别是0.6和0.4)""" def comparable(self, person1, person2): value1 = person1.height * 0.6 + person1.weight * 0.4 value2 = person2.height * 0.6 + person2.weight * 0.4 return value1 - value2 class SortPerson: "Person的排序类" def __init__(self, compare): self.__compare = compare def sort(self, personList): """排序算法 这里采用最简单的冒泡排序""" n = len(personList) for i in range(0, n-1): for j in range(0, n-i-1): if(self.__compare.comparable(personList[j], personList[j+1]) > 0): tmp = personList[j] personList[j] = personList[j+1] personList[j+1] = tmp # j += 1 # i += 1 # 基于框架的实现 #============================== # Test #======================================================================================================================= def testTheDinner(): sharedBicycle = SharedBicycle() joe = Classmate("Joe", sharedBicycle) joe.attendTheDinner() helen = Classmate("Helen", Subway()) helen.attendTheDinner() henry = Classmate("Henry", ExpressBus()) henry.attendTheDinner() ruby = Classmate("Ruby", Express()) ruby.attendTheDinner() def testSortPerson(): personList = [ Person("Tony", 2, 54.5, 0.82), Person("Jack", 31, 74.5, 1.80), Person("Nick", 54, 44.5, 1.59), Person("Eric", 23, 62.0, 1.78), Person("Helen", 16, 45.7, 1.60) ] ageSorter = SortPerson(CompareByAge()) ageSorter.sort(personList) print("根据年龄进行排序后的结果:") for person in personList: person.showMysef() print() heightSorter = SortPerson(CompareByHeight()) heightSorter.sort(personList) print("根据身高进行排序后的结果:") for person in personList: person.showMysef() print() # # heightWeightSorter = SortPerson(CompareByHeightAndWeight()) # heightWeightSorter.sort(personList) # print("根据身高和体重进行排序后的结果:") # for person in personList: # person.showMysef() from operator import itemgetter,attrgetter def testPersonListInPython(): "用Python的方式对Person进行排序" personList = [ Person("Tony", 2, 54.5, 0.82), Person("Jack", 31, 74.5, 1.80), Person("Nick", 54, 44.5, 1.59), Person("Eric", 23, 62.0, 1.78), Person("Helen", 16, 45.7, 1.60) ] # 使用operator模块根据年龄、身高进行排序 sortedPerons = sorted(personList, key = attrgetter('age')) sortedPerons1 = sorted(personList, key=attrgetter('height')) print("根据年龄进行排序后的结果:") for person in sortedPerons: person.showMysef() print() print("根据身高进行排序后的结果:") for person in sortedPerons1: person.showMysef() # print("根据身高和体重的综合情况来排序:") # sortedPerons1 = sorted(personList, key=attrgetter("height" + "weight")) # for person in sortedPerons1: # person.showMysef() # testTheDinner() testSortPerson() # testPersonListInPython() # testArray()