设计模式-享元模式(Flyweight)

1. 概念

  • 享元模式是一种结构型设计模式,它通过共享技术有效地支持大量细粒度对象的复用。

2. 原理结构图

图1
在这里插入图片描述
图2
在这里插入图片描述

2. 1 角色

  • 抽象享元(Flyweight):这是所有具体享元类的基类,它定义了享元对象的内部状态和外部状态,以及需要实现的公共接口。外部状态通常以参数的形式通过方法传入。
  • 具体享元(Concrete Flyweight):这个角色实现了抽象享元中定义的接口,具体的享元类包含了实现细节,它们共享相同的内部状态,但可以根据需要存储或计算外部状态。
  • 非享元(Unsharable Flyweight):代表不可以共享的外部状态,这些状态是特定于上下文的,因此不能共享。它们以参数的形式注入到具体享元的相关方法中。
  • 享元工厂(Flyweight Factory):用于构建一个池容器,负责创建和管理享元对象。当客户端请求一个享元对象时,享元工厂会检查系统中是否已存在符合要求的享元对象,如果存在,则提供给客户;如果不存在,则创建一个新的享元对象

2. 2 内部状态和外部状态

**享元模式的内部状态和外部状态是模式的核心概念,**它们在实现对象共享和优化内存使用中起着关键作用。

  • 内部状态
    • 内部状态是享元对象的固有属性,这些属性不会随着外部环境的变化而改变。
    • 它是可以共享的状态,通常设计为不可变的属性,以便于多个享元对象之间共享。
    • 例如,在一个文档编辑器中,字体或颜色方案可以是内部状态,因为它们是共享的并且不会因为文档的不同部分而改变
  • 外部状态
    • 外部状态是随环境变化而变化的、不可以共享的状态。
    • 它通常是依赖于某个特定上下文的信息,比如对象的位置或者是与其他对象的临时关系。
    • 在连接池的例子中,连接的可用状态就是外部状态,因为它会随着连接是否被使用而改变,并且每个连接的可用状态是独立的,不能共享。
  • 举例
    • 假设我们有一个在线字体库,用户可以从字体库中选择字体,并设置字体的颜色和大小。在这个例子中,字体的字符集(如宋体、黑体等)就是内部状态,因为这是字体对象固有的属性,不随用户的操作而改变。而字体的颜色、大小等属性就是外部状态,因为这些属性会随着用户的选择而改变。

3. 代码示例

3.1 示例1
  • 使用享元模式实现一个字符渲染系统。在这个系统中,有很多字符需要渲染,但每个字符可能只是颜色或字体大小上有所不同,而字符的形状本身是一样的。因此,可以共享字符的形状,而只存储不同的颜色或字体大小信息。
// Flyweight 接口
interface Flyweight {
    void operation(int extrinsicState);
}

// ConcreteFlyweight 类,实现 Flyweight 接口
class UncheckedFlyweight implements Flyweight {
    private final String name;

    public UncheckedFlyweight(String name) {
        this.name = name;
    }

    @Override
    public void operation(int extrinsicState) {
        System.out.println("Displaying " + name + " with extrinsic state " + extrinsicState);
    }
}

// FlyweightFactory 类,用于创建和管理 Flyweight 对象
class FlyweightFactory {
    private final Map<String, Flyweight> flyweights = new HashMap<>();

    public Flyweight getFlyweight(String key) {
        Flyweight flyweight = flyweights.get(key);
        if (flyweight == null) {
            flyweight = new UncheckedFlyweight(key);
            flyweights.put(key, flyweight);
        }
        return flyweight;
    }
}

// Client 类,使用 Flyweight 对象
class Client {
    private final FlyweightFactory factory;

    public Client(FlyweightFactory factory) {
        this.factory = factory;
    }

    public void display(String key, int extrinsicState) {
        Flyweight flyweight = factory.getFlyweight(key);
        flyweight.operation(extrinsicState);
    }
}

