面向对象设计模式(Python版)-- 结构模式

面向对象设计模式(Python版)-- 结构模式

5.Adapter 适配器

适配器模式定义: 将两个不兼容的类整合在一起使用,属于结构型模式,需要有Adaptee (被适配者)和Adaptor (适配器)两个身份。 我们经常碰到要将两个没有关系的类组合在一起使用,解决方案是修改各自类的接口,但是如果我们没有源代码,或者,我们不愿意为了一个应用而修改各自的接口。那么就创建一个Adapter类,在这两种接口之间创建一个统一的接口。本质上,根据被适配者创建一个适配器对象,该适配器对象完成了被适配者的接口映射到统一接口的工作。实例代码如下。

class Adapter:
    """
    Adapts an object by replacing methods.

    """
    def __init__(self, obj, adapted_methods):
        """We set the adapted methods in the object's dict"""
        self.obj = obj
        self.__dict__.update(adapted_methods)
    
    def __getattr__(self, attr):
        """All non-adapted calls are passed to the object"""
        return getattr(self.obj, attr)

class Dog:
    def __init__(self):
        self.name = "Dog"
    def wang(self):
        return "wangwang!"

class Cat:
    def __init__(self):
        self.name = "Cat"
    def miao(self):
        return "miaomiao!"

if __name__ == "__main__":
    dog = Dog()
    dog = Adapter(dog, dict(make_noise=dog.wang))
    cat = Cat()
    cat = Adapter(cat, dict(make_noise=cat.miao))
    print(f"{dog.name} makes {dog.make_noise()}")
    print(f"{cat.name} makes {cat.make_noise()}")

从上面所示代码可以看出,适配器初始化的过程完成了不同对象的接口映射到统一接口。需要注意的是__getattr__方法的实现不可缺少,否则无法获得对象未做映射的属性和方法。

适配器模式可以认为是对现在业务的一种补偿应用,并不需要在设计阶段使用。其优点在于,完成了接口的统一,减少了开发中的风险,提高了类的复用性。并且适配器可以灵活适配。


6.Proxy 代理

代理模式是指为其他对象提供一种代理,以控制对这个对象的访问。Proxy模式的使用场景在于

  • 授权机制 不同级别的用户对同一对象拥有不同的访问权利。如Jive论坛系统中,就使用Proxy进行授权机制控制,访问论坛有两种人:注册用户和游客(未注册用户),Jive中就通过类似ForumProxy这样的代理来控制这两种用户对论坛的访问权限。

  • 某个客户端不能直接操作到某个对象,但又必须和该对象有所互动。 举例两个具体情况。

    • 如果对象是一个是很大的图片,需要花费很长时间才能显示出来,那么当这个图片包含在文档中时,使用编辑器或浏览器打开这个文档,打开文档必须很迅速,不能等待大图片处理完成,这时需要做个图片Proxy来代替真正的图片。

    • 如果对象在Internet的某个远端服务器上,无法直接操作这个对象,因为网络速度原因可能比较慢,那可以先用Proxy来代替该对象。

总之,对于开销很大的对象。只有在使用它时才创建,这个原则可以为我们节省很多宝贵的内存。所以,程序过于耗费资源内存和程序编制思路也有一定的关系。

针对授权机制的场景的实例代码如下。

from abc import ABC, abstractmethod
class Server(ABC):
    @abstractmethod
    def recv(self,info):
        pass
    @abstractmethod
    def send(self,info):
        pass
    @abstractmethod
    def show(self,info):
        pass

class InfoServer(Server):
    def recv(self,info):
        self.content = info
        return "Recv OK!"
    
    def send(self,info):
        return "Send OK!"

    def show(self):
        print(f"Show: {self.content}")

# 创建InfoServer的代理类。这里需要注意,代理模式在使用过程中,应尽量对抽象类进行代理,而尽量不要直接对加过修饰和方法的子类进行代理。
# 代理类和被代理类之间要尽可能减少耦合,服从单一职责原则。
class ProxyServer:
    def __init__(self,server):
        self.server = server

