设计模式:享元与代理模式解析
1. 享元模式介绍
享元模式(Flyweight Pattern)利用共享技术高效支持大量细粒度对象。当满足以下条件时,可考虑使用该模式:
- 应用程序使用大量对象。
- 内存中保存对象会影响性能。
- 对象的大部分唯一状态(外部状态)可外部化且轻量化。
- 去除对象的外部状态后,相对较少的共享对象可替代原有的大量对象。
- 应用程序不依赖对象标识,因为共享对象没有唯一标识。
为了说明该模式的概念,我们将开发一个应用程序,从可共享的花池中绘制数百个或更多的花图案。
2. 创建百花池应用
我们要创建一个小应用,在屏幕上显示大量随机的花图像。有六种花需要显示,分别是银莲花、大波斯菊、非洲菊、蜀葵、茉莉和百日草。目标是仅用这六种花的唯一实例,绘制大量(数百个或更多)随机大小和位置的花。如果为屏幕上绘制的每个实例都创建一个花实例,应用程序可能会消耗大量内存。因此,我们使用享元模式将花实例数量限制为可选花的类型总数。
3. 享元模式设计与实现
-
享元工厂与产品
:需要一个享元工厂和一些享元产品。
FlowerView是UIImageView的子类,可自定义绘制花图像。应用的享元工厂是FlowerFactory,它管理FlowerView实例池。虽然池中对象的类是FlowerView,但客户端期望从FlowerFactory返回的是UIView实例,这样设计更灵活。 -
可共享花的设计与实现
:有六种花图像,每种图像由
FlowerView的唯一实例维护。无论需要返回多少花,FlowerFactory只返回FlowerView的唯一实例。 -
FlowerView类的实现 :
// FlowerView.h
@interface FlowerView : UIImageView
{
}
- (void) drawRect:(CGRect)rect;
@end
// FlowerView.m
#import "FlowerView.h"
@implementation FlowerView
- (void) drawRect:(CGRect)rect
{
[self.image drawInRect:rect];
}
@end
-
FlowerFactory类的实现 :
// FlowerFactory.h
@interface FlowerFactory : NSObject
{
@private
NSMutableDictionary *flowerPool_;
}
- (UIView *) flowerViewWithType:(FlowerType)type;
@end
// 定义 FlowerType
typedef enum
{
kAnemone,
kCosmos,
kGerberas,
kHollyhock,
kJasmine,
kZinnia,
kTotalNumberOfFlowerTypes
} FlowerType;
// FlowerFactory.m
#import "FlowerFactory.h"
#import "FlowerView.h"
@implementation FlowerFactory
- (UIView *) flowerViewWithType:(FlowerType)type
{
// 懒加载花池
if (flowerPool_ == nil)
{
flowerPool_ = [[NSMutableDictionary alloc]
initWithCapacity:kTotalNumberOfFlowerTypes];
}
// 尝试从池中检索花
UIView *flowerView = [flowerPool_ objectForKey:[NSNumber
numberWithInt:type]];
// 如果请求的类型不可用,则创建一个新的并添加到池中
if (flowerView == nil)
{
UIImage *flowerImage;
switch (type)
{
case kAnemone:
flowerImage = [UIImage imageNamed:@"anemone.png"];
break;
case kCosmos:
flowerImage = [UIImage imageNamed:@"cosmos.png"];
break;
case kGerberas:
flowerImage = [UIImage imageNamed:@"gerberas.png"];
break;
case kHollyhock:
flowerImage = [UIImage imageNamed:@"hollyhock.png"];
break;
case kJasmine:
flowerImage = [UIImage imageNamed:@"jasmine.png"];
break;
case kZinnia:
flowerImage = [UIImage imageNamed:@"zinnia.png"];
break;
default:
break;
}
flowerView = [[[FlowerView alloc]
initWithImage:flowerImage] autorelease];
[flowerPool_ setObject:flowerView
forKey:[NSNumber numberWithInt:type]];
}
return flowerView;
}
- (void) dealloc
{
[flowerPool_ release];
[super dealloc];
}
@end
花池(
flowerPool_
)不在
FlowerFactory
的
init
方法中初始化,而是在
flowerViewWithType:
工厂方法中懒加载。如果请求的实例不在池中,工厂将创建一个新的
FlowerView
实例,并将其添加到池中。
4.
FlowerView
对象的共享
享元对象通常与某种可共享的内部状态相关联,
FlowerView
享元对象共享其内部花图像作为内部状态。无论享元对象是否有可共享的内部状态,都需要定义某种外部数据结构来跟踪享元对象的外部状态(唯一信息)。我们定义了一个
ExtrinsicFlowerState
的 C 结构体:
typedef struct
{
UIView *flowerView;
CGRect area;
} ExtrinsicFlowerState;
每个花的显示区域是唯一的,因此需要将其视为外部状态。我们需要一个数组来存储客户端创建的花的外部状态。
5. 享元模式的节省效果
使用享元对象能节省多少资源取决于以下因素:
| 因素 | 说明 |
| ---- | ---- |
| 对象总数的减少 | 来自共享的对象数量减少 |
| 每个对象的内部状态量 | 即可共享状态的量 |
| 外部状态的处理方式 | 是计算还是存储 |
然而,传输、查找和/或计算享元对象的外部状态可能会带来运行时成本。随着更多享元对象被共享,这种成本可以被节省的空间所抵消。共享的享元对象越多,存储节省就越大。当对象同时使用大量的内部和外部状态,且外部状态可以计算而不是存储时,节省效果最佳。
6. 客户端代码实现
- (void)viewDidLoad
{
[super viewDidLoad];
// 构建花列表
FlowerFactory *factory = [[[FlowerFactory alloc] init] autorelease];
NSMutableArray *flowerList = [[[NSMutableArray alloc]
initWithCapacity:500] autorelease];
for (int i = 0; i < 500; ++i)
{
// 从花工厂获取随机花类型的共享花实例
FlowerType flowerType = arc4random() % kTotalNumberOfFlowerTypes;
UIView *flowerView = [factory flowerViewWithType:flowerType];
// 设置花在屏幕上的位置和区域
CGRect screenBounds = [[UIScreen mainScreen] bounds];
CGFloat x = (arc4random() % (NSInteger)screenBounds.size.width);
CGFloat y = (arc4random() % (NSInteger)screenBounds.size.height);
NSInteger minSize = 10;
NSInteger maxSize = 50;
CGFloat size = (arc4random() % (maxSize - minSize + 1)) + minSize;
// 为花分配属性到外部状态对象
ExtrinsicFlowerState extrinsicState;
extrinsicState.flowerView = flowerView;
extrinsicState.area = CGRectMake(x, y, size, size);
// 将外部花状态添加到花列表
[flowerList addObject:[NSValue value:&extrinsicState
withObjCType:@encode(ExtrinsicFlowerState)]];
}
// 将花列表添加到 FlyweightView 实例
[(FlyweightView *)self.view setFlowerList:flowerList];
}
// 自定义视图重写 drawRect: 方法绘制花
- (void)drawRect:(CGRect)rect
{
// 绘图代码
for (NSValue *stateValue in flowerList_)
{
ExtrinsicFlowerState state;
[stateValue getValue:&state];
UIView *flowerView = state.flowerView;
CGRect area = state.area;
[flowerView drawRect:area];
}
}
在主循环的每次迭代中,
FlowerFactory
根据随机选择的花类型返回一个
UIView
实例(实际上是
FlowerView
)。将唯一的位置和大小放入
ExtrinsicFlowerState
结构中,并将其添加到
flowerList
中。当 500 朵花的完整列表分配到数组后,将列表设置到
FlyweightView
实例。
7. 代理模式介绍
代理模式是一种常见的设计模式,在很多场景中都有应用。例如,在线约会网站提供免费搜索、调情等有限功能,但不提供与其他成员的实际交流。直到用户觉得值得支付费用时,才可以访问更多仅付费成员可用的功能。代理模式就是基于这种思想,代理作为一个替身或占位符,控制对另一个对象的访问。
8. 代理模式的类型
- 远程代理 :为不同地址空间或网络中的对象提供本地代表。
- 虚拟代理 :按需创建重量级对象。
- 保护代理 :根据不同的访问权限控制对原始对象的访问。
- 智能引用代理 :计算对真实对象的引用数量以进行内存管理,也可用于锁定真实对象,防止其他对象更改。
9. 虚拟代理实现图片懒加载
在某些应用中,如
TouchPainter
应用的缩略图视图,用户要浏览文件系统中所有以前保存的涂鸦。但加载所有缩略图不实际,尤其是设备内存有限时。在原始设计中,涂鸦以小缩略图的形式逐行排列在屏幕上。每个缩略图占位符在实际缩略图图像加载之前,显示一个占位符图像。当真实缩略图图像完全加载后,原始占位符图像将被替换。
这种用户界面在一些 iOS 应用中用于在用户滚动视图时加载缩略图,在后台加载单个小缩略图时,为用户提供流畅的操作和响应。
以下是享元模式中客户端代码绘制花的流程 mermaid 图:
graph TD;
A[开始] --> B[初始化 FlowerFactory 和 flowerList];
B --> C{循环 500 次};
C -- 是 --> D[随机选择花类型];
D --> E[从 FlowerFactory 获取花实例];
E --> F[设置花的位置和大小];
F --> G[创建 ExtrinsicFlowerState 结构];
G --> H[将 ExtrinsicFlowerState 编码并添加到 flowerList];
H --> C;
C -- 否 --> I[将 flowerList 设置到 FlyweightView];
I --> J[结束];
以下是虚拟代理加载图片的流程 mermaid 图:
graph TD;
A[用户进入缩略图视图] --> B[显示占位符图像];
B --> C{是否有缩略图需要加载};
C -- 是 --> D[后台加载缩略图];
D --> E{缩略图是否加载完成};
E -- 是 --> F[替换占位符图像为真实缩略图];
F --> C;
C -- 否 --> G[结束加载];
E -- 否 --> D;
设计模式:享元与代理模式解析
10. 代理模式的概念与工作原理
代理模式的核心概念是代理作为一个替身或占位符,控制对另一个对象的访问。当客户端向代理对象发送“请求”消息时,代理对象会将相同的消息转发给真实主题对象(RealSubject),由真实主题对象执行实际操作来间接满足客户端的请求。
在运行时,客户端拥有一个抽象类型的主题引用,实际上这个引用指向的是代理对象,而代理对象内部又持有一个真实主题实例的引用,当需要执行重型任务时,由真实主题实例来完成。
11. 何时使用代理模式
以下情况可以考虑使用代理模式:
- 需要远程代理为不同地址空间或网络中的对象提供本地代表。
- 需要虚拟代理按需创建重量级对象,如上述提到的图片懒加载场景。
- 需要保护代理根据不同的访问权限控制对原始对象的访问。
- 需要智能引用代理计算对真实对象的引用数量以进行内存管理,或锁定真实对象防止其他对象更改。
12. 虚拟代理在图片加载中的详细实现
在
TouchPainter
应用的缩略图视图中,为了实现图片的懒加载,我们可以使用虚拟代理。以下是一个简化的实现思路:
首先,定义一个抽象的图片协议,让真实图片对象和代理图片对象都遵循这个协议:
@protocol ImageProtocol <NSObject>
- (void)displayImage;
@end
然后,实现真实图片对象:
@interface RealImage : NSObject <ImageProtocol>
- (instancetype)initWithImageName:(NSString *)imageName;
@end
@implementation RealImage
{
NSString *_imageName;
UIImage *_image;
}
- (instancetype)initWithImageName:(NSString *)imageName
{
self = [super init];
if (self) {
_imageName = imageName;
_image = [UIImage imageNamed:imageName];
}
return self;
}
- (void)displayImage
{
// 显示真实图片
NSLog(@"Displaying real image: %@", _imageName);
}
@end
接着,实现虚拟代理图片对象:
@interface ProxyImage : NSObject <ImageProtocol>
- (instancetype)initWithImageName:(NSString *)imageName;
@end
@implementation ProxyImage
{
NSString *_imageName;
RealImage *_realImage;
}
- (instancetype)initWithImageName:(NSString *)imageName
{
self = [super init];
if (self) {
_imageName = imageName;
}
return self;
}
- (void)displayImage
{
if (!_realImage) {
_realImage = [[RealImage alloc] initWithImageName:_imageName];
}
[_realImage displayImage];
}
@end
在客户端代码中使用代理对象:
- (void)viewDidLoad
{
[super viewDidLoad];
ProxyImage *proxyImage = [[ProxyImage alloc] initWithImageName:@"thumbnail.png"];
// 当需要显示图片时调用
[proxyImage displayImage];
}
13. 代理模式的优势
- 性能优化 :如虚拟代理按需创建重量级对象,避免一次性加载大量资源,提高应用性能。
- 访问控制 :保护代理可以根据不同的访问权限控制对原始对象的访问,增强系统的安全性。
- 延迟加载 :通过代理模式可以延迟对真实对象的操作,直到真正需要时才进行,节省系统资源。
14. 享元与代理模式的对比
| 模式 | 特点 | 应用场景 |
|---|---|---|
| 享元模式 | 利用共享技术支持大量细粒度对象,节省内存 | 当应用使用大量对象,且对象有可共享的内部状态时 |
| 代理模式 | 作为替身或占位符控制对另一个对象的访问 | 如远程访问、按需创建对象、访问控制等场景 |
15. 总结
享元模式和代理模式都是非常实用的设计模式,它们在不同的场景下发挥着重要作用。享元模式通过共享对象节省内存,提高资源利用率;代理模式通过控制对对象的访问,实现性能优化、访问控制和延迟加载等功能。在实际开发中,我们可以根据具体的需求选择合适的设计模式,以提高应用的性能和可维护性。
以下是代理模式中虚拟代理加载图片的详细流程 mermaid 图:
graph TD;
A[客户端请求显示图片] --> B[创建 ProxyImage 对象];
B --> C{RealImage 是否已创建};
C -- 否 --> D[创建 RealImage 对象];
D --> E[加载真实图片];
E --> F[显示真实图片];
C -- 是 --> F;
F --> G[结束];
通过对享元模式和代理模式的学习和应用,我们可以更好地设计和实现高效、灵活的软件系统。在后续的开发中,我们可以根据具体的业务需求,合理运用这些设计模式,提升软件的质量和性能。
超级会员免费看
2550

被折叠的 条评论
为什么被折叠?



