一文吃透python常见设计模式

目录

  • 六大设计原则( SOLID )
    • 单一职责原则
    • 里氏替换原则
    • 迪米特法则
    • 接口隔离原则
    • 依赖倒置原则
    • 开闭原则
  • 单例模式(创建型)
    • 概述
    • 实现
      • 使用装饰器
      • 使用基类
      • 使用元类
  • 工厂模式(创建型)
    • 概述
    • 简单工厂
    • 工厂方法
    • 抽象工厂
  • 建造者模式(创建型)
    • 概述
    • 实现
  • 策略模式(行为型)
    • 概述
    • 实现
  • 观察者模式(行为型)
    • 概述
    • 实现
  • 代理模式(结构型)
    • 概述
    • 实现
      • 实现缓存
      • 实现远程服务代理
  • 装饰器模式(结构型)
    • 概述
    • 实现
  • 适配器模式(结构型)
    • 概述
    • 实现

六大设计原则( SOLID )

单一职责原则

单一职责原则(Single Responsibility Principle):一个类应该只有一个发生变化的原因。

主要为了不要让一个类承担过多的职责。避免职责耦合在一起,避免一个职责的变化影响到其他职责。

举个例子:一个视频播放系统,一个客户端类有两个功能接口,视频播放接口和音频播放接口。虽然这样的设计很常见,但却不满足单一职责原则的。原因是,如果对视频播放有变更需求或者对音频播放有修改需求,都会变更视频客户端的类结构。符合单一原则的设计是,将视频播放单元和音频播放单元各建一个类,播放客户端继承两个类,构成客户端。

单一职责原则的最大难点在于职责的划分,试想,以上划分是否是符合单一职责了?既是,也不是。试想,如果将视频传输和音频传输的协议信息和数据信息区分开,为符合这种粒度的单一职责原则就必须要有协议传输类和数据传输类的划分。如果接着细分,可能一个简单的小模块,都要设计非常多的类。因此,单一职责原则粒度的选择,应该根据业务流程和人员分工来进行考虑。一些基本的划分,似乎已经成了行业规范性的内容,比如,业务逻辑与用户信息管理的划分等。

单一职责的好处:

  1. 类的复杂性降低,实现什么职责都有清晰明确的定义;

  2. 可读性高,复杂性降低,可读性自然就提高了;

  3. 可维护性提高,可读性提高了,那自然更容易维护了;

  4. 变更引起的风险降低,变更是必不可少的,如果接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,这对系统的扩展性、维护性都有非常大的帮助。

里氏替换原则

里氏替换原则(Liskov Substitution Principle):所有引用基类的地方必须能透明地使用其子类的对象。

通俗点讲,就是只要父类能出现的地方,子类就可以出现,而且替换为子类也不会产生任何错误或异常。

可以理解为子类继承父类时,尽量不要修改父类方法预期的行为,也就是尽量不要重写父类方法。

在运用里氏代换原则时,我们尽量把父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类中声明的方法,在运行时再确定其子类类型,用子类实例来替换父类实例。

迪米特法则

迪米特法则(Law of Demeter):只与你的直接朋友交谈,不跟“陌生人”说话。

通俗的讲,一个类对自己需要耦合的类应该知道的最少,你内部多么复杂和我没关系,我只对你提供的public方法感兴趣。这样的话,如果一个系统符合迪米特法则,那么当其中某一个类发生修改时,就会尽量少地影响其他模块,降低系统的耦合度,使类与类之间保持松散的耦合关系。

接口隔离原则

接口隔离原则(Interface Segregation Principle):建立单一接口而不是建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。

也就是说,我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。

比如说,定义了一个接口MyInterface。

public interface MyInterface{public void method1();  public void method2();  public void method3();  
}

但是实现类A只想实现接口中的method1方法,而实现类B只想实现接口中的method2、method3方法。此时只写一个接口让A、B都去实现该接口的话,会导致AB中会实现不需要的方法,这样设计接口会导致接口比较臃肿,因此我们应该要把这个接口拆分开来写。

接口隔离原则跟之前的单一职责原则很相似,但其实不同。单一职责原则注重的是职责,针对的是程序中的实现和细节;而接口隔离原则注重的是对接口依赖的隔离,主要针对抽象。

依赖倒置原则

依赖倒置原则(Dependence Inversion Principle):上层模块不应该依赖底层模块,它们都应该依赖于抽象。

即接口或抽象类不依赖于实现类,而实现类依赖接口或抽象类。

就是说如果类与类直接依赖细节,那么就会直接耦合,那么当修改时,就会同时修改依赖者代码,这样限制了可扩展性。依赖倒置就是为了解决耦合。让程序依赖于抽象。

依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性。

开闭原则

开闭原则(Open Closed Principle):一个软件实体,如类、模块和函数应该对扩展开放,对修改关闭。

在开发过程中,需求的变化如果要通过修改原有的代码来完成,那么就很可能将新的错误引入到旧的代码中。因此才有了开放封闭原则。就是说,需求改变时,我们应该尽量通过拓展的方式、即加入新代码来实现变化。

开闭原则是最基础的一个原则,前面介绍的5个原则都是开闭原则的具体形态,而开闭原则才是其精神领袖。

单例模式(创建型)

概述

单例模式(Singleton Pattern)属于创建型模式。为了确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

单例模式应该是所有设计模式中结构最简单的一个。

通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。

优点

1、由于单例模式要求在全局内只有一个实例,因而可以节省比较多的内存空间;
2、全局只有一个接入点,可以更好地进行数据同步控制,避免多重占用;3、单例可长驻内存,减少系统开销。

缺点