# InfoServer的代理类,用于授权
class WhiteInfoServerProxy(ProxyServer):
    def __init__(self,server):
        super().__init__(server)
        self.white_list = []

    def recv(self,info):
        addr = info.get("address",0)
        if not addr in self.white_list:
            return "Address not in white list"
        else:
            return self.server.recv(info)
    def send(self,info):
        addr = info.get("address",0)
        if not addr in self.white_list:
            return "Address not in white list"
        else:
            return self.server.send(info)
    def show(self):
        addr = info.get("address",0)
        if not addr in self.white_list:
            return "Address not in white list"
        else:
            self.server.show()

    def addWhite(self,addr):
        self.white_list.append(addr)
    def rmWhite(self,addr):
        self.white_list.remove(addr)    
    def clearWhite(self):
        self.white_list.clear()
        
if __name__ == '__main__':
    # 被代理类对象
    server= InfoServer()
    # 代理类对象
    proxy_server = WhiteInfoServerProxy(server)
    proxy_server.addWhite("1")
    re1 = proxy_server.recv({"address":"1","content":"Hello"})
    proxy_server.rmWhite("1")
    re2 = proxy_server.send({"address":"1","content":"Hello"})
    print(re1)
    print(re2)
    del proxy_server
    server.show()

从上面的示例代码可以看出。首先,代理类不应继承被代理类,而是维护一个被代理类对象,可以提供相同的接口。本质上,代理类相当于被代理类的一个外壳,只是这个外壳实现了额外的功能,但不会影响被代理对象的方法。这也符合单一职责原则。其次,当代理类对象被删除时,被代理类对象并没有消亡,因此,可以不断生成和删除代理类对象提供给外部接口使用。

第二个场景,针对被加载对象的负载过大,而需要对其进行保护或允许其他部分运行的情况。示例代码如下。

import time
class SalesManager:
    def __init__(self):
        self.busy = 'No'

    def work(self):
        self.busy = 'Yes'
        print("Sales Manager working...")
        time.sleep(2)
        # self.busy = 'No'

    def talk(self):
        if self.busy == 'No':
            print("Sales Manager ready to talk")

class Proxy:
    def __init__(self,sale):
        self.sale = sale

    def work(self):
        print("Proxy checking for Sales Manager availability")
        if self.sale.busy == 'No':
            self.sale.talk()
            self.sale.work()
        else:
            print("Sales Manager is busy")
            
if __name__ == '__main__':
    sale = SalesManager()
    proxy_sale = Proxy(sale)
    proxy_sale.work()
    proxy_sale.work()

7.Decorator 装饰器模式

装饰器模式是指,动态给一个对象添加一些额外的职责。使用Decorator模式相比用生成子类方式达到功能的扩充显得更为灵活。通常可以使用继承来实现功能的拓展,如果这些需要拓展的功能的种类很繁多,那么势必生成很多子类,增加系统的复杂性。同时,使用继承实现功能拓展,要求我们必须可预见这些拓展功能,这些功能是编译时就确定的,静态的。使用Decorator模式使得这些功能需要由用户动态决定加入的方式和时机。Decorator提供了"即插即用"的方法,在运行期间决定何时增加何种功能。Python本身就支持装饰器。

这里介绍一个概念,AOP编程模式即Aspect Oriented Programming,面向切面的编程。如果几个或者更多逻辑过程中有重复操作行为,就可以将这些行为提取出来(视为切面),进行统一管理和维护。下面示例代码中,每个函数都有需要测量运行的时间,因此,可以将测量时间功能设计为装饰器。

