设计模式学习(九)——《大话设计模式》
装饰模式
装饰模式(Decorator Pattern)是一种设计模式,用于向对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它通过创建一个装饰类包装原有的类,提供更强的扩展功能,这样做既保持了类的核心职责,又允许了向对象动态地添加职责。
工作原理
- 组件接口(Component):定义了一个对象接口,可以给这些对象动态地添加职责。
- 具体组件(Concrete Component):是组件接口的一个实现,我们可以给这个对象添加一些职责。
- 装饰(Decorator):持有一个组件(Component)对象的实例,并定义了与组件接口一致的接口,这样就可以让装饰类在不改变接口的前提下,增加新的职责。
- 具体装饰(Concrete Decorator):是装饰抽象类的具体实现对象,负责给组件添加新的职责。
应用场景
装饰模式通常用于以下场景:
- 当需要给一个现有的对象添加额外的功能,而又不希望影响其他对象时。
- 当需要给对象添加的功能可能在未来会被动态地撤销或添加时。
- 当扩展一个类的功能比创建子类更灵活时。
优点
- 装饰模式提供了比继承更有弹性的替代方案。通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。
- 装饰模式允许对对象进行动态的修改,相比静态继承更加灵活。
- 可以通过一种透明的方式来扩展对象的功能,用户从外部使用这些功能时不需要了解具体的实现细节。
缺点
- 使用装饰模式会产生很多小对象,过多地使用装饰模式会使程序变得复杂。
- 装饰链设置不当可能会导致问题,比如循环引用、过深的装饰链等。
示例代码
# 组件接口
class Coffee:
def cost(self):
return 5
# 装饰器基类
class CoffeeDecorator(Coffee):
def __init__(self, coffee):
self.coffee = coffee
def cost(self):
return self.coffee.cost()
# 具体装饰器
class MilkDecorator(CoffeeDecorator):
def cost(self):
return self.coffee.cost() + 2
class SugarDecorator(CoffeeDecorator):
def cost(self):
return self.coffee.cost() + 1
# 使用
basicCoffee = Coffee()
milkCoffee = MilkDecorator(basicCoffee)
sugarMilkCoffee = SugarDecorator(milkCoffee)
print("Cost of sugarMilkCoffee:", sugarMilkCoffee.cost())
// 定义一个组件接口,所有消息组件都需要实现这个接口
trait MessageComponent {
fn display(&self) -> String;
}
// 具体组件:文本消息
struct TextMessage {
content: String,
}
impl TextMessage {
// 构造函数,用于创建一个新的文本消息
fn new(content: &str) -> TextMessage {
TextMessage {
content: content.to_string(),
}
}
}
impl MessageComponent for TextMessage {
// 实现 display 方法,返回消息内容
fn display(&self) -> String {
self.content.clone()
}
}
// 装饰器基类,持有一个实现了 MessageComponent 接口的对象
struct MessageDecorator {
component: Box<dyn MessageComponent>,
}
impl MessageDecorator {
// 构造函数,接收一个实现了 MessageComponent 接口的对象
fn new(component: Box<dyn MessageComponent>) -> MessageDecorator {
MessageDecorator { component }
}
}
impl MessageComponent for MessageDecorator {
// 默认实现 display 方法,直接调用内部组件的 display 方法
fn display(&self) -> String {
self.component.display()
}
}
// 具体装饰器:加密装饰器
struct EncryptDecorator {
component: Box<dyn MessageComponent>,
}
impl EncryptDecorator {
// 构造函数,接收一个实现了 MessageComponent 接口的对象
fn new(component: Box<dyn MessageComponent>) -> EncryptDecorator {
EncryptDecorator { component }
}
// 加密方法(示例,未实现真正的加密逻辑)
fn encrypt(&self, message: &str) -> String {
format!("加密({})", message)
}
}
impl MessageComponent for EncryptDecorator {
// 覆盖 display 方法,添加加密逻辑
fn display(&self) -> String {
let message = self.component.display();
self.encrypt(&message)
}
}
// 使用示例
fn main() {
// 创建一个文本消息
let message = TextMessage::new("Hello, World!");
// 使用加密装饰器装饰文本消息
let encrypted_message = EncryptDecorator::new(Box::new(message));
// 显示加密后的消息
println!("加密后的消息: {}", encrypted_message.display());
}
代理模式
代理模式(Proxy Pattern)是一种结构型设计模式,它为其他对象提供一个代理或占位符以控制对这个对象的访问。使用代理模式,可以在不改变原始对象接口的情况下,进行一些额外的操作,比如访问控制、延迟初始化、网络请求、记录日志等。代理模式主要分为三种类型:静态代理、动态代理和虚拟代理。
代理模式的组成
- 主题(Subject)接口:定义了真实对象和代理对象共同的接口,这样就在任何使用真实对象的地方都可以使用代理对象。
- 真实主题(Real Subject)类:实现了主题接口的类,定义了代理所代表的真实对象。
- 代理(Proxy)类:也实现了主题接口,用来包含对真实主题的引用,并可以在访问真实主题之前或之后执行一些操作。
代理模式的应用场景
- 远程代理:为远程对象提供一个本地的代理对象,这样可以隐藏对象位于不同地址空间的事实。
- 虚拟代理:根据需要创建开销很大的对象。通过它来存放实例化需要很长时间的真实对象。
- 保护代理:控制对原始对象的访问。保护代理用于对象应该有不同访问权限的时候。
- 智能引用:指当调用真实的对象时,代理处理另外一些事情,比如计算真实对象的引用次数,这样当该对象没有引用时,可以自动释放它。
优点与缺点
优点:
- 可以在不修改被封装对象的代码的情况下进行控制。
- 可以实现懒加载,对象只有在需要的时候才会被创建。
- 可以实现访问控制和日志记录等功能。
缺点:
- 可能会导致系统设计变得复杂,因为引入了很多新类。
- 请求的响应可能会延迟。
示例代码
from abc import ABC, abstractmethod
class Image(ABC):
@abstractmethod
def display(self):
pass
class RealImage(Image):
def __init__(self, filename):
self.filename = filename
self.load_from_disk()
def load_from_disk(self):
print(f"Loading {self.filename}")
def display(self):
print(f"Displaying {self.filename}")
class ProxyImage(Image):
def __init__(self, filename):
self.filename = filename
self.real_image = None
def display(self):
if self.real_image is None:
self.real_image = RealImage(self.filename)
self.real_image.display()
if __name__ == "__main__":
image1 = ProxyImage("test_10mb.jpg")
image2 = ProxyImage("test_20mb.jpg")
# 图像将从磁盘加载
image1.display()
print("")
# 图像不需要从磁盘加载
image1.display()
print("")
# 图像将从磁盘加载
image2.display()
// 定义图像特征(trait),所有图像类型都需要实现这个特征
trait Image {
fn display(&self);
}
// 真实图像结构体,表示从磁盘加载的图像
struct RealImage {
filename: String,
}
impl RealImage {
// 构造函数,用于创建并加载一个新的真实图像
fn new(filename: &str) -> RealImage {
println!("Loading {} from disk.", filename); // 模拟从磁盘加载图像
RealImage {
filename: filename.to_string(),
}
}
}
// 实现图像特征,允许真实图像被显示
impl Image for RealImage {
fn display(&self) {
println!("Displaying {}.", self.filename);
}
}
// 代理图像结构体,用于控制对真实图像的访问
struct ProxyImage {
real_image: Option<RealImage>,
filename: String,
}
impl ProxyImage {
// 构造函数,用于创建一个新的代理图像,此时不加载真实图像
fn new(filename: &str) -> ProxyImage {
ProxyImage {
real_image: None,
filename: filename.to_string(),
}
}
}
// 实现图像特征,允许通过代理来显示图像
impl Image for ProxyImage {
fn display(&self) {
match &self.real_image {
Some(real_image) => {
// 如果真实图像已加载,则直接显示
real_image.display();
},
None => {
// 如果真实图像未加载,则先加载再显示
let real_image = RealImage::new(&self.filename);
real_image.display();
},
}
}
}
fn main() {
let image1 = ProxyImage::new("test_10mb.jpg");
let image2 = ProxyImage::new("test_20mb.jpg");
// 第一次显示时,将从磁盘加载图像
image1.display();
println!("");
// 第二次显示时,由于真实图像已加载,将直接显示而无需再次加载
image1.display();
println!("");
// 对于另一个图像对象,如果尝试显示,也将经历相同的加载过程
image2.display();
}