public class FlyweightPatternDemo {
    public static void main(String[] args) {
        // 创建 FlyweightFactory 实例
        FlyweightFactory factory = new FlyweightFactory();

        // 创建 Client 实例
        Client client = new Client(factory);

        // 使用 Client 显示字符,传入不同的外部状态
        client.display("A", 1); // 显示字符 A,使用外部状态 1(可能是红色)
        client.display("B", 2); // 显示字符 B,使用外部状态 2(可能是绿色)
        client.display("A", 3); // 再次显示字符 A,使用外部状态 3(可能是蓝色)
    }
}
  • 运行FlyweightPatternDemo类的main方法,将看到以下输出:
Displaying A with extrinsic state 1  
Displaying B with extrinsic state 2  
Displaying A with extrinsic state 3
  • 在这个例子中,尽管多次请求显示字符"A",但FlyweightFactory只创建了一个"A"字符对象,并在后续请求中重复使用了它。这减少了内存中的对象数量,提高了性能。同时,通过传入不同的外部状态,可以为同一个字符对象赋予不同的表现(如不同的颜色或字体大小)。

3.2 示例2
  • 有一个简单的图形渲染系统,其中有一些基础的图形(如圆形、矩形)可以共享,而每个图形实例可以有不同的颜色、大小等属性。
// Flyweight 接口,表示可共享对象
interface Flyweight {
    void draw();
}

// UnsharedFlyweight 接口,表示不可共享对象
interface UnsharedFlyweight {
    void setExtrinsicState(int state);

    void operation();
}

// ConcreteFlyweight 类,实现 Flyweight 接口
class ConcreteFlyweight implements Flyweight {
    private final String shape;

    public ConcreteFlyweight(String shape) {
        this.shape = shape;
    }

    @Override
    public void draw() {
        System.out.println("Drawing " + shape);
    }
}

// ConcreteUnsharedFlyweight 类,实现 UnsharedFlyweight 接口
class ConcreteUnsharedFlyweight implements UnsharedFlyweight {
    private int extrinsicState;

    @Override
    public void setExtrinsicState(int state) {
        this.extrinsicState = state;
    }

    @Override
    public void operation() {
        System.out.println("Operating on unshared flyweight with extrinsic state " + extrinsicState);
    }
}


// FlyweightFactory 类,用于创建和管理 Flyweight 对象
class FlyweightFactory {
    private final Map<String, Flyweight> flyweights = new HashMap<>();

    public Flyweight getFlyweight(String key) {
        Flyweight flyweight = flyweights.get(key);
        if (flyweight == null) {
            flyweight = new ConcreteFlyweight(key);
            flyweights.put(key, flyweight);
        }
        return flyweight;
    }
}

class GraphicsSystem {
    private final FlyweightFactory flyweightFactory;
    private UnsharedFlyweight unsharedFlyweight;

    public GraphicsSystem(FlyweightFactory flyweightFactory) {
        this.flyweightFactory = flyweightFactory;
    }

    public void setUnsharedFlyweight(UnsharedFlyweight unsharedFlyweight) {
        this.unsharedFlyweight = unsharedFlyweight;
    }

    public void drawShape(String shapeKey, int extrinsicState) {
        Flyweight flyweight = flyweightFactory.getFlyweight(shapeKey);
        flyweight.draw();

        if (unsharedFlyweight != null) {
            unsharedFlyweight.setExtrinsicState(extrinsicState);
            unsharedFlyweight.operation();
        }
    }
}

