设计模式之结构型模式

这些模式关注对象之间的组合和关联方式,以便形成更大的结构和功能。

  • 适配器模式(Adapter Pattern)
  • 桥接模式(Bridge)
  • 装饰器模式(Decorator)
  • 组合模式(Composite)
  • 外观模式(Facade)
  • 享元模式(Flyweight)
  • 代理模式(Proxy)

适配器模式(Adapter Pattern)

将一个类的接口转换成客户端所期望的另一个接口。
这种模式通常用于解决两个不兼容接口之间的兼容性问题。(充电线的转接头的功能)

一个生活中的实例是使用适配器模式连接不同类型的电源插头和电源插座。假设你有一个美国制造的电器,它使用美国标准的两脚插头(Type A),而你的墙上只有中国标准的三脚插座(Type I)。为了让电器能够在中国使用,你需要一个适配器来连接两者。
以下是一个简单的代码示例,展示了如何使用适配器模式来连接不同类型的插头和插座:

# 目标接口
class SocketAdapter:
    def __init__(self, plug):
        self.plug = plug

    def connect(self):
        pass

# 中国标准插座
class ChinaSocket:
    def connect_china_socket(self):
        print("连接中国标准插座")

# 美国标准插头
class USPlug:
    def connect_us_plug(self):
        print("连接美国标准插头")

# 适配器类
class ChinaToUSAdapter(SocketAdapter):
    def connect(self):
        self.plug.connect_china_socket()

# 客户端代码
if __name__ == "__main__":
    china_socket = ChinaSocket()
    adapter = ChinaToUSAdapter(china_socket)
    adapter.connect()

在上述代码中,我们定义了一个目标接口 SocketAdapter,它声明了一个 connect 方法。然后,我们有一个中国标准插座类 ChinaSocket,它具有一个 connect_china_socket 方法,用于连接中国标准插座。

接下来,我们有一个美国标准插头类 USPlug,它具有一个 connect_us_plug 方法,用于连接美国标准插头。

然后,我们创建了一个适配器类 ChinaToUSAdapter,它继承了目标接口 SocketAdapter。适配器类内部持有一个中国标准插座对象 china_socket,并实现了目标接口的 connect 方法,该方法通过适配器调用中国标准插座的连接方法。

最后,我们在客户端代码中创建了中国标准插座对象 china_socket,并将其传递给适配器类的构造函数创建适配器对象 adapter。然后,我们调用适配器对象的 connect 方法,它会通过适配器调用中国标准插座的连接方法。

通过适配器模式,我们可以使用适配器对象连接不同类型的插头和插座,使得它们能够兼容并正常工作。这样,我们就实现了不同类型的插头和插座之间的兼容性。

桥接模式(Bridge)

把抽象化与实现化解耦

# 桥接模式的实现

# 实现部分的接口
class DrawingAPI:
    def draw_circle(self, x, y, radius):
        pass

# 具体实现部分A
class DrawingAPIA(DrawingAPI):
    def draw_circle(self, x, y, radius):
        print(f"在坐标({x}, {y})处以半径{radius}绘制圆形(使用实现A)")

# 具体实现部分B
class DrawingAPIB(DrawingAPI):
    def draw_circle(self, x, y, radius):
        print(f"在坐标({x}, {y})处以半径{radius}绘制圆形(使用实现B)")

# 抽象部分
class Shape:
    def __init__(self, drawing_api):
        self.drawing_api = drawing_api

    def draw(self):
        pass

# 具体抽象部分1
class CircleShape(Shape):
    def __init__(self, x, y, radius, drawing_api):
        super().__init__(drawing_api)
        self.x = x
        self.y = y
        self.radius = radius

    def draw(self):
        self.drawing_api.draw_circle(self.x, self.y, self.radius)

# 具体抽象部分2
class SquareShape(Shape):
    def __init__(self, x, y, side_length, drawing_api):
        super().__init__(drawing_api)
        self.x = x
        self.y = y
        self.side_length = side_length

    def draw(self):
        self.drawing_api.draw_square(self.x, self.y, self.side_length)


# 客户端代码
if __name__ == "__main__":
    circle_a = CircleShape(1, 2, 3, DrawingAPIA())
    circle_a.draw()

    circle_b = CircleShape(4, 5, 6, DrawingAPIB())
    circle_b.draw()

