# Design Patterns @ Python – Flyweight Pattern 享元(蠅量)模式筆記

**URL:** <https://vip.studycamp.tw/t/design-patterns-python-%E2%80%93-flyweight-pattern-%E4%BA%AB%E5%85%83%E8%A0%85%E9%87%8F%E6%A8%A1%E5%BC%8F%E7%AD%86%E8%A8%98/6574>\
**Category:** Python 深入學習\
**Tags:** 上課筆記\
**Created:** [2023年十月17日 03:56 UTC](https://vip.studycamp.tw/t/design-patterns-python-%E2%80%93-flyweight-pattern-%E4%BA%AB%E5%85%83%E8%A0%85%E9%87%8F%E6%A8%A1%E5%BC%8F%E7%AD%86%E8%A8%98/6574 "2023-10-17T03:56:04Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![ChrisWei](https://vip.studycamp.tw/user_avatar/vip.studycamp.tw/chriswei/32/196_2.png) [@ChrisWei](https://vip.studycamp.tw/u/ChrisWei)\
**Post date:** [2023年十月17日 03:56 UTC](https://vip.studycamp.tw/t/design-patterns-python-%E2%80%93-flyweight-pattern-%E4%BA%AB%E5%85%83%E8%A0%85%E9%87%8F%E6%A8%A1%E5%BC%8F%E7%AD%86%E8%A8%98/6574/1 "2023-10-17T03:56:04Z")

</div>

### 設計模式–享元(蠅量)模式(Flyweight Pattern)

 ![Design_Pattern](https://vip.studycamp.tw/uploads/default/original/2X/c/cbb6be10adf27a7684ff110482ebe2e877d69c05.jpeg)

#### 前言：

- 這篇筆記主要是參考自 Udemy 線上學習平台上 Dmitri Nesteruk 老師開的 [Design Patterns in Python （付費）課程](https://www.udemy.com/course/design-patterns-python/) 中的 Flyweight Pattern 享元模式的課程部分以及 對岸圖靈星球論壇發表在 Youtube 頻道上的[設計模式–享元模式（免費）教學視頻](https://youtu.be/yNa9ZMRk0ys?si=4xW5SL4XFs8Bjhtk)的重點摘要。  
 ![圖靈星球論壇 -- 設計模式之享元模式](https://vip.studycamp.tw/uploads/default/original/2X/6/6db4a1c932e6aaed0656acc8e0c9fd7eb52fc2a8.jpeg)

#### ※ 什麼是享元模式?

- Flyweight Pattern 在中文維基百科是譯為[享元模式](https://zh.wikipedia.org/zh-tw/%E4%BA%AB%E5%85%83%E6%A8%A1%E5%BC%8F)
- 如程式中有大量的固定類的相似實例(Instance)物件，而該類物件中有不變的及可變的部分，就可把 **_可變的部分抽離出來，將不變的部分則共享_**
- Flyweight Pattern 就是透過這樣共享物件的設計，來 **_節省程式執行時的記憶體開銷_**
- Flyweight Pattern 適用於 **_程式中有大量類似的 Instance 物件，其物件中又具備很多相似或不變的值或屬性_** ，當程式有這樣特徵，就可藉由 Flyweight Factory 來設計物件的共享，並以此方式來節省程式執行時的記憶體開銷

#### ※ Example - 1

```auto
import random
import string
import sys

class User:
    def __init__ (self, name):
        self.name = name

class User2:
    # 創建一個共享的字串列表容器
    strings = []

    def __init__ (self, full_name):
        def get_or_add(s):
            '''檢查及取用名稱字串中的文字區塊，如不存在則加入字串列表容器中，如存在則返回該文字區塊於名稱字串的位置 Index'''
            if s in self.strings:
                return self.strings.index(s)
            else:
                self.strings.append(s)
                return len(self.strings)-1
        # 將 Initial 的名稱字串分割成文字區塊，並透過 get_or_add() 方法取得文字區塊於名稱字串的位置 Index 的集合
        self.names = [get_or_add(x) for x in full_name.split(' ')]

    def __str__ (self):
        '''從文字區塊於名稱字串的位置 Index 的集合還原回原先 Initial 的名稱字串'''
        return ' '.join([self.strings[x] for x in self.names])

def random_string():
    chars = string.ascii_lowercase
    # 產生 8 個隨機字元的字串
    return ''.join([random.choice(chars) for x in range(8)])

if __name__ == ' __main__':
    # 使用 Flyweight Pattern
    u2 = User2('Jim Jones')
    u3 = User2('Frank Jones')
    print(u2.names) # [0, 1]
    print(u3.names) # [2, 1]
    print(User2.strings) # ['Jim', 'Jones', 'Frank'] 其中 'Jones' 重複，所以後面的就直接共享使用了前面創建的，從而減少儲存物件的記憶位址數量

```

```auto
# Execute Result:
[0, 1]
[2, 1]
['Jim', 'Jones', 'Frank']

```

#### ※ Example - 2

```auto
class FormattedText:
    '''格式化字串，字串中如有要設定成大寫的字元，就用 capitalize function 將要設成大寫的字元
       範圍的字元其大寫狀態的布林值設成 True，而非落於該大寫字元範圍的字元的大寫狀態的布林值就
       吃預設值 False
    '''
    def __init__ (self, plain_text):
        self.plain_text = plain_text
        # 初始化時先將所有大寫的布林值設成 False
        self.caps = [False] * len(plain_text)

    def capitalize(self, start, end):
        '''將指定範圍的字元設成大寫'''
        for i in range(start, end):
            self.caps[i] = True

    def __str__ (self):
        '''複寫 __str__ method，將字串中的字元依照其大寫狀態的布林值來決定是否要設成大寫'''
        result = []
        for i in range(len(self.plain_text)):
            c = self.plain_text[i]
            result.append(c.upper() if self.caps[i] else c)
        return ''.join(result)

class BetterFormattedText:
    def __init__ (self, plain_text):
        self.plain_text = plain_text
        self.formatting = []

    class TextRange:
        '''利用內建的TextRange class來儲存所有字元的狀態，包含是否要設成大寫、是否要設成粗體等等 <-- 共享狀態'''
        def __init__ (self, start, end, capitalize=False, bold=False, italic=False):
            self.end = end
            self.bold = bold
            self.capitalize = capitalize
            self.italic = italic
            self.start = start

        def covers(self, position):
            return self.start <= position <= self.end

    def get_range(self, start, end):
        '''get_range 就相當於 Flyweight Factory，用來產生 TextRange Instance Object，並將其加入到 formatting list 中'''
        range = self.TextRange(start, end)
        self.formatting.append(range)
        return range

    def __str__ (self):
        result = []
        for i in range(len(self.plain_text)):
            c = self.plain_text[i]
            for r in self.formatting:
                if r.covers(i) and r.capitalize:
                    c = c.upper()
            result.append(c)
        return ''.join(result)

if __name__ == ' __main__':
    ft = FormattedText('This is a brave new world')
    ft.capitalize(10, 15)
    print(ft)

    bft = BetterFormattedText('This is a brave new world')
    # Flyweight Pattern 讓架構變得複雜，以這例子而言，User 很難知道要這樣來設定大寫字元的範圍
    bft.get_range(16, 19).capitalize = True
    print(bft)

```

```auto
# Execute Result:
This is a BRAVE new world
This is a brave NEW world

```

#### ※ Example - 3

```auto
import unittest

# 這個範例和老師講的第一個範例相似，只是從字元大寫變成單詞大寫
class Sentence:
    def __init__ (self, plain_text):
        self.words = plain_text.split(' ')
        self.tokens = {}

    def __getitem__ (self, item):
        wt = self.WordToken()
        self.tokens[item] = wt
        return self.tokens[item]

    class WordToken:
        '''Flyweight Factory'''
        def __init__ (self, capitalize=False):
            self.capitalize = capitalize

    def __str__ (self):
        result = []
        for i in range(len(self.words)):
            w = self.words[i]
            if i in self.tokens and self.tokens[i].capitalize:
                w = w.upper()
            result.append(w)
        return ' '.join(result)

# 單元測試
class Evaluate(unittest.TestCase):
    def test_exercise(self):
        s = Sentence('alpha beta gamma')
        s[1].capitalize = True
        self.assertEqual(str(s), 'alpha BETA gamma')

# unittest.main() 如果在 jupyter notebook 中沒有加 argv=[''], exit=False 會出現錯誤 --> https://tinyurl.com/yn3bfrln
unittest.main(argv=[''], verbosity=2, exit=False)

```

```auto
# Execute Result:
test_exercise ( __main__.Evaluate.test_exercise) ... C:\Users\chriswei\AppData\Roaming\Python\Python312\site-packages\jupyter_client\session.py:200: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
  return datetime.utcnow().replace(tzinfo=utc) # noqa
ok

----------------------------------------------------------------------
Ran 1 test in 0.002s

OK

```

#### ※ 享元模式的優缺點及應用場景

##### (1). 優點

1. 減少程式執行時的記憶體消耗，從而提高程式的執行效能
2. 使得程式中的物件，藉由 Flyweight Factory 而變得更具擴展性

##### (2). 缺點

1. 程式中物件被切分得更細，而使得程式變得更複雜，更不 pythonic
2. 在多線程環境上應用困難

##### (3). 應用場景

1. 大量的繁複的文字處裡，例如戶籍資料的處理
2. 圖形應用，例如:大量圖形的元素渲染
3. 遊戲開發，例如:多人扮演遊戲中的角色