1、单例模式的扩展是比较困难的;
2、赋于了单例以太多的职责,某种程度上违反单一职责原则;
3、单例模式是并发协作软件模块中需要最先完成的,因而其不利于测试;
4、单例模式在某种情况下会导致“资源瓶颈”。

应用场景

1、生成全局惟一的序列号;
2、访问全局复用的惟一资源,如磁盘、总线等;
3、单个对象占用的资源过多,如数据库等;
4、系统全局统一管理,如Windows下的Task Manager;
5、网站计数器。

实现

使用装饰器

def singleton(cls):instances = {}def wrapper(*args, **kwargs):if cls not in instances:instances[cls] = cls(*args, **kwargs)return instances[cls]return wrapper@singleton
class Foo(object):passif __name__ == "__main__":foo1 = Foo()foo2 = Foo()print(foo1 is foo2)

使用基类

class Singleton(object):_instance = Nonedef __new__(cls, *args, **kwargs):if not hasattr(cls, '_instance'):cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)return cls._instance# 继承Singleton
class Foo(Singleton):passif __name__ == "__main__":foo1 = Foo()foo2 = Foo()print(foo1 is foo2)

使用元类

class Singleton(type):# 元类必须继承typedef __call__(cls, *args, **kwargs):if not hasattr(cls, '_instance'):cls._instance = super(Singleton, cls).__call__(*args, **kwargs)return cls._instanceclass Foo(metaclass=Singleton):passif __name__ == "__main__":foo1 = Foo()foo2 = Foo()print(foo1 is foo2)

工厂模式(创建型)

参考:https://www.cnblogs.com/Zzbj/p/15778464.html

概述

工厂模式(Factory Pattern)属于创建型模式,它提供了一种创建对象的最佳方式。

在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

**意图:**定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。

**主要解决:**主要解决接口选择的问题。

**何时使用:**我们明确地计划不同条件下创建不同实例时。

简单工厂

统一使用一个类作为对外接口,根据参数的不同,去选择实例化不同的类。

"""
两个产品(两种类型的书)
"""class TechnicalBooks(object):"""技术书籍"""def publish(self):return "Python-Book"class LiteraryBooks(object):"""文学书籍"""def publish(self):return "Black Hole Book"# 现在我们有两种类型的书,分别是TechnicalBooks和LiteraryBooks的书
# 按照我们平常的方法来实例化的话,此时创建对象时是会对客户端暴露真正创建的类
it_books = TechnicalBooks()
ly_books = LiteraryBooks()# 这时我们就可以构造一个"简单工厂"把所有实例化的过程封装在里面,把真正实例的类隐藏起来
class SimpleFactory(object):"""简单工厂"""@staticmethoddef publish_book(name):if name == 'technical':return TechnicalBooks()elif name == 'literary':return LiteraryBooks()it_books2 = SimpleFactory.publish_book('technical')
ly_books2 = SimpleFactory.publish_book('literary')

简单工厂的好处在于,把不同类的实例化统一到一个"工厂",即不对外暴露真正的创建类,也提供了一个对外的统一接口。

但是简单工厂也有一个缺点,那就是违背了solid的 “开闭原则”,假如我们还需要增加一种书籍,那就必须要对简单工厂SimpleFactory进行源码的修改。

简单工厂使用场景:

  • 已经确定有多少具体的类,不会再增加的情况下使用。

例如:某个系统,已经明确就只会有MySQL、Redis、MongoDB三个数据库的情况下,可以直接使用简单工厂模式。

工厂方法

上面的简单工厂我们已经知道了,如果新增一些类型的时候会违背软件设计中的开闭原则,但是我们希望在扩展新的类时,不要修改原有的代码。这个时候我们可以在简单工厂的基础上把SimpleFactory抽象成不同的工厂,每个工厂对应生成自己的产品,这就是工厂方法。

"""
两个产品(两种类型的书)
"""
import abc# 真正进行实例化的类
class TechnicalBooks(object):"""技术书籍"""def publish(self):return "Python-Book"class LiteraryBooks(object):"""文学书籍"""def publish(self):return "Black Hole Book"# 抽象工厂:先定义抽象类,然后每种类型的书籍都有自己对于的工厂
class AbstractFactory(metaclass=abc.ABCMeta):"""抽象工厂"""@abc.abstractmethoddef publish_book(self):passclass TechnicalFactory(AbstractFactory):"""技术书籍工厂"""def publish_book(self):return TechnicalBooks()class LiteraryFactory(AbstractFactory):"""文学书籍工厂"""def publish_book(self):return LiteraryBooks()it_books2 = TechnicalFactory().publish_book()
ly_books2 = LiteraryFactory().publish_book()

这样每个工厂就只负责生产自己的产品,避免了在新增产品时需要修改工厂的代码,遵循了"开闭原则",如果需要新增产品时,只需要增加相应的工厂即可。

比如要新增一种小说类型的书籍,只需新增一个NovelBooks类和NovelFactory类。

工厂方法的使用场景

  • 当系统中拥有的子类很多,并且以后可能还需要不断拓展增加不同的子类时。
  • 当设计系统时,还不能明确具体有哪些类时。

在工厂方法中,使用者不需要知道具体的产品类名,只需要知道其对应的工厂即可。

抽象工厂

工厂方法解决了"开闭原则"的问题,但是我们出版书籍之前肯定还会有其他的步骤,比如印刷。
如果每一个步骤我们就要写一个对应的工厂类,那我们就会需要创建很多很多类了。
因此为了解决这个问题,我们就要需要抽象工厂类,让一个工厂可以生产同一类的多个产品或多个动作(步骤),这就是抽象工厂。

