结构型模式-python版
在21种设计模式中, 结构型设计模式有7种, 分别是:
- 适配器模式
- 代理模式
- 桥接模式
- 享元模式
- 外观模式
- 组合模式
- 装饰器模式
下面逐一简要介绍:
1 适配器模式
适配器(Adapter)设计模式是一种结构型设计模式,它允许接口不兼容的类之间进行合作。适配器模式充当两个不兼容接口之间的桥梁,使得它们可以一起工作,而无需修改它们的源代码。
主要角色:
目标接口(Target): 定义客户端使用的接口,客户端通过该接口与适配器进行交互。
适配器(Adapter): 实现目标接口,并且持有一个被适配者的实例,将客户端的请求转换为被适配者能够处理的形式。
被适配者(Adaptee): 拥有一组不兼容目标接口的方法,适配器通过包装被适配者,使其能够与目标接口协同工作。
客户端(Client): 通过目标接口与适配器进行交互,无需直接与被适配者打交道。
1.1 代码举例
from abc import ABC, abstractmethod
# 目标接口
class EnglishSpeaker(ABC):
@abstractmethod
def speak_english(self):
pass
# 被适配者
class FrenchSpeaker:
def parler_francais(self):
return "Je parle français"
# 适配器
class FrenchToEnglishAdapter(EnglishSpeaker):
def __init__(self, french_speaker):
self.french_speaker = french_speaker
def speak_english(self):
french_phrase = self.french_speaker.parler_francais()
# 这里可以进行一些转换操作,这里简单地将法语短语翻译成英语
english_translation = "I speak English: " + french_phrase
return english_translation
# 客户端
def communicate_in_english(english_speaker):
print(english_speaker.speak_english())
# 创建被适配者
french_speaker = FrenchSpeaker()
# 创建适配器
adapter = FrenchToEnglishAdapter(french_speaker)
# 客户端调用
communicate_in_english(adapter)
1.2 适配器模式的优缺点
优点
(1)提高了类的复用性
通过适配器模式,原本不兼容的类可以被一起使用,避免了重写代码的需求,从而提高了代码的复用性。
(2)灵活性和扩展性
可以通过编写新的适配器,轻松地将现有类与新接口或新系统集成在一起,增加系统的灵活性和扩展性。
(3)遵循开闭原则
适配器模式允许扩展系统而不改变现有的代码结构,符合面向对象设计的开闭原则(对扩展开放,对修改关闭)。
(4)解耦
适配器模式将客户端与被适配的类解耦,使客户端不需要关心被适配类的接口细节,只需要使用适配器提供的接口。
缺点
(1)增加了系统的复杂性
如果过度使用适配器模式,可能会导致系统中出现大量的适配器类,增加了代码的复杂性和维护难度。
(2)性能开销
由于适配器模式涉及对象的间接调用,可能会导致一定的性能开销,特别是在高性能要求的系统中。
(3)可能会掩盖真实的系统设计缺陷
适配器模式虽然解决了接口不兼容的问题,但它可能会掩盖一些设计上的问题。如果系统设计时能够提前规划好接口,这样的适配可能本不需要。
(4)可能影响代码的可读性
引入适配器后,系统的结构变得更加复杂,可能会让代码的可读性下降,特别是对于不熟悉这种模式的开发人员。
2 代理模式
Python代理模式(Proxy Pattern)是一种结构型设计模式。在代理模式中,代理对象充当了另一个对象的占位符,以控制对该对象的访问。
代理对象和被代理对象实现了相同的接口,因此它们可以互相替代。客户端和代理对象之间的交互是无缝的,因为它们的接口是一样的。
代理模式的主要功能是为其他对象提供一个代理,以控制对对象的访问。代理对象可以在调用被代理对象之前或之后执行一些操作,例如身份验证,缓存等。
主要角色:
Subject类:通过接口或抽象类声明真实角色实现的业务方法。
Proxy类:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作
RealSubject:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用
2.1 代码举例
from abc import abstractmethod, ABCMeta
class Subject(metaclass=ABCMeta):
@abstractmethod
def Request(self):
pass
class RealSubject(Subject):
def Request(self):
print("Receive a request")
class Proxy(Subject):
def __init__(self):
self.subject = None
def Request(self):
self.subject = RealSubject()
self.subject.Request()
class Client(object):
def main(self):
p = Proxy()
p.Request()
if __name__ == '__main__':
Client().main()
2.2 代理模式的优缺点
优点
(1)保护了真实对象的访问,可以对访问进行限制和控制;
(2)可以提高访问效率,通过代理对象可以缓存数据或者调用其他服务等;
(3)可以提高系统的灵活性,因为代理对象可以在不影响真实对象的情况下扩展其功能。
缺点
(1)可能引入额外的复杂性,因为需要创建代理对象。
(2)如果代理对象没有正确实现与真实对象相同的接口,可能会导致客户端代码无法正常工作。
3 桥接模式
桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。
通俗点讲就是在不同的地方之间搭一座桥,让他们连接起来,可以相互通讯和使用。
在模式中,就是为被分离了的抽象部分和实现部分来搭桥。
桥接模式中的桥接是单向的,也就是只能是抽象部分的对象去使用实现部分的对象,而不能反过来,也就是个单向桥。
主要角色:
抽象化(Abstraction)角色:抽象化给出的定义,并保存一个对实现化对象的引用。
修正抽象化(Refined Abstraction)角色:扩展抽象化角色,改变和修正父类对抽象化的定义。
实现化(Implementor)角色:这个角色给出实现化角色的接口,但不给出具体的实现。必须指出的是,这个接口不一定和抽象化角色的接口定义相同,实际上,这两个接口可以非常不一样。实现化角色应当只给出底层操作,而抽象化角色应当只给出基于底层操作的更高一层的操作。
具体实现化(Concrete Implementor)角色:这个角色给出实现化角色接口的具体实现
什么场景下会用到桥接模式? https://www.cnblogs.com/baxianhua/p/11358707.html 中给出了一个形象化的例子, 设想如果要绘制矩形、圆形、椭圆、正方形,我们至少需要4个形状类,但是如果绘制的图形需要具有不同的颜色,如红色、绿色、蓝色等
是对于每个图形都提供一个颜色的版本, 还是将图形和颜色组合起来使用, 显然后一种更为合适, 对于有两个变化维度(即两个变化的原因)的系统,采用方案二来进行设计系统中类的个数更少,且系统扩展更为方便。设计方案二即是桥接模式的应用。
3.1 代码举例
from abc import ABC, abstractmethod
# 接口实现类
class Implementor(ABC):
@abstractmethod
def Operation(self):
raise NotImplementedError
class ConcreteImplementorA(Implementor):
def Operation(self):
print("实现 A的方法")
class ConcreteImplementorB(Implementor):
def Operation(self):
print("实现 B的方法")
# 抽象类
class Abstraction(ABC):
def __init__(self, implementor):
self.implementor = implementor
@abstractmethod
def Operation(self):
raise NotImplementedError
class RefineAbstraction(Abstraction):
def Operation(self):
self.implementor.Operation()
if __name__ == "__main__":
a = ConcreteImplementorA()
b = ConcreteImplementorB()
aa = RefineAbstraction(a)
ab = RefineAbstraction(b)
aa.Operation()
ab.Operation()
3.2 桥接模式的优缺点
优点
(1)分离抽象接口及其实现部分。
(2)桥接模式有时类似于多继承方案,但是多继承方案违背了类的单一职责原则(即一个类只有一个变化的原因),复用性比较差,而且多继承结构中类的个数非常庞大,桥接模式是比多继承方案更好的解决方法。
(3)桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。
(4)实现细节对客户透明,可以对用户隐藏实现细节。
缺点
(1)桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
(2)桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。
4 享元模式
享元模式(Flyweight Pattern)是一种结构型设计模式,用于减少创建对象的数量,以提高应用程序的性能。享元模式通过共享尽可能多的数据来减少内存使用。
主要角色:
抽象享元角色(FlyWeight):享元对象抽象基类或者接口,同时定义出对象的外部状态和内部状态的接口或实现;
具体享元角色(ConcreteFlyWeight):实现抽象享元类中的方法,是需要共享的对象类
享元工厂(FlyWeightFactory):维护一个享元对象的池,内部使用一个 Map 存储已经创建的享元对象
4.1 代码举例
from abc import abstractmethod, ABCMeta
# 抽象享元类
class Flyweight(metaclass=ABCMeta):
@abstractmethod
def operation(self):
pass
# 具体享元类,实现了Flyweight
class ConcreteFlyweight(Flyweight):
def __init__(self, name):
self.name = name
def operation(self):
print("Name: %s" % self.name)
# 享元创建工厂类
class FlyweightFactory():
_dict = {}
def getFlyweight(self, name):
if name not in self._dict:
self._dict[name] = ConcreteFlyweight(name)
return self._dict[name]
def getFlyweightCount(self):
return len(self._dict)
class Client(object):
def main(self):
factory = FlyweightFactory()
c1_capp = factory.getFlyweight("cappuccino")
c1_capp.operation()
c2_mocha = factory.getFlyweight("mocha")
c2_mocha.operation()
c3_capp = factory.getFlyweight("cappuccino")
c3_capp.operation()
print("Num of Flyweight Instance: %s" % factory.getFlyweightCount())
if __name__ == "__main__":
Client().main()
4.2 享元模式的优缺点
优点:
(1)减少内存使用。
(2)提高程序性能。
(3)对象共享可以降低程序的复杂度。
缺点:
(1)可能会导致代码复杂性增加。
(2)缓存的数据可能过多,导致需要更多的内存。
应用场景:
(1)有大量的相似对象需要创建。
(2)对象的大部分属性可以共享,相对稳定。
(3)需要缓存数据的应用程序。
5 外观模式
外观模式(Facade Pattern):外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。外观模式又称为门面模式,它是一种对象结构型模式。外观模式的核心在于将复杂的内部实现包装起来,只向外界提供简单的调用接口。类似现实世界中的电脑,开机按钮可以说就是一个简单的调用接口,帮用户屏蔽了复杂的内部电路。
5.1 代码举例
class AirConditioner:
def on(self):
print("Air Conditioner is on.")
def off(self):
print("Air Conditioner is off.")
class WaterHeater:
def on(self):
print("Water Heater is on.")
def off(self):
print("Water Heater is off.")
class Curtains:
def open(self):
print("Curtains are open.")
def close(self):
print("Curtains are closed.")
class Humidifier:
def on(self):
print("Humidifier is on.")
def off(self):
print("Humidifier is off.")
class SmartHomeFacade:
def __init__(self, air_conditioner, water_heater, curtains, humidifier):
self.air_conditioner = air_conditioner
self.water_heater = water_heater
self.curtains = curtains
self.humidifier = humidifier
def arrive_home(self):
print("Arriving home...")
self.air_conditioner.on()
self.water_heater.on()
self.curtains.open()
self.humidifier.on()
def leave_home(self):
print("Leaving home...")
self.air_conditioner.off()
self.water_heater.off()
self.curtains.close()
self.humidifier.off()
if __name__ == "__main__":
air_conditioner = AirConditioner()
water_heater = WaterHeater()
curtains = Curtains()
humidifier = Humidifier()
smart_home = SmartHomeFacade(air_conditioner, water_heater, curtains, humidifier)
smart_home.arrive_home()
smart_home.leave_home()
5.2 外观模式的优缺点
优点:
(1)实现了子系统与客户端之间的松耦合关系。
(2)客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目,并使得子系统使用起来更加容易。
(3)这其实也是Python一直提倡的封装思想,隐藏一些丑陋的系统,提供API去调用,不用管内部如何实现,只需调用API即可实现相关功能。
缺点:
(1)不能很好地限制客户使用子系统类
(2)在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
6 组合模式
组合模式(Composite Pattern)是一种结构型设计模式,它允许你将对象组合成树形结构来表示“部分-整体”的层次结构。组合模式使得客户端可以统一地处理单个对象和对象组合。组合模式的适用场景表示部分-整体层次结构:当需要表示对象的部分-整体层次结构时,可以使用组合模式。统一处理单个对象和组合对象:当需要统一处理单个对象和组合对象时,可以使用组合模式。构建递归结构:当需要构建递归结构(如树形结构)时,可以使用组合模式。
主要角色:
组件(Component):定义对象的接口,并实现一些默认行为。声明一个接口,用于访问和管理Leaf和Composite中的子组件。
叶子(Leaf):代表树的叶子节点,叶子节点没有子节点。
组合(Composite):定义有子部件的那些部件的行为,存储子部件。并在组件接口中实现与子部件有关的操作,如添加、删除等
6.1 代码举例
from abc import ABC, abstractmethod
class Graphic(ABC):
@abstractmethod
def draw(self):
pass
def add(self, graphic):
raise NotImplementedError("This method is not supported")
def remove(self, graphic):
raise NotImplementedError("This method is not supported")
def get_child(self, index):
raise NotImplementedError("This method is not supported")
class Circle(Graphic):
def draw(self):
print("Drawing a circle")
class Square(Graphic):
def draw(self):
print("Drawing a square")
class CompositeGraphic(Graphic):
def __init__(self):
self.children = []
def draw(self):
for child in self.children:
child.draw()
def add(self, graphic):
self.children.append(graphic)
def remove(self, graphic):
self.children.remove(graphic)
def get_child(self, index):
return self.children[index]
def main():
# 创建叶子节点
circle1 = Circle()
circle2 = Circle()
square1 = Square()
# 创建组合节点
composite1 = CompositeGraphic()
composite2 = CompositeGraphic()
# 组合图形
composite1.add(circle1)
composite1.add(circle2)
composite2.add(square1)
composite2.add(composite1)
# 绘制组合图形
composite2.draw()
if __name__ == "__main__":
main()
6.2 组合模式的优缺点
优点
(1)统一处理单个对象和组合对象:组合模式使得客户端可以统一地处理单个对象和对象组合,提高了代码的灵活性和可扩展性。
(2)简化客户端代码:客户端代码可以一致地使用组件接口,而不需要关心处理的是单个对象还是组合对象。
(3)符合开闭原则:可以通过增加新的叶子和组合类来扩展系统,而不需要修改现有代码。
缺点
(1)增加复杂性:组合模式会增加系统中类和对象的数量,可能会使系统变得复杂。
(2)难以限制组合层次:有时需要对组合层次进行限制,但组合模式本身没有提供这样的机制。
7 装饰器模式
装饰器模式(Decorator Pattern)是一种结构型设计模式,它允许将行为动态添加到一个对象中,而不必改变该对象的类。这种模式利用了组合的方式,以在运行时动态地添加功能,同时避免了静态类继承的缺点。
装饰器模式的基本思想是:将一个对象“包装”在另一个对象中,从而实现增强原有对象的功能,而不改变原有对象的结构。这种方式可以让我们通过添加新的装饰器来动态地改变对象的行为,而无需对其进行修改。
装饰器模式的实现通常涉及创建一个抽象的装饰器类和一个具体的装饰器类,这个具体的装饰器类可以添加额外的行为或修改对象的行为。同时,还需要创建一个具体的组件类,它是被装饰的对象。最终,装饰器模式将组件类和装饰器类组合起来,以实现动态添加功能的目的。
装饰器模式常常应用于需要动态添加功能或修改对象行为的场景,例如在不改变现有代码的情况下为一个类添加新的功能,或在运行时添加日志、缓存等功能。
7.1 代码举例
def my_decorator(func):
def wrapper():
print("Something is happening before the function is called.")
func()
print("Something is happening after the function is called.")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
7.2 装饰器模式的优缺点
优点
(1)增强对象功能:装饰器模式可以增强一个对象的功能,而不需要修改原始对象的代码。通过添加不同的装饰器,可以在运行时为对象增加不同的功能,从而满足不同的需求。
(2)可扩展性:装饰器模式非常灵活,允许在运行时动态地添加或删除功能,因此非常适合应对需求的变化。
(3)组合性:装饰器模式允许将多个装饰器组合在一起使用,从而实现更复杂的功能。
(4)单一职责原则:装饰器模式遵循单一职责原则,每个装饰器只关注一个特定的功能,使得代码更加清晰简洁。
(5)开闭原则:装饰器模式遵循开闭原则,可以在不修改原始对象的情况下扩展其功能,从而保证了系统的可维护性和可扩展性。
缺点
(1)增加了代码复杂性:使用装饰器模式会增加代码的复杂度,因为需要创建多个类来实现装饰器。
(2)可能会导致性能问题:由于装饰器是通过递归来实现的,可能会对性能产生一定的影响,尤其是在多层嵌套的情况下。可能会出现装饰器的堆叠问题:当多个装饰器同时应用于同一对象时,可能会出现装饰器的堆叠问题,导致代码变得难以理解和维护。
(3)可能会破坏对象的封装性:使用装饰器模式会暴露对象的内部细节,可能会破坏对象的封装性,导致代码变得不安全和不稳定。