import time
def timeSpend(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print('time spend: %.2f' % (end - start))
        return result
    return wrapper

@timeSpend
def test(num=1):
    print('test')
    for i in range(num):
        time.sleep(2)

if __name__ == '__main__':
    test(2)

在Python中装饰器本质上是个函数,也可以自带参数,或是设计装饰类。具体的使用方法可以查阅Python文档装饰器部分,这里不做赘述。

相比于继承方法,装饰器更加灵活,可以根据需要动态添加。


8.Facade 外观模式

外观模式也叫门面模式,旨在为一个复杂子系统提供统一的接口,使得子系统更易于使用。外观模式很常用,大家可能不熟悉这个名字,但是很早就已经开始使用了。示例代码如下。

class AlarmSensor:
    def run(self):
        print("AlarmSensor run")
    def close(self):
        print("AlarmSensor close")

class FireSensor:
    def run(self):
        print("FireSensor run")
    def close(self):
        print("FireSensor close")

class Sprinker:
    def run(self):
        print("Sprinker run")
    def close(self):
        print("Sprinker close")

# Facade
class EmergencyFacade:
    def __init__(self):
        self.alarm_sensor = AlarmSensor()
        self.fire_sensor = FireSensor()
        self.sprinker = Sprinker()
        
    def run(self):
        self.fire_sensor.run()
        self.alarm_sensor.run()
        self.sprinker.run()

    def close(self):
        self.sprinker.close()
        self.alarm_sensor.close()
        self.fire_sensor.close()


# Client
if __name__ == '__main__':
    fire_emergency = EmergencyFacade()
    fire_emergency.run()
    print("...."*10)
    fire_emergency.close()

上述示例代码中,构建了一个火灾报警响应系统,包含火焰检测、报警和喷水等一系列操作。外观模式将子系统的复杂关系封装成了统一的接口。通常子系统各自的接口是不会被单独使用的。这样做的好处可以减少系统之间的相互依赖。同时隐藏了很多内部接口的细节,提高了系统的安全性。


9.Composite 组合模式

组合模式将对象组合成树状结构,使得客户端可以统一地处理单个对象或组合对象。无论是单个对象或是组合对象,它们的使用接口具有一致性。下面以文件系统为例,文件夹中可以包含文本文件或图像文件,也可以包含子文件夹,子文件夹同样可以继续扩展,它们都具有统一的接口,比如add()show()。示例代码如下。

from abc import ABC, abstractmethod
# 虚类文件类,是所有文件或文件夹的父类,提供统一接口
class File(ABC):
    @abstractmethod
    def add(self,file):
        pass
    @abstractmethod
    def show(self,depth=0):
        pass
    
class ImageFile(File):
    def __init__(self,file_name):
        self.file_name = file_name
    def add(self,file):
        print("ImageFile can't add file")
    def show(self,depth=0):
        temp_str = "-"*depth + self.file_name
        print(temp_str)
        
class TextFile(File):
    def __init__(self,file_name):
        self.file_name = file_name
    def add(self,file):
        print("TextFile can't add file")
    def show(self,depth=0):
        temp_str = "-"*depth + self.file_name
        print(temp_str)

# 定义Composite类
class Folder(File):
    def __init__(self,file_name):
        self.file_name = file_name
        self.subfiles = []
    def add(self,file):
        self.subfiles.append(file)
    def show(self,depth=0):
        temp_str = "-"*depth + self.file_name
        print(temp_str)
        for subfile in self.subfiles:
            subfile.show(depth+2)

if __name__ == "__main__":
    folder1 = Folder("folder1")
    folder1.add(ImageFile("image1.jpg"))
    folder1.add(ImageFile("image2.jpg"))
    folder2 = Folder("folder2")
    folder1.add(folder2)
    folder2.add(TextFile("text1.txt"))
    folder2.add(TextFile("text2.txt"))
    folder1.show()
    print('------'*10)
    folder2.show()

在上述示例代码中,不同对象都具有统一的接口(采用虚基类约束),这样用户使用起来更加方便。类似的文件系统的场景比如评论系统,也都可以采用组合模式进行管理。


10.Bridge 桥接模式

桥接模式是一种结构型设计模式,用于将抽象部分与其实现部分分离,以便两者可以独立变化。我们以绘制图形为例,主要涉及形状和颜色的组合,例如蓝色方块和红色圆圈。一般方法,我们需要为每一种组合构建一个类,但是这样不利于代码的扩展,此时就可以采用桥接模式。示例代码如下。

# 颜色类
class Color:
    def __init__(self,color):
        self.color = color
    def fill(self):
        pass

class Red(Color):
    def fill(self):
        print(f"fill {self.color}")

class Green(Color):
    def fill(self):
        print(f"fill {self.color}")

# 图形类    
class Shape:
    def __init__(self,color):
        self.color = color
    def draw(self):
        pass

class Rectangle(Shape):
    def draw(self):
        print("draw rectangle", end = ' ')
        self.color.fill()

class Circle(Shape):
    def draw(self):
        print("draw circle", end = ' ')
        self.color.fill()


if __name__ == '__main__':
    red_rectangle = Rectangle(Red("red"))
    green_circle = Circle(Green("green"))
    red_rectangle.draw()
    green_circle.draw()

桥接模式的使用场景,业务需要将抽象部分和实现部分解耦,使得它们可以独立变化时,可以使用桥接模式。桥接模式可以更灵活的扩展系统,而不需要修改现有代码。从上述示例代码可以看到,图形类对象的实现利用了颜色类对象来改变其样式,但图形类和颜色类是可以自由扩展和组合的。值得一提的是,许多的设计模式都是利用在一个类中保存另一个类的对象来扩展其功能。这个技巧是很值得学习的。


11.Flyweight 享元模式

享元模式通过共享对象来减少内存消耗。它使用一个工厂来管理这些共享对象,并确保每个对象只被创建一次。这和单例模式有些类似,但是不同之处在于,单例模式没有办法维护多个对象的外部状态信息,例如网购平台中订购商品,同类商品往往会被订购多次,如果每次都创建一个商品对象(内部状态信息),那么资源显然会大大浪费,但是单例模式无法应对此问题,因为每个订单商品的数量和寄送地址(外部状态信息)都需要单独保存。享元模式中需要区分好外部状态和内部状态。示例代码如下。

#商品类
class Goods:
    def __init__(self, uid, scale):
        self.uid = uid
        self.scale = scale
    
    def show(self):
        print("uid:%s, scale:%s" % (self.uid, self.scale))
# 构建享元工厂类维护内部状态
class goodsFactory:
    def __init__(self):
        self.goods_pool = {}

    def getGoods(self, uid, scale):
        if (uid,scale) not in self.goods_pool.keys():
            self.goods_pool[(uid,scale)] = Goods(uid, scale)
        return self.goods_pool[(uid,scale)]

# 用户类维护外部信息,通过工厂类获取共享对象
class Customer:
    order = []
    def __init__(self, name, goods_factory):
        self.name = name
        self.goods_factory = goods_factory

    def buyGoods(self, uid, scale):
        goods = self.goods_factory.getGoods(uid, scale)
        Customer.order.append((self.name, goods))

    @classmethod
    def showOrder(cls):
        for name, goods in cls.order:
            print("%s buy goods(id:%s)" % (name, id(goods)))

if __name__ == "__main__":
    goods_factory = goodsFactory()
    customer1 = Customer("customer1", goods_factory)
    customer2 = Customer("customer2", goods_factory)
    customer1.buyGoods("001", "A")
    customer1.buyGoods("002", "B")
    customer2.buyGoods("001", "A")
    customer2.showOrder()

上面所述代码,利用Hash表(Python中是字典)完成内部状态和共享对象的映射,外部信息用order表进行存储和维护。使用共享对象支持大量细粒度对象。享元模式中只包含内部状态信息,不应包含外部状态信息。

posted @ 2025-03-12 12:37  不秃头的程序员不秃头  阅读(21)  评论(0)    收藏  举报