在这个示例中,我们使用了桥接模式来将抽象部分(Shape)和实现部分(DrawingAPI)解耦。抽象部分定义了基本的形状(如圆形和正方形),而实现部分定义了具体的绘制方法。
通过将抽象部分和实现部分组合在一起,我们可以轻松地在运行时选择不同的实现,而无需更改抽象部分的代码。这样可以实现更灵活和可扩展的代码结构。
在上述示例中,我们创建了两个具体的实现部分:DrawingAPIADrawingAPIB,它们分别用于绘制圆形。然后,我们创建了两个具体的抽象部分:CircleShape,分别使用不同的实现部分进行绘制。

如果不使用桥接模式进行解耦,代码可能会变得更加紧密耦合,具体实现部分将直接嵌入到抽象部分中。以下是一个示例,展示了没有使用桥接模式的情况:

# 没有使用桥接模式的紧密耦合示例

# 圆形类
class CircleShape:
    def __init__(self, x, y, radius):
        self.x = x
        self.y = y
        self.radius = radius

    def draw(self):
        # 在这里直接实现绘制圆形的逻辑
        print(f"在坐标({self.x}, {self.y})处以半径{self.radius}绘制圆形")


# 正方形类
class SquareShape:
    def __init__(self, x, y, side_length):
        self.x = x
        self.y = y
        self.side_length = side_length

    def draw(self):
        # 在这里直接实现绘制正方形的逻辑
        print(f"在坐标({self.x}, {self.y})处以边长{self.side_length}绘制正方形")


# 客户端代码
if __name__ == "__main__":
    circle = CircleShape(1, 2, 3)
    circle.draw()

    square = SquareShape(4, 5, 6)
    square.draw()

在这个紧密耦合的示例中,绘制逻辑直接嵌入到了具体的抽象部分类中。这导致了以下问题:

  1. 如果需要修改绘制逻辑,例如更改绘制圆形的方式,就需要直接修改 CircleShape 类的代码,而不是通过更改实现部分来实现。
  2. 添加新的形状类,例如三角形,需要在每个具体形状类中重复实现绘制逻辑,造成代码重复。

因此,使用桥接模式可以将抽象部分和实现部分解耦,提高代码的灵活性和可扩展性。它允许我们在运行时选择不同的实现部分,而无需修改抽象部分的代码。

装饰器模式(Decorator)

在不修改原始对象的情况下动态地添加额外的功能。
装饰器模式通过将对象包装在一个具有相同接口的装饰器对象中来实现。

装饰器模式由以下几个关键角色组成:

  1. 抽象组件(Component):定义了被装饰对象和装饰器对象的公共接口。
  2. 具体组件(ConcreteComponent):实现了抽象组件接口,并定义了需要被装饰的对象。
  3. 抽象装饰器(Decorator):继承或实现了抽象组件接口,并持有一个对抽象组件的引用。它的主要目的是为了扩展或修改抽象组件的行为。
  4. 具体装饰器(ConcreteDecorator):实现了抽象装饰器接口,并扩展了抽象组件的行为。具体装饰器可以在调用被装饰对象的方法之前或之后添加额外的逻辑。
# 抽象组件
class Component:
    def operation(self):
        pass


# 具体组件
class ConcreteComponent(Component):
    def operation(self):
        print("执行具体组件操作")


# 抽象装饰器
class Decorator(Component):
    def __init__(self, component):
        self.component = component

    def operation(self):
        self.component.operation()


# 具体装饰器
class ConcreteDecorator(Decorator):
    def operation(self):
        super().operation()
        self.additional_operation()

    def additional_operation(self):
        print("执行额外的操作")


# 客户端代码
if __name__ == "__main__":
    # 创建具体组件
    component = ConcreteComponent()

    # 创建具体装饰器,并将具体组件作为参数传递
    decorator = ConcreteDecorator(component)

    # 执行操作
    decorator.operation()

在上述示例中,Component 是抽象组件,ConcreteComponent 是具体组件。Decorator 是抽象装饰器,ConcreteDecorator 是具体装饰器。客户端代码创建了一个具体组件对象,并将其作为参数传递给具体装饰器对象。装饰器对象在调用具体组件对象的方法之前或之后添加了额外的操作。

装饰器模式的优点是可以动态地添加功能,而不需要修改原始对象的代码。它提供了一种灵活的方式来扩展对象的行为,同时遵循开闭原则。然而,装饰器模式可能会导致类的数量增加,并且在多层装饰的情况下可能会变得复杂。因此,在使用时需要权衡利弊,并根据具体需求进行设计和实现。