public class FlyweightPatternDemo {
    public static void main(String[] args) {
        // 创建 FlyweightFactory 实例
        FlyweightFactory flyweightFactory = new FlyweightFactory();

        // 创建 GraphicsSystem 实例
        GraphicsSystem graphicsSystem = new GraphicsSystem(flyweightFactory);

        // 创建非共享的 UnsharedFlyweight 对象
        UnsharedFlyweight unsharedFlyweight = new ConcreteUnsharedFlyweight();
        graphicsSystem.setUnsharedFlyweight(unsharedFlyweight);

        // 使用 GraphicsSystem 绘制不同的形状,并设置非共享对象的状态
        graphicsSystem.drawShape("Circle", 1); // 绘制圆形,设置非共享对象状态为 1
        graphicsSystem.drawShape("Rectangle", 2); // 绘制矩形,设置非共享对象状态为 2
        graphicsSystem.drawShape("Circle", 3); // 再次绘制圆形,设置非共享对象状态为 3
    }
}

  • 当运行main方法时,会看到以下输出:
Drawing Circle  
Operating on unshared flyweight with extrinsic state 1  
Drawing Rectangle  
Operating on unshared flyweight with extrinsic state 2  
Drawing Circle  
Operating on unshared flyweight with extrinsic state 3
  • 在这个输出中,可以看到:
    • 对于圆形和矩形,由于它们是可共享的,所以FlyweightFactory只创建了一个实例,并在后续的调用中返回了相同的实例。
    • 对于非共享对象,每次调用drawShape方法时都设置了不同的状态,并进行了操作。
  • 这个示例展示了享元模式在资源受限的情况下如何有效地重用对象,同时保留了处理特定外部状态或行为的能力。通过区分共享和非共享对象,可以在提高性能和减少内存消耗的同时保持代码的灵活性和扩展性。

4. 优缺点

  • 主要作用
    • 通过共享对象来减少内存使用和提高性能。
  • 优点
    • 减少内存消耗:通过共享对象,可以显著减少系统中对象的数量,从而节省内存空间。这在处理大量相似对象时尤为有效,如数据库连接池、线程池等场景。
    • 提高系统性能:由于减少了对象的创建和销毁,享元模式可以提高系统的响应速度和性能。在高并发场景下,如电商网站的购物车或在线游戏的排行榜,这种性能提升尤为显著。
    • 支持对象共享:通过将对象的状态分为内部状态和外部状态,享元模式可以实现对象的可共享性。这使得多个对象可以共享相同的内部状态,而保持各自独立的外部状态。
  • 缺点
    • 实现较为复杂:享元模式需要将对象的状态分为内部状态和外部状态,这可能需要额外的设计和编程工作。同时,使用工厂类来管理共享对象也可能增加实现的复杂性。
    • 可能影响系统维护性:将对象状态分为内部和外部状态可能会增加系统的复杂度,使得系统的维护和理解变得更加困难。此外,如果过度使用享元模式,可能会导致代码的可读性和可维护性下降。
    • 可能降低代码可读性:由于享元模式使用工厂类来管理共享对象,可能会增加代码的抽象层次和间接性,从而降低代码的可读性。对于不熟悉该模式的开发人员来说,理解和维护代码可能会更加困难。

5. 应用场景

5.1 主要包括以下几个方面
  1. 大量共享对象的场景:在系统中存在大量共享对象时,如数据库连接池、线程池等,可以通过享元模式实现对象的共享,减少对象的创建和销毁,提高系统的性能和可扩展性。
  2. 大数据量的场景:处理大量数据的系统中,往往存在大量重复对象,如图像处理中的像素点、文本处理中的单词等。这些对象可以通过享元模式进行共享,减少对象的创建和内存消耗,提高系统的性能和可扩展性。
  3. 高并发的场景:高并发系统如电商网站的购物车、在线游戏的排行榜等,存在大量的请求和对象。通过享元模式共享这些对象,可以减少对象的创建和销毁,从而提高系统的并发处理能力和响应速度。
  4. 分布式系统中的对象共享:在分布式系统中,存在大量对象需要在不同节点间共享,如分布式缓存系统、分布式锁等。享元模式可以有效地支持这些对象在分布式环境中的共享。