"""
两个产品(两种类型的书)
"""
import abc# 印刷书籍
class PrintingTechnicalBooks(object):"""印刷技术书籍"""def printing(self):return "Print-Python-Book"class PrintingLiteraryBooks(object):"""印刷文学书籍"""def printing(self):return "Print Black Hole Book"# 出版书籍
class TechnicalBooks(object):"""出版技术书籍"""def publish(self):return "Python-Book"class LiteraryBooks(object):"""出版文学书籍"""def publish(self):return "Black Hole Book"# 抽象工厂:先定义抽象类,然后每种类型的书籍都有自己对于的工厂
class AbstractFactory(metaclass=abc.ABCMeta):"""抽象工厂"""@abc.abstractmethoddef print_book(self):pass@abc.abstractmethoddef publish_book(self):passclass TechnicalFactory(AbstractFactory):"""技术书籍工厂"""def print_book(self):return PrintingTechnicalBooks()def publish_book(self):return TechnicalBooks()class LiteraryFactory(AbstractFactory):"""文学书籍工厂"""def print_book(self):return PrintingLiteraryBooks()def publish_book(self):return LiteraryBooks()# 实例化工厂对象
it = TechnicalFactory()
ly = LiteraryFactory()# 印刷书籍
it_print = it.print_book()
ly_print = ly.print_book()
# 出版书籍
it_publish = it.publish_book()
ly_publish = ly.publish_book()

抽象工厂模式与工厂方法模式的区别:抽象工厂中的一个工厂对象可以负责多个不同产品对象的创建 。

抽象工厂的使用场景

  • 当多个产品(步骤)集合在一起,组成产品族时。
  • 对于一个产品族,如果只想显示接口而不是实现时。

建造者模式(创建型)

参考:https://blog.csdn.net/songpeiying/article/details/131902497

概述

如果我们想建造一个由多个部分构成的对象,而且它的构成需要一步接着一步完成,只有当各个部分都创建好,这个对象才算是完整的。这就是建造者模式(Builder Pattern)。

建造者模式是一种创建型设计模式,它可以将复杂对象的构造与表现分离,这样同一个构造过程可用于创建多个不同表现形式。该模式通过一步一步创建复杂对象,将对象的构造过程与表示过程解耦。

例如快餐店使用的就是建造者模式。即使存在多种汉堡和不同包装,但是准备一个汉堡及打包的流程是相同的。经典汉堡和奶酪汉堡之间的区别在于表现,而不是建造过程。指挥者是出纳员,将需要准备什么产品传达给工作人员,建造者是工作人员的个体,关注具体的顺序。

优点

  • 将对象的构造过程与其表现形式解耦,可以灵活地组合不同的构造过程来得到不同的表现形式。
  • 使得代码结构更加清晰,易于维护和扩展。
  • 可以有效地控制对象的创建过程,生成符合设计要求的对象。

缺点

  • 建造者模式需要编写较多的代码,且在构造对象时需要一定的时间和精力。
  • 如果需要创建的对象较少或者结构较简单,则建造者模式可能会增加代码的复杂性。

应用场景

一个对象经过多个步骤来创建,并且要求构建过程可以产生不同的表现。

例如,在 Web 开发中,我们可以使用建造者模式来构建复杂的 HTML 页面、邮件消息等。在游戏开发中,我们可以使用建造者模式来生成游戏场景、角色等。在数据库开发中,我们可以使用建造者模式来构建 SQL 查询语句等。

实现

通常来说,建造者模式包括以下几个组成部分:

  • 产品类(Product):表示被构造的复杂对象。
  • 抽象建造者类(Builder):声明抽象方法来构建不同部分的复杂对象。
  • 具体建造者类(Concrete Builder):实现抽象建造者类中的方法来构建对象的各个部分,并返回构建好的对象。
  • 指挥者类(Director):负责调用具体建造者类中的方法来构建复杂对象。

具体的工作流程如下:

  1. 创建一个产品类,该类表示待构建的复杂对象,可以包含多个部分。
  2. 创建一个抽象的建造者类,该类声明了构建不同部分的抽象方法。
  3. 创建具体的建造者类,该类实现抽象建造者类中的方法,构建对象的各个部分,并返回构建好的对象。
  4. 创建一个指挥者类,该类负责调用具体建造者类中的方法来构建复杂对象。
  5. 在应用程序中,创建具体建造者类的实例,并将其传递给指挥者类。指挥者类使用具体建造者类中的方法来构建复杂对象。
  6. 最后,应用程序可以使用构建好的复杂对象。

假设我们需要构建一个计算机,计算机包含CPU、内存、硬盘、显卡等部件。我们可以使用Python建造者模式来构建这个复杂对象。具体实现步骤如下:

  1. 首先创建一个产品类——计算机类,它包含CPU、内存、硬盘、显卡等部件,并提供各个部件的设置和获取方法。
  2. 然后创建一个抽象建造者类——计算机建造者类,它声明了构建不同部分的抽象方法:
  3. 接着创建具体的建造者类——台式机建造者类和笔记本建造者类,它们实现了抽象建造者类中的方法,构建对象的各个部分,并返回构建好的对象:
  4. 由于计算机建造的过程比较复杂,我们需要创建一个指挥者类——计算机装配员类,它负责调用具体建造者类中的方法来构建复杂对象:
  5. 最后,我们可以使用以上代码来创建不同的计算机