组合模式(Composite)

将对象组合成树形结构以表示“部分-整体”的层次结构。
组合模式使得用户可以统一对待单个对象和组合对象,从而简化了代码的使用和维护。

组合模式由以下几个关键角色组成:

  1. 组件(Component):定义组合中对象的通用接口,可以是抽象类或接口。该接口声明了操作方法,例如添加、删除、获取子组件等。
  2. 叶子节点(Leaf):表示组合中的叶子对象,它没有子组件。实现组件接口的具体类。
  3. 组合节点(Composite):表示组合中的容器对象,它可以包含子组件。实现组件接口的具体类,并包含一个集合来存储子组件。
  4. 客户端(Client):通过组件接口操作组合对象。
# 组件接口
class Component:
    def add(self, component):
        pass

    def remove(self, component):
        pass

    def get_child(self, index):
        pass

    def operation(self):
        pass


# 叶子节点
class Leaf(Component):
    def operation(self):
        print("执行叶子节点操作")


# 组合节点
class Composite(Component):
    def __init__(self):
        self.children = []

    def add(self, component):
        self.children.append(component)

    def remove(self, component):
        self.children.remove(component)

    def get_child(self, index):
        return self.children[index]

    def operation(self):
        print("执行组合节点操作")
        for child in self.children:
            child.operation()


# 客户端代码
if __name__ == "__main__":
    # 创建叶子节点
    leaf1 = Leaf()
    leaf2 = Leaf()

    # 创建组合节点,并添加叶子节点
    composite1 = Composite()
    composite1.add(leaf1)
    composite1.add(leaf2)

    # 创建叶子节点
    leaf3 = Leaf()

    # 创建组合节点,并添加叶子节点和组合节点
    composite2 = Composite()
    composite2.add(leaf3)
    composite2.add(composite1)

    # 执行操作
    composite2.operation()

在上述示例中,Component 是组件接口,Leaf 是叶子节点类,Composite 是组合节点类。客户端代码创建了一棵树形结构,包含了叶子节点和组合节点,并通过调用 operation() 方法执行操作。组合节点会递归调用其子组件的操作方法,从而实现整个树形结构的操作。

组合模式的优点是简化了代码的使用和维护,使得客户端可以统一对待单个对象和组合对象。它也提供了灵活性和可扩展性,因为可以轻松地添加新的叶子节点或组合节点。然而,组合模式可能会增加系统的复杂性,并且在某些情况下可能会导致性能问题,因此在使用时需要权衡利弊。

外观模式(Facade)

提供了一个统一的接口,用于访问子系统中的一组接口。
外观模式隐藏了子系统的复杂性,为客户端提供了一个简单的接口来与子系统进行交互。

下面是一个外观模式的示例代码:

# 子系统类A
class SubsystemA:
    def operation_a(self):
        print("执行子系统A的操作")

# 子系统类B
class SubsystemB:
    def operation_b(self):
        print("执行子系统B的操作")

# 子系统类C
class SubsystemC:
    def operation_c(self):
        print("执行子系统C的操作")

# 外观类
class Facade:
    def __init__(self):
        self.subsystem_a = SubsystemA()
        self.subsystem_b = SubsystemB()
        self.subsystem_c = SubsystemC()

    def operation(self):
        self.subsystem_a.operation_a()
        self.subsystem_b.operation_b()
        self.subsystem_c.operation_c()

# 客户端代码
facade = Facade()
facade.operation()

在上述代码中,有三个子系统类 SubsystemASubsystemBSubsystemC,它们分别提供了各自的操作。外观类 Facade 将这些子系统进行了封装,并提供了一个统一的接口 operation()

在客户端代码中,我们创建了一个外观对象 facade,然后通过调用 facade.operation() 来访问子系统的操作。在内部,外观对象会依次调用子系统类的相应方法,隐藏了子系统的复杂性。

当我们执行 facade.operation() 时,会依次输出:

执行子系统A的操作
执行子系统B的操作
执行子系统C的操作

这样,客户端就可以通过简单的接口来访问子系统的功能,而不需要了解和处理子系统的复杂逻辑。外观模式帮助简化了系统的使用和维护。

享元模式(Flyweight)

