享元模式

60 阅读4分钟

1. 享元模式简介

享元模式(Flyweight Pattern)是一种结构型设计模式,旨在通过共享对象来减少系统中的对象数量,从而提高系统的性能和减少内存消耗。在享元模式中,共享的对象通常是不可变的(即其状态不能修改),而客户端需要的可变状态则会被作为外部状态传递给享元对象。

2. 用一个常见的例子来理解享元模式:以五子棋应用为例

五子棋应用需要大量的棋子对象,它们除了颜色有黑白之分,摆放的位置不同之外其他都一样,非常适合使用享元模式。

2.1 UML类图

classDiagram
    class Client {
        +call()
    }
    
    class ChessFactory {
        -CHESS: dict
        -chesses: dict
        +__init__()
        +getChess(color: str): Chess
    }

    class Chess {
        +draw(x: int, y: int): void
    }

    class WhiteChess {
        -color: str
        +__init__()
        +draw(x: int, y: int): void
    }

    class BlackChess {
        -color: str
        +__init__()
        +draw(x: int, y: int): void
    }

    Chess <|.. WhiteChess
    Chess <|.. BlackChess
    Chess --o ChessFactory
    Client --> ChessFactory

在这个类图中,Chess 是抽象基类,定义了一个抽象方法 draw(),表示棋子的绘制操作。WhiteChessBlackChess 类分别表示白色和黑色棋子,它们实现了 Chess 类并且具有相应的颜色属性。ChessFactory 类是一个工厂类,用于创建棋子对象。

2.2 Python代码

from abc import ABC, abstractmethod


class Chess(ABC):
    @abstractmethod
    def draw(self, x: int, y: int):
        raise NotImplemented
    

class WhiteChess(Chess):
    def __init__(self):
         # 这里将颜色当做内部状态,
         # 其实也可以把颜色当做是外部状态,这样就不需要黑白两个类了,
         # 而只需要一个通用的棋子类即可。
         self.color = "White"

    def draw(self, x: int, y: int):
        print(f"{self.color} chess drawn at ({x}, {y})")


class BlackChess(Chess):
    def __init__(self):
        self.color = "Black"

    def draw(self, x: int, y: int):
        print(f"{self.color} chess drawn at ({x}, {y})")


class ChessFactory:
    def __init__(self):
        self.CHESS = {"White": WhiteChess, "Black": BlackChess}
        self.chesses = {}

    def getChess(self, color: str):
        assert color in self.CHESS
        
        if color not in self.chesses:
            self.chesses[color] = self.CHESS[color]()
        chess = self.chesses[color]
        return chess
    

def client():
    colors = ["White", "Black"]
    factory = ChessFactory()
    # 下黑子:
    blackChess = factory.getChess(colors[1])
    # 下白子:
    whiteChess = factory.getChess(colors[0])

3. 享元模式的结构

享元模式包含以下实体:

  • 享元接口Flyweight: 所有具体享元类的共享接口,通常包含对外部状态的操作。
  • 具体享元类ConcreteFlyweight: 继承Flyweight类或实现享元接口,包含内部状态。
  • 享元工厂类FlyweightFactory: 创建并管理享元对象,当用户请求时,提供已创建的实例或者创建一个。
  • 客户端Client: 维护外部状态,在使用享元对象时,将外部状态传递给享元对象。

用UML表示如下:

classDiagram
    class Client {
        client(): void
    }
    
    class FlyweightFactory {
        -flyweights: Dict[Any, Flyweight]
        +getFlyweight(internalState): Flyweight
    }
    
    class Flyweight {
        +operate(externalState)
    }
    
    class ConcreteFlyweight {
        -internalState: Any
        +operate(externalState)
    }
    
    Client --> FlyweightFactory
    Flyweight --o FlyweightFactory
    Flyweight <|.. ConcreteFlyweight

4. 享元模式的使用场景以及优缺点

使用场景

  • 系统中存在大量相似对象,且大部分对象的状态可以抽取出来成为共享状态。
  • 对象的可变状态可以外部化,或者客户端可以通过参数传递给对象。
  • 系统需要频繁创建和销毁对象,且对象的复用率较高。