代码如下:

 
# 创建产品类
class Computer():def __init__(self):self.cpu = Noneself.memory = Noneself.hark_disk = Noneself.graphics_card = Nonedef set_cpu(self, cpu):self.cpu = cpudef set_memory(self, memory):self.memory = memorydef set_hard_disk(self, hard_disk):self.hark_disk = hard_diskdef set_graphics_card(self, graphics_card):self.graphics_card = graphics_carddef get_specs(self):specs = f'CPU:{self.cpu}\nMemory:{self.memory}\nHard Disk:{self.hark_disk}\nGraphics Card:{self.graphics_card}'return specs# 创建抽象的建造者类
class ComputerBuilder():def build_cpu(self): # 声明构建不同部分的抽象方法passdef build_memory(self):passdef build_hard_disk(self):passdef build_graphics_card(self):passdef get_computer(self):pass# 创建具体的建造者类
class DesktopBuilder(ComputerBuilder): # 继承抽象的建造者类def __init__(self):self.computer = Computer() # 初始化产品类def build_cpu(self): # 实现抽象建造者方法self.computer.set_cpu("Intel Core 7") # 设置产品类方法值def build_memory(self):self.computer.set_memory("16GB DDR4")def build_hard_disk(self):self.computer.set_hard_disk("1TB HDD")def build_graphics_card(self):self.computer.set_graphics_card("NVIDIA GTX 1050")def get_computer(self):return self.computerclass LaptopBuilder(ComputerBuilder):def __init__(self):self.computer = Computer()def build_cpu(self):self.computer.set_cpu("Intel Core i5")def build_memory(self):self.computer.set_memory("8GB DDR4")def build_hard_disk(self):self.computer.set_hard_disk("256GB SSD")def build_graphics_card(self):self.computer.set_graphics_card("Intergrated")def get_computer(self):return self.computer
# 创建指挥者
class ComputerAssembler:def __init__(self,builder):self.builder = builderdef assemble(self):self.builder.build_cpu() # 调用具体建造者类方法self.builder.build_memory()self.builder.build_hard_disk()self.builder.build_graphics_card()return self.builder.get_computer()
# 创建台式计算机
desktop_builder = DesktopBuilder()
desktop_assembler = ComputerAssembler(desktop_builder)
desktop = desktop_assembler.assemble()
print(desktop.get_specs())# 创建笔记本计算机
laptop_builder = LaptopBuilder()
laptop_assembler = ComputerAssembler(laptop_builder)
laptop = laptop_assembler.assemble()
print(laptop.get_specs())

策略模式(行为型)

参考:https://blog.csdn.net/songpeiying/article/details/131939340

概述

策略模式(Strategy)是一种行为型策略模式。定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化,从而达到代码的可扩展性、灵活性和可维护性。

优点

  1. 代码可扩展性和灵活性好,能够适应不同的需求。
  2. 降低模块之间的耦合度,提高代码的可维护性和可读性。
  3. 具有良好的可扩展性,可以动态地增加、删除或替换算法。

缺点

  1. 会增加一定的复杂度,需要额外的类定义。
  2. 可能会导致系统中出现较多的类,增加系统的复杂度
  3. 需要客户端了解不同策略的差异,才能选择合适的策略。

应用场景

1.需要在运行时根据不同情况选择不同算法的场景。

2.需要封装业务逻辑的场景。

3.需要对同一种算法进行多次修改的场景。

实现

策略模式实现通常需要以下三个角色:

  1. 抽象出一个策略接口,定义策略的方法。
  2. 将不同的算法分别封装为具体的策略类,并实现策略接口的方法。
  3. 创建一个策略上下文类,负责调用不同的策略,根据不同的需求选择合适的策略。

以电商中的出校策略为例,客户可以根据自己需要选择不同的促销策略,比如有满减策略和折扣策略。

代码如下:

# 定义促销策略接口
class PromotionStrategy():# 定义促销活动方法def do_promotion(self, price):pass# 定义具体促销策略
class ReductionPromotion(PromotionStrategy):  # 满减def do_promotion(self, price):if price >= 200:return price - 50return priceclass DiscountPromotion(PromotionStrategy):  # 折扣def do_promotion(self, price):return price * 0.8# 定义上下文类,负责调用不同的促销策略
class PromotionContext():def __init__(self, promotion_strategy: PromotionStrategy):self._promotion_strategy = promotion_strategydef execute_promotion_strategy(self, price):return self._promotion_strategy.do_promotion(price)# 使用满减策略计算价格
promotion_context = PromotionContext(ReductionPromotion())
print(promotion_context.execute_promotion_strategy(200))
# 使用折扣策略计算价格
promotion_context = PromotionContext(DiscountPromotion())
print(promotion_context.execute_promotion_strategy(200))

观察者模式(行为型)

参考:https://blog.csdn.net/songpeiying/article/details/131945036

概述

观察者模式(Observer Pattern)又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式。

观察者模式定义了对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。

例如在MVC模式中有这样一个非常常见的例子,假设在两个视图(例如,一个饼图和一个电子表格)中使用同一个模型的数据无论何时更改了模型,都需要更新两个视图。这就是观察者设计模式要处理的问题。

观察者模式描述单个对象(发布者,又称为主持者或可观察者)与一个或多个对象(订阅者又称为观察者)之间的发布-订阅关系。在MVC例子中,发布者是模型,订阅者是视图。然而.MVC并非是仅有的发布-订阅例子。信息聚合订阅(比如,RSS或Atom)是另一种例子。许多读者通常会使用一个信息聚合阅读器订阅信息流,每当增加一条新信息时,他们就能自动地获取到更新。

观察者模式背后的思想等同于MVC和关注点分离原则背后的思想,即降低发布者与订阅者之间的耦合度,从而易于在运行时添加/删除订阅者。此外,发布者不关心它的订阅者是谁。它只是将通知发送给所有订阅者。