通过共享对象来减少内存使用和提高性能。
享元模式适用于存在大量相似对象的场景,通过共享这些对象的内部状态,来减少对象的数量。

下面是一个在模具设计中使用享元模式的示例代码:

# 模具接口
class Mold:
    def __init__(self, mold_type):
        self.mold_type = mold_type

    def produce(self, product):
        print(f"生产 {product} 使用 {self.mold_type} 模具")

# 享元工厂类
class MoldFactory:
    def __init__(self):
        self.molds = {}

    def get_mold(self, mold_type):
        if mold_type not in self.molds:
            self.molds[mold_type] = Mold(mold_type)
        return self.molds[mold_type]

# 客户端代码
mold_factory = MoldFactory()

# 第一批产品使用 A 类型模具
mold_a = mold_factory.get_mold("A")
mold_a.produce("产品1")
mold_a.produce("产品2")

# 第二批产品使用 B 类型模具
mold_b = mold_factory.get_mold("B")
mold_b.produce("产品3")
mold_b.produce("产品4")

# 第三批产品继续使用 A 类型模具
mold_a.produce("产品5")
mold_a.produce("产品6")

在上述代码中,Mold 类表示模具,每个模具有一个类型(mold_type)属性,并且可以生产产品。MoldFactory 类是享元工厂类,用于创建和管理模具对象。

在客户端代码中,我们通过 MoldFactory 获取模具对象。如果请求的模具对象已经存在于享元工厂中,则直接返回该对象;否则,创建一个新的模具对象并添加到享元工厂中。这样,相同类型的模具对象会被共享使用。

在示例中,我们先获取了一个类型为 “A” 的模具对象,并使用它生产了两个产品。然后,我们获取了一个类型为 “B” 的模具对象,并使用它生产了两个产品。最后,我们又使用之前获取的 “A” 类型模具对象生产了两个产品。

运行上述代码,会输出以下结果:

生产 产品1 使用 A 模具
生产 产品2 使用 A 模具
生产 产品3 使用 B 模具
生产 产品4 使用 B 模具
生产 产品5 使用 A 模具
生产 产品6 使用 A 模具

可以看到,尽管我们只创建了两个不同类型的模具对象,但通过共享对象的方式,成功地减少了模具对象的数量。享元模式帮助我们节省了内存开销,并提高了系统的性能。

代理模式(Proxy)

它提供了一个代理对象,控制对另一个对象的访问。
通过使用代理对象,可以在访问对象时添加额外的逻辑,例如权限控制、缓存、延迟加载等。

下面是一个代理模式的示例代码:

# 主题接口
class Subject:
    def request(self):
        pass

# 真实主题类
class RealSubject(Subject):
    def request(self):
        print("处理真实请求")

# 代理类
class Proxy(Subject):
    def __init__(self, real_subject):
        self.real_subject = real_subject

    def request(self):
        # 在访问真实主题前添加额外的逻辑
        self.pre_request()

        # 调用真实主题的请求方法
        self.real_subject.request()

        # 在访问真实主题后添加额外的逻辑
        self.post_request()

    def pre_request(self):
        print("代理对象处理请求前的操作")

    def post_request(self):
        print("代理对象处理请求后的操作")

# 客户端代码
real_subject = RealSubject()
proxy = Proxy(real_subject)

# 通过代理对象访问真实主题
proxy.request()

在上述代码中,有一个主题接口 Subject,其中定义了一个 request() 方法。RealSubject 类是真实主题类,实现了主题接口,并提供了具体的请求处理逻辑。

Proxy 类是代理类,也实现了主题接口,并持有一个真实主题对象。在代理类的 request() 方法中,可以在调用真实主题对象的请求方法前后添加额外的逻辑。在示例中,我们在访问真实主题前后分别输出了一些操作。

在客户端代码中,我们创建了一个真实主题对象 real_subject,然后将其传递给代理对象 proxy 的构造函数。通过代理对象 proxy,我们可以访问真实主题的请求方法。

当执行 proxy.request() 时,会依次输出:

代理对象处理请求前的操作
处理真实请求
代理对象处理请求后的操作

可以看到,代理对象在访问真实主题前后添加了额外的操作。代理模式帮助我们控制对真实主题的访问,并可以在访问前后进行一些附加处理。这样,我们可以在不修改真实主题代码的情况下,对其进行扩展和增强。

你可能感兴趣的:(设计模式,设计模式)