5.2 实际应用
  1. 五子棋游戏:在五子棋游戏中,可以使用享元模式来表示棋子。由于棋盘上可能存在大量重复的棋子(例如黑棋或白棋),享元模式可以通过共享相同的棋子对象来减少内存占用。在这个场景中,棋子类可以作为抽象享元角色,而具体的颜色实例(如黑棋或白棋)则是具体享元角色。
  2. 文档编辑器:在文本编辑器或处理软件中,可以使用享元模式来管理文本中的字符。由于同一个文档中可能包含大量重复的字符,使用享元模式可以有效地减少内存消耗。在这种情况下,字符类是抽象享元角色,而具体的字符实例(如’a’、‘b’、'c’等)是具体享元角色。
  3. 网页开发:在网页开发中,可以使用享元模式来管理图标、图片或其他重复使用的图形元素。这有助于减少页面加载时间,并降低服务器的带宽需求。
  4. 网络游戏:在多人网络游戏中,可以使用享元模式来管理游戏中的角色、道具等资源。这有助于减少服务器的资源消耗,并提高游戏的可伸缩性。
  5. 电子商务平台:在电子商务平台中,可以使用享元模式来管理商品图片、评价等级图标等元素。这有助于减少页面加载时间,并提高用户体验。

6. JDK中的使用

  • 字符串池(String Pool):Java中的String类使用了一个内部的字符串池来管理字符串对象。当您创建一个新的String对象时,如果相同的值已经存在于池中,那么新的引用将指向池中的现有对象,而不是创建一个新的对象。这通过String类的intern()方法实现,该方法确保了相同内容的字符串共享同一个对象。
  • Integer缓存:对于整数类型的自动装箱操作,Java内部维护了一个小的缓存(通常是-128到127之间的整数),当进行自动装箱时,如果整数值在这个范围内,将直接返回缓存中的对象,而不是创建新的对象。
  • Boolean值:类似于Integer,Boolean类型的true和false在自动装箱时也是直接返回缓存中的实例,不会创建新对象。

7. 注意事项

  1. 区分内外部状态:确保对象的内部状态是共享的,而外部状态是从外部传入的。内部状态是共享的关键,而外部状态则是每个对象特有的。
  2. 管理共享对象:使用工厂模式来管理享元对象,确保它们被正确地创建和复用。
  3. 线程安全:在多线程环境中,要注意线程安全问题,避免共享对象的状态被意外修改。
  4. 节省内存:享元模式通过复用对象来节省内存,这在处理大量对象时尤其重要。
  5. 理解概念:“享”代表共享,“元”代表对象。当系统中有大量对象消耗大量内存时,可以考虑使用享元模式。
  6. 不变属性存储:享元对象应只存储不变的共享属性,可变属性应由非享元类存储,并通过享元工厂进行管理。

相关推荐

  1. 设计模式--模式Flyweight Pattern)

    2024-04-13 14:26:04       41 阅读
  2. 设计模式】11、flyweight 模式

    2024-04-13 14:26:04       35 阅读
  3. 设计模式——模式Flyweight

    2024-04-13 14:26:04       36 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-13 14:26:04       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-13 14:26:04       106 阅读
  3. 在Django里面运行非项目文件

    2024-04-13 14:26:04       87 阅读
  4. Python语言-面向对象

    2024-04-13 14:26:04       96 阅读

热门阅读

  1. 10个经典Python设计模式解析

    2024-04-13 14:26:04       40 阅读
  2. centos7 安装 rabbitmq3.8.5

    2024-04-13 14:26:04       33 阅读
  3. 时空大数据引擎-GeoMesa

    2024-04-13 14:26:04       31 阅读
  4. 便携式汽车充气泵方案开发设计研发

    2024-04-13 14:26:04       31 阅读
  5. 子矩阵(c++实现)

    2024-04-13 14:26:04       33 阅读
  6. 微服务需要多表关联吗

    2024-04-13 14:26:04       38 阅读
  7. Django 实现登录功能

    2024-04-13 14:26:04       36 阅读
  8. ES6中的Proxy

    2024-04-13 14:26:04       38 阅读