优点:

  • 提供灵活性,减少类间的耦合度。
  • 可以实现动态的发布-订阅机制,并且观察者可在运行时动态地加入或离开主题。
  • 实现开放-封闭原则,使得主题和观察者可以独立地扩展和修改。

缺点

  • 观察者过多会导致通知效率等性能问题。
  • 如果观察者与主题相互依赖,修改其中任意一方的代码都需要修改对方的代码,不利于系统的扩展和维护。

应用场景:

  • 当一个对象的改变需要同时改变其他对象时,比如消息订阅模型。
  • 当一个抽象模型有两个方面,其中一个方面需要依赖另外一个方面时。

实现

工作原理:

  • 观察者将自己注册到主题中。
  • 主题维护一个观察者列表。
  • 当主题状态发生改变时,通知所有观察者。
  • 观察者执行相应的操作。

下面我们以一个简单的消息订阅系统为例,详细说明 Python观察者模式实现消息订阅功能。

首先,我们需要定义两个类:主题类和观察者类。主题类包含一个观察者列表和通知观察者的方法,观察者类包含需要执行的方法。,NewsSubject类为新闻主题类,包含observers字典和register_observer、remove_observer、notify_observers、set_news等方法。其中,observers字典的键为订阅主题,值为观察者列表。NewsObserver类为观察者类,包含name属性和update方法。

接下来,我们可以创建主题对象和观察者对象,并将观察者对象注册到主题对象中。创建了一个主题对象subject和三个观察者对象observer_1、observer_2和observer_3,并将观察者对象注册到主题对象中,每个观察者订阅不同的新闻主题。

# *********************************观察者模式实现消息订阅功能# 新闻主题类
class NewsSubject():def __init__(self):self.observers = {} # 字典self.news = Nonedef register_observer(self, observer, topic):if topic not in self.observers:self.observers[topic] = []self.observers[topic].append(observer)def remove_observer(self, observer, topic):if topic in self.observers:self.observers[topic].remove(observer)def notify_observers(self,topic):if topic in self.observers: # if语句,存在观察者订阅的主题新闻,通知观察者for observer in self.observers[topic]:observer.update(self.news)  # 通知观察者新闻def set_news(self, news, topic):self.news = newsself.notify_observers(topic)class NewsObserver():def __init__(self, name):self.name = namedef update(self, news):print(f"{self.name}收到新闻:{news}")subject = NewsSubject()observer_1 = NewsObserver("name1")
observer_2 = NewsObserver("name2")
observer_3 = NewsObserver("name3")subject.register_observer(observer_1, "国内新闻")
subject.register_observer(observer_2, "国外新闻")
subject.register_observer(observer_3, "娱乐新闻")subject.set_news("中国的新闻,中国......", "国内新闻")
subject.set_news("外国的新闻,美国......", "国外新闻")
subject.set_news("明星...","娱乐新闻")
subject.set_news("歌星...","娱乐新闻")

运行结果:

name1收到新闻:中国的新闻,中国......
name2收到新闻:外国的新闻,美国......
name3收到新闻:明星...
name3收到新闻:歌星...

在上述代码中,我们发布了三条新闻消息,并使用set_news方法通知订阅相应主题的观察者。观察者会收到通知并执行相应的操作,例如打印收到通知的信息。

这就是 Python观察者模式实现消息订阅功能的方法。当主题状态发生变化时,通知所有订阅该主题的观察者,执行相应的操作,实现了消息订阅功能。

代理模式(结构型)

参考:https://blog.csdn.net/songpeiying/article/details/131932669

概述

在某些应用中,我们想要早访问某些对象之前执行一些重要操作。例如,访问敏感信息之前,做一些权限认证,或者把一个创建成本较高的对象延迟到用户使用时才真正创建。

这类操作通常使用代理模式实现。

代理模式(Proxy Pattern)是一种结构型设计模式。在代理模式中,代理对象充当了另一个对象的占位符,以控制对该对象的访问。

代理对象和被代理对象实现了相同的接口,因此它们可以互相替代。客户端和代理对象之间的交互是无缝的,因为它们的接口是一样的。

代理模式的主要功能是为其他对象提供一个代理,以控制对对象的访问。代理对象可以在调用被代理对象之前或之后执行一些操作,例如身份验证,缓存等。

优点:

  1. 保护了真实对象的访问,可以对访问进行限制和控制;
  2. 可以提高访问效率,通过代理对象可以缓存数据或者调用其他服务等;
  3. 可以提高系统的灵活性,因为代理对象可以在不影响真实对象的情况下扩展其功能。

缺点:

  1. 可能引入额外的复杂性,因为需要创建代理对象。
  2. 此外,如果代理对象没有正确实现与真实对象相同的接口,可能会导致客户端代码无法正常工作。

应用场景:

  1. 身份验证:在访问某些敏感数据或操作时,可以使用代理对象执行身份验证,以确保用户有权访问该数据或执行该操作。
  2. 缓存:使用代理对象来缓存数据,以便在下一次访问时可以更快地访问数据。
  3. 远程服务:代理对象可以用作远程服务的本地代表,在使用远程服务时提供更好的用户体验,同时也可以提高访问效率。

实现

代理模式可以通过组合或继承实现。通常,代理对象会继承与真实对象相同的接口,并在继承的方法中调用真实对象的方法。

实现缓存