优点

  • 减少内存消耗:通过共享对象来减少系统中对象的数量,降低内存消耗。
  • 提高性能:减少对象的创建和销毁次数,提高系统性能。
  • 简化代码:将可变状态与不可变状态分离,简化了对象的设计和管理。

缺点

  • 增加复杂性:引入了共享对象和外部状态的概念,增加了系统的复杂性。
  • 限制修改:共享对象通常是不可变的,对于需要频繁修改状态的对象不太适用。

5. 简单练习:【设计模式专题之享元模式】12-图形编辑器

""" 
【设计模式专题之享元模式】12-图形编辑器
时间限制:1.000S  空间限制:256MB
题目描述
在一个图形编辑器中,用户可以绘制不同类型的图形,包括圆形(CIRCLE)、矩形(RECTANGLE)、三角形(TRIANGLE)等。现在,请你实现一个图形绘制程序,要求能够共享相同类型的图形对象,以减少内存占用。
输入描述
输入包含多行,每行表示一个绘制命令。每个命令包括两部分: 

图形类型(Circle、Rectangle 或 Triangle) 

绘制的坐标位置(两个整数,分别表示 x 和 y)

输出描述
对于每个绘制命令,输出相应图形被绘制的位置信息。如果图形是首次绘制,输出 "drawn at",否则输出 "shared at"。
输入示例
CIRCLE 10 20
RECTANGLE 30 40
CIRCLE 15 25
TRIANGLE 5 15
CIRCLE 10 20
RECTANGLE 30 40
输出示例
CIRCLE drawn at (10, 20)
RECTANGLE drawn at (30, 40)
CIRCLE shared at (15, 25)
TRIANGLE drawn at (5, 15)
CIRCLE shared at (10, 20)
RECTANGLE shared at (30, 40)
"""


from abc import ABC, abstractmethod
from typing import Dict


class Shape(ABC):
    @abstractmethod
    def draw(self, x: int, y: int):
        raise NotImplemented
        
        
class Circle(Shape):
    def __init__(self):
        self.isDrawn = False  # 内部状态,标记是否是首次访问
        self.shapeType = "CIRCLE"
        
    def draw(self, x: int, y: int):
        if not self.isDrawn:
            print(f"{self.shapeType} drawn at ({x}, {y})")
            self.isDrawn = True
        else:
            print(f"{self.shapeType} shared at ({x}, {y})")
            
            
class Rectangle(Shape):
    def __init__(self):
        self.isDrawn = False
        self.shapeType = "RECTANGLE"
        
    def draw(self, x: int, y: int):
        if not self.isDrawn:
            print(f"{self.shapeType} drawn at ({x}, {y})")
            self.isDrawn = True
        else:
            print(f"{self.shapeType} shared at ({x}, {y})")


class Triangle(Shape):
    def __init__(self):
        self.isDrawn = False
        self.shapeType = "TRIANGLE"
    
    def draw(self, x: int, y: int):
        if not self.isDrawn:
            print(f"{self.shapeType} drawn at ({x}, {y})")
            self.isDrawn = True
        else:
            print(f"{self.shapeType} shared at ({x}, {y})")


class ShapeFactory:
    def __init__(self):
        self.SHAPES = {
            "CIRCLE": Circle,
            "RECTANGLE": Rectangle,
            "TRIANGLE": Triangle
        }
        self.shapes: Dict[str, Shape] = {}
        
    def getShape(self, shapeType: str):
        if shapeType not in self.shapes:
            self.shapes[shapeType] = self.SHAPES[shapeType]()
        return self.shapes[shapeType]


def client():
    factory = ShapeFactory()
    while True:
        try:
            t, x, y = input().split(" ")
            x, y = int(x), int(y)
        except EOFError:
            break
        else:
            shape = factory.getShape(t)
            shape.draw(x, y)
            

if __name__ == "__main__":
    client()

6. 参考文章

  1. 卡码网设计模式-享元模式
  2. 秒懂设计模式之享元模式(Flyweight Pattern)