结构型模式之享元模式

371 阅读5分钟

在软件系统中,有时候会存在资源浪费的情况,例如在计算机内存中存储了多个完全相同或者非常相似的对象,如果这些对象的数量太多将导致系统运行代价过高,内存属于计算机的"稀缺资源",不应该用来"随便浪费",那么是否存在一种技术可以用于节约内存使用空间,实现对这些相同或者相似对象的共享访问呢?答案是肯定,这种技术就是我们本章将要学习的享元模式。

享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。

享元模式是设计模式中少数几个以提高系统性能为目的的模式之一

模式结构和说明

享元模式的主要角色由享元工厂、抽象享元、具体享元类和主函数几部分组成

在这里插入图片描述

  • Flyweight: 抽象享元类.所有具体享元类的超类或者接口,通过这个接口,Flyweight可以接受并作用于外部专题

  • ConcreteFlyweight:具体享元类.指定内部状态,为内部状态增加存储空间

  • UnsharedConcreteFlyweight:非共享具体享元类,指出那些不需要共享的Flyweight子类

  • FlyweightFactory:享元工厂,主要用来创建并管理共享的享元对象,并对外提供访问共享享元的接口

享元模式的核心在于享元工厂类,享元工厂类的作用在于提供一个用于存储享元对象的享元池,用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。

享元模式以共享的方式高效地支持大量的细粒度对象,享元对象能做到共享的关键是区分内部状态(Internal State)和外部状态(External State)

  • 内部状态是存储在享元对象内部并且不会随环境而改变的状态,因此内部状态可以共享
  • 外部状态是随环境改变而改变的,不可以共享的状态.享元对象的外部状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部.一个外部状态与另一个外部状态之间是相互独立的

示例代码

场景:构建不同颜色的圆,弧,方形,共享的是形状,外部动态修改的是形状的颜色

  1. 先创建一个享元对象
@interface Shape : NSObject
/**/
@property (nonatomic,strong) NSString *shapeName;

- (void)shapeShowColor:(NSString *)color;

@end
@interface Shape()
/**/
@property (nonatomic,strong) NSString *shapeColor;

@end

@implementation Shape

- (void)shapeShowColor:(NSString *)color
{
    self.shapeColor = color;
    NSLog(@"%@--颜色%@",self,color);
}

@end
  1. 创建享元工厂
@implementation ShapeFactory

- (Shape *)shapeWithType:(ShapeType)type
{
    //懒加载shapePools,初始化享元池
    if (self.shapePools == nil) {
        self.shapePools = [NSMutableDictionary dictionary];
    }
    //从享元池取
    Shape *shape =[self.shapePools objectForKey:@(type)];
    
    if (!shape) {
        shape = [[Shape alloc] init];
        switch (type) {
            case kCircle:
                shape.shapeName = @"圆";
             
                break;
            case kArc:
                shape.shapeName = @"弧";
              
                break;
            case kRect:
                shape.shapeName = @"方形";
                break;
            default:
                break;
        }
        self.shapePools[@(type)] = shape;
    }
    return shape;
}
  1. 客户端使用
 ShapeFactory *factory = [[ShapeFactory alloc] init];
    Shape *circle =  [factory shapeWithType:kCircle];
    Shape *circle1 =  [factory shapeWithType:kCircle];
    [circle shapeShowColor:@"蓝色"];
    [circle1 shapeShowColor:@"白色"];
  1. 结果:
    在这里插入图片描述

可以看出打印的是同一对象. 其实最经典的例子应该是围棋的棋子,共享的应该是这个棋子的形状、大小、图片,外部动态修改的应该是棋子的位置。那么,只要定义两个类,黑棋和白棋,而且工厂中或说整个系统中也就只维护两个对象,一个是黑棋一个是白棋。大家可以实践一下

模式讲解

1. 变与不变

享元模式设计的重点在于分离变与不变,把一个对象的状态分成内部和外部状态,内部状态是不变的,外部状态是可变的.然后通过共享不变的部分,达到减少对象数量、并节约内存的目的。在享元对象需要的时候,可以从外部传入外部状态给共享的对象,共享对象会在功能处理的时候,使用自己内部的状态和这些外部的状态。

2. 实例池

在享元模式中,为了创建和管理共享的享元部分,引入享元工厂,享元工厂中一般都包含有享元对象的实例池,享元对象就是缓存在这个实例池中的.

并且享元对象的初始化也是在第一次向享元工厂请求获取共享对象的时候,进行共享对象的初始化,而且多半都是在享元工厂内部实现,不会从外部传入共享对象。

3. 优缺点

  • 优点: 能够极大的减少系统中的对象个数.享元模式由于使用了外部状态,外部状态相对独立,不会影响到内部状态,所以享元模式使得享元对象能够在不同的环境被共享。
  • 缺点:
    1. 由于享元模式需要区分外部状态和内部状态,使得应用程序在某种程度上来说更加复杂化
    2. 为了使对象可以共享,享元模式需要将享元对象的状态外部化,而读取外部状态使得运行时间变长

4. 使用场景

  • 如果一个系统中存在大量的相同或者相似的对象,由于这类对象的大量使用,会造成系统内存的耗费,可以使用享元模式来减少系统中对象的数量。

  • 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。

  • 需要缓冲池的场景。

    Demo地址