假设我们正在开发一个网络应用程序,该应用程序可以向外提供图片资源。由于图片资源较大,我们希望通过代理模式来缓存这些数据,以提高程序的性能和响应速度。

 
from abc import ABC, abstractmethod# 定义抽象基类
class Image():@abstractmethoddef display(self):pass# 定义具体子类,继承Image,重新display方法
class RealImage(Image):def __init__(self, filename): # 构造函数,接收一个参数filenameself.filename = filename  # 文件名保存在filename实例变量中self.load_from_disk()     # 调用load_from_disk()方法,从磁盘中加载图片数据'''从磁盘中加载图片数据子类RealImage独有的方法由于这个过程比较耗时,因此我们需要在初始化时进行加载,避免在图片显示时等待。'''def load_from_disk(self): #print("loading " + self.filename)# 显示图片def display(self):print("Displaying " + self.filename)# 定义代理类,继承Image,重写display方法
class ImageProxy(Image):def __init__(self, filename):self.filename = filename# 定义真实图片self.real_image = Nonedef display(self):if self.real_image is None:# 如果没有加载真实图片# 调用具体子类RealImage, 创建真实图片对象, 缓存真实对象,避免重复加载图片资源self.real_image = RealImage(self.filename)# 显示真实图片self.real_image.display()image = ImageProxy("test.jpg")
print("第一次调用display方法:图片没有加载真实图片,调用子类RealImage, 创建真实图片对象")
image.display()
print("第二次调用display方法: 直接从缓存中获取图片")
image.display()

运行结果:

第一次调用display方法:图片没有加载真实图片,调用子类RealImage, 创建真实图片对象
loading test.jpg
Displaying test.jpg
第二次调用display方法: 直接从缓存中获取图片
Displaying test.jpg

在上面的示例中,我们定义了一个抽象基类Image,其中包含一个display()抽象方法。接着,我们定义了一个具体子类RealImage,它代表了真实的图片对象,负责从磁盘中加载图片数据,并提供display()方法用于显示图片。

我们还定义了一个代理类ImageProxy,它也实现了Image接口。当客户端调用display()方法时,代理类会检查是否已经加载了真实的图片对象。如果没有,代理会先创建一个真实的图片对象,然后再调用它的display()方法。这样,代理对象就可以缓存真实对象,避免了重复加载图片资源,从而提高程序性能。

使用代理模式时,客户端代码只需要与代理对象交互,并不需要知道真实对象的存在。当代理对象需要访问真实对象时,它会自动创建并调用真实对象,从而实现对真实对象的间接访问。

实现远程服务代理

Python 代理模式实现远程服务功能:

from abc import ABC, abstractmethod# 定义抽象类:远程服务接口
class RemoteService():@abstractmethoddef request(self, param: str) -> str: # 抽象方法:接受字符串类型参数,返回字符串类型结果pass# 实现远程服务
class RemoteServiceIml(RemoteService):def request(self, param: str) -> str:return f"Remote service received {param}"# 实现代理类
class RemoteServiceProxy(RemoteService):def __init__(self, remote_service: RemoteService):self._remote_service = remote_servicedef request(self, param: str) -> str:print("Remote service aouthentication...")# 调用远程服务器前,本地操作...,例如:身份认证、参数校验if not param:raise ValueError("Missing parameter")# 调用远程服务,返回结果return self._remote_service.request(param)remote_service_impl = RemoteServiceIml()
proxy = RemoteServiceProxy(remote_service_impl)res = proxy.request("test")
print(res)

运行结果:

Remote service aouthentication...
Remote service received test

在上述代码中,我们首先定义了一个远程服务接口RemoteService,其中有一个request方法,用于处理远程服务请求,返回一个字符串类型的结果。同时,我们还实现了RemoteServiceImpl类,该类用于实现具体的远程服务逻辑。在具体的request方法中,简单地将接收到的参数拼接成一条消息,并返回。

接下来,我们使用代理模式实现了RemoteServiceProxy类,该类也实现了RemoteService接口,并接收一个RemoteService实例作为构造函数参数。当我们调用request方法时,代理类先进行身份认证、参数校验等本地操作,然后再调用远程服务,最后将结果返回。

装饰器模式(结构型)

概述

装饰器模式定义如下:动态地给一个对象添加一些额外的职责。在增加功能方面,装饰器模式比生成子类更为灵活。

装饰器模式和上一节说到的代理模式非常相似,可以认为,装饰器模式就是代理模式的一个特殊应用,两者的共同点是都具有相同的接口,不同点是侧重对主题类的过程的控制,而装饰模式则侧重对类功能的加强或减弱。上一次说到,JAVA中的动态代理模式,是实现AOP的重要手段。而在Python中,AOP通过装饰器模式实现更为简洁和方便。先来解释一下什么是AOP。AOP即Aspect Oriented Programming,中文翻译为面向切面的编程,它的含义可以解释为:如果几个或更多个逻辑过程中(这类逻辑过程可能位于不同的对象,不同的接口当中),有重复的操作行为,就可以将这些行为提取出来(即形成切面),进行统一管理和维护。举例子说,系统中需要在各个地方打印日志,就可以将打印日志这一操作提取出来,作为切面进行统一维护。从编程思想的关系来看,可以认为AOP和OOP(面向对象的编程)是并列关系,二者是可以替换的,也可以结合起来用。

在Python语言中,是天然支持装饰器的。

优点:

  1. 增强已有函数的功能,提高代码的复用性和可维护性;
  2. 不修改原函数的代码,避免了可能引入的新问题。

缺点:

  1. 由于装饰器本质上是一个函数,因此会产生额外的函数调用开销;
  2. 如果装饰器过多,会导致代码的可读性降低。

应用场景:

  1. 缓存:可以为一些需要大量计算的函数添加缓存功能,减少函数的计算时间;
  2. 日志:可以为一些需要记录日志的函数添加日志功能;
  3. 计时:可以为一些需要计时的函数添加计时功能。

实现

可参考文章:https://blog.csdn.net/qq_43745578/article/details/128710602

适配器模式(结构型)

概述

