設計模式–享元(蠅量)模式(Flyweight Pattern)
前言:
※ 什麼是享元模式?
- Flyweight Pattern 在中文維基百科是譯為享元模式
- 如程式中有大量的固定類的相似實例(Instance)物件,而該類物件中有不變的及可變的部分,就可把 可變的部分抽離出來,將不變的部分則共享
- Flyweight Pattern 就是透過這樣共享物件的設計,來 節省程式執行時的記憶體開銷
- Flyweight Pattern 適用於 程式中有大量類似的 Instance 物件,其物件中又具備很多相似或不變的值或屬性,當程式有這樣特徵,就可藉由 Flyweight Factory 來設計物件的共享,並以此方式來節省程式執行時的記憶體開銷
※ Example - 1
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' 重複,所以後面的就直接共享使用了前面創建的,從而減少儲存物件的記憶位址數量
# Execute Result:
[0, 1]
[2, 1]
['Jim', 'Jones', 'Frank']
※ Example - 2
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)
# Execute Result:
This is a BRAVE new world
This is a brave NEW world
※ Example - 3
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)
# 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). 優點
- 減少程式執行時的記憶體消耗,從而提高程式的執行效能
- 使得程式中的物件,藉由 Flyweight Factory 而變得更具擴展性
(2). 缺點
- 程式中物件被切分得更細,而使得程式變得更複雜,更不 pythonic
- 在多線程環境上應用困難
(3). 應用場景
- 大量的繁複的文字處裡,例如戶籍資料的處理
- 圖形應用,例如:大量圖形的元素渲染
- 遊戲開發,例如:多人扮演遊戲中的角色