适配器模式 (Adapter pattem)是一种结构型设计模式,帮助我们实现两个不兼容接口之间的兼容。首先,解释一下不兼容接口的真正含义。如果我们希望把一个老组件用于一个新系统中,或者把一个新组件用于一个老系统中,不对代码进行任何修改两者就能够通信的情况很少见。但又并非总是能修改代码,或因为我们无法访问这些代码( 例如,组件以外部库的方式提供 ),或因为修改代码本身就不切实际。在这些情况下,我们可以编写一个额外的代码层,该代码层包含让两个接口之间能够通信需要进行的所有修改。这个代码层就叫适配器。

例如,一部智能手机或者一台平板电脑,在想把它(比如,iPhone手机的闪电接口)连接到你的电脑时就需要使用一个USB适配器。如果你从大多数欧洲国家到英国旅行,在为你的笔记本电脑充电时需要使用一个插头适配器。如果你从欧洲到美国旅行,同样如此;反之亦然。适配器无处不在!

适配器模式和装饰模式有一定的相似性,都起包装的作用,但二者本质上又是不同的,装饰模式的结果,是给一个对象增加了一些额外的职责,而适配器模式,则是将另一个对象进行了“伪装”。

优点:

  1. 提高了系统的灵活性,使得系统具备更好的可扩展性和可移植性。
  2. 增强了系统的兼容性,使得原本不兼容的类可以合作无间,降低了系统维护成本。
  3. 降低了系统耦合度,减少了系统间的依赖关系。

缺点:

  1. 适配器模式增加了代码的复杂度,可能会影响系统性能。
  2. 在适配器模式中,适配器本身会成为系统的一个单点故障。

应用场景:

  1. 系统需要与现有的代码或第三方库进行交互,但它们的接口与系统的要求不符。

  2. 系统需要将同一接口的多个实现进行统一,提高系统的可维护性和可扩展性。

工作原理

适配器模式主要由适配器、待适配接口和目标接口三个部分组成。

  • 适配器:通过继承或组合待适配接口,实现目标接口,使得待适配接口可以转换为目标接口。
  • 待适配接口:需要被转换的原始接口。
  • 目标接口:系统期望的兼容接口,适配器将待适配接口转换为目标接口,以满足系统间接口的兼容性需求。

实现

假设您有一个现有的类 Logger,它提供了一种将消息记录到文件的方法。 此类需要将文件指定为文件路径字符串:

class Logger:def __init__(self, file_path: str):self.file_path = file_pathdef log(self, message: str):with open(self.file_path, 'a') as file:file.write(message + '\n')

现在假设您有一个客户端想要使用 Logger 类,但它希望传递一个文件对象而不是文件路径字符串。 在这种情况下,您可以创建一个适配器类,它接受一个文件对象并使用它来创建一个 Logger 实例:

class FileLoggerAdapter:def __init__(self, file: File):self.logger = Logger(file.name)def log(self, message: str):self.logger.log(message)

此适配器类允许客户端代码使用 FileLoggerAdapter 类,就好像它具有与 Logger 类相同的接口一样,但它在幕后将文件对象转换为文件路径字符串。 客户端可以像这样使用适配器类:

# Open a file for writing
file = open('log.txt', 'w')# Create a FileLoggerAdapter instance
logger = FileLoggerAdapter(file)# Log a message using the adapter
logger.log('This is a log message')

这个例子展示了如何使用适配器模式来允许两个具有不兼容接口的类无缝地协同工作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/101352.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Response Status Code 301、302

目录 Information Django redirect Influence Information HTTP状态码301、302和304分别表示以下情况: codeinformation301(Moved Permanently) 永久重定向。当请求的资源已经被永久地移动到了一个新的URI时,服务器会返回这个…

【数据结构】Decreasing String—CF1886C

Decreasing String—CF1886C 代码我现在还不是很理解&#xff0c;群友说是单调栈。 C o d e Code Code #include <bits/stdc.h> #define int long long #define sz(a) ((int)a.size()) #define all(a) a.begin(), a.end() using namespace std; using PII pair<int…

JVM-Java字节码的组成部分

Java字节码文件是一种由Java编译器生成的二进制文件&#xff0c;用于在Java虚拟机&#xff08;JVM&#xff09;上执行Java程序。字节码文件的组成可以分为以下几个主要部分&#xff1a; 基本信息&#xff1a; 魔数&#xff08;Magic Number&#xff09;&#xff1a;前4个字节的…

RabbitMQ队列持久化的重要性与意义

1. 数据安全性 持久化队列的一个主要目的是确保数据的安全性。在RabbitMQ中&#xff0c;消息通常存储在内存中&#xff0c;以提高消息传递的速度。然而&#xff0c;如果队列没有持久化&#xff0c;一旦RabbitMQ服务器发生故障或者重启&#xff0c;所有未被处理的消息都会丢失。…

VUEX的基础使用存值及异步

目录 什么是VUEX 有什么作用 安装 取值 异步 什么是VUEX VUEX 是一个用于状态管理的状态容器模式&#xff08;state management pattern&#xff09;库&#xff0c;用于 Vue.js 应用程序。它允许你在应用程序中集中管理和共享状态&#xff0c;并提供了一组用于更改状态的规则…

[安洵杯 2019]easy_web - RCE(关键字绕过)+md5强碰撞+逆向思维

[安洵杯 2019]easy_web 1 解题流程1.1 阶段一1.2 阶段二2 思考总结1 解题流程 1.1 阶段一 1、F12发现提示md5 is funny ~;还有img标签中,有伪协议和base64编码 2、url地址是index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=   这就有意思了,这里的img明显是编码后的…

C++ 命名空间-----namespace(超详细解析,小白必看系列)

目录 一、前言 &#x1f34e;什么是C 二、关键字 三、命名空间&#xff08;重点&#xff09; &#x1f350;C语言--命名冲突 &#x1f349;C--命名空间的定义 &#x1f353;C--命名空间的使用 四、C输入&输出 五、共勉 一、前言 既博主学过C语言后又一新的语言&a…

大厂设计师力推的14款平面图设计工具!

从事设计行业的工人或多或少会接触到平面图。例如&#xff0c;在建造新房、办公室、酒店等任何类型的建筑时&#xff0c;都需要使用平面图来保证项目的准确性。因此&#xff0c;掌握绘制平面图软件的技巧也非常重要。在保证效率的同时&#xff0c;结果的准确性也非常高。在本文…

seata分布式事务理论概述

分布式事务产生的原因&#xff1a; 数据库分库分表 应用的SOA化。就是业务的服务化(面向服务架构) 分布式事务的解决方案&#xff1a; 1、两阶段提交协议2PC 这里的两阶段提交和redolog binlog的两阶段提交不是一个东西&#xff0c;redo log和bin log的两阶段提交保证的是…

Vue2 Watch的语法

Watch语法 一、监听普通数据类型&#xff08;1&#xff09;把要监听的msg值看作方法名&#xff0c;来进行监听。&#xff08;2&#xff09;把要监听的msg值看作对象&#xff0c;利用hanler方法来进行监听 二、监听对象&#xff1a;&#xff08;1&#xff09;监听对象需要用到深…

Python算法练习 10.11

leetcode 394 字符串解码 给定一个经过编码的字符串&#xff0c;返回它解码后的字符串。 编码规则为: k[encoded_string]&#xff0c;表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。 你可以认为输入字符串总是有效的&#xff1b;输入字符串中没…

ubuntu 23.04安装中文输入法

使用ubuntu 23.04安装中文输入法&#xff0c;尝试了最新的搜狗&#xff0c;谷歌拼音&#xff0c;fcitx的原始拼音&#xff0c;最终的结果就是使用了谷歌拼音。 搜狗输入法&#xff1a;好用&#xff0c;但是用了没几天发现各种闪退&#xff0c;一打开就闪烁&#xff0c;根本无法…

c语言练习85:通讯录的实现(基于顺序表实现)

通讯录的实现(基于顺序表实现&#xff09; 基于动态顺序表实现通讯录 C语⾔基础要求&#xff1a;结构体、动态内存管理、顺序表、⽂件操作 1、功能要求 1&#xff09;⾄少能够存储100个⼈的通讯信息 2&#xff09;能够保存⽤⼾信息&#xff1a;名字、性别、年龄、电话、地址…

飞凌嵌入式受邀参加「NXP创新技术论坛」

2023年10月10日&#xff0c;「NXP创新技术论坛」在深圳湾万丽酒店举行&#xff0c;飞凌嵌入式作为NXP金牌合作伙伴受邀参加此次论坛&#xff0c;与众多智能工业行业的伙伴深入交流市场趋势与行业洞察&#xff0c;共同促进未来市场的发展。 本次论坛&#xff0c;飞凌嵌入式展示了…

docker版jxTMS使用指南:数据采集系统的高可用性

本文讲解4.6版jxTMS中数据采集系统的高可用性&#xff0c;整个系列的文章请查看&#xff1a;4.6版升级内容 docker版本的使用&#xff0c;请查看&#xff1a;docker版jxTMS使用指南 4.0版jxTMS的说明&#xff0c;请查看&#xff1a;4.0版升级内容 4.2版jxTMS的说明&#xff…

大数据学习(3)-hive分区表与分桶表

&&大数据学习&& &#x1f525;系列专栏&#xff1a; &#x1f451;哲学语录: 承认自己的无知&#xff0c;乃是开启智慧的大门 &#x1f496;如果觉得博主的文章还不错的话&#xff0c;请点赞&#x1f44d;收藏⭐️留言&#x1f4dd;支持一下博>主哦&#x…

Hive中生成自增序列的常用方法

在日常业务开发过程中&#xff0c;通常遇到需要hive数据表中生成一列唯一ID&#xff0c;当然连续递增的更好。 最近在结算业务中&#xff0c;需要在hive表中生成一列连续且唯一的账单ID&#xff0c;于是就了解生成唯一ID的方法 1. 利用row_number函数 语法&#xff1a;row_n…

ansible 调研

参考&#xff1a;自动化运维工具——ansible详解&#xff08;一&#xff09; - 珂儿吖 - 博客园 (cnblogs.com) ansible是新出现的自动化运维工具&#xff0c;基于Python开发&#xff0c;集合了众多运维工具&#xff08;puppet、chef、func、fabric&#xff09;的优点&#xf…

使用Plotly模拟远古博弈游戏_掷骰子

不乏投资大师、量化基金经理从着迷博弈游戏开始迈出步伐...... 开始学习使用python包Plotly模拟掷骰子。 安装Plotly 终端输入命令&#xff1a;python3 -m pip install --user plotly 创建骰子类 掷骰子 分析结果 绘制直方图 投掷一个骰子点数的分布 投掷两个骰子1000次的点数…

【QT】Ubuntu 编译安装 QT 5.12.7 源码

直接通过源码来编译安装 QT&#xff0c;难度太大&#xff0c;耗时较长&#xff0c;一般不是特别推荐使用源码安装。 目录 1、下载 QT 源码包 2、搭建安装环境(下载依赖库) 3、编译QT源码的脚本 1、下载 QT 源码包 QT5.12.7源码下载地址: download | QT 5.12.7 选择任意一…