オブジェクト指向について復習していくと、行き着く先はデザインパターン
という事でデザパタについても掘り下げていきます。
ソフトウェア開発におけるデザインパターンまたは設計パターン(英: design pattern)とは、過去のソフトウェア設計者が発見し編み出した設計ノウハウを蓄積し、名前をつけ、再利用しやすいように特定の規約に従ってカタログ化したものである。
Wikipediaより
つまり大体の設計パターンは先人が発見したノウハウのいずれかのパターンに当てはまるので、そのコンセプトに沿って開発を進めるための解決策がもう既に用意されている
書籍『オブジェクト指向における再利用のためのデザインパターン』において、GoF (Gang of Four) と呼ばれる4人の共著者は、デザインパターンという用語を初めてソフトウェア開発に導入した。
Wikipediaより
GoFは、エーリヒ・ガンマ、リチャード・ヘルム、ラルフ・ジョンソン、ジョン・ブリシディースの4人である。
彼らは、その書籍の中で23種類のパターンを取り上げた。
その先人となる書籍の著者4人をGang of Fourと総称してGoFデザインパターンと呼ばれている
パターンカテゴリ | 各パターン名 |
生成に関するパターン | Factory Method, Abstract Factory, Builder, Prototype, Singleton |
構造に関するパターン | Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy |
振る舞いに関するパターン | Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Mement, Observer, State, Strategy, Visitor |
上記3カテゴリの23種類のパターンがメインだが、近年のマイクロサービス向けのマルチスレッドプログラミングに関するパターンがさらに14種類もあるがとりあえず古くから伝わる23種を軸に今回は生成に関するパターンを掘り下げる
# 週末コースクラス
class DEV:
def price(self):
return 300000
def schedule(self):
return "土日"
def __str__(self):
return "DEV"
# 平日コースクラス
class LAB:
def price(self):
return 500000
def schedule(self):
return "月〜金"
def __str__(self):
return "LAB"
if __name__ == "__main__":
# 各コースのクラスをインスタンス化
dev = DEV()
lav = LAB()
print(f'{dev}コースの授業料は{dev.price()}円で、授業は毎週{dev.schedule()}に行われます')
# --> DEVコースの授業料は300000円で、授業は毎週土日に行われます
print(f'{lav}コースの授業料は{lav.price()}円で、授業は毎週{lav.schedule()}に行われます')
# --> LABコースの授業料は500000円で、授業は毎週火〜金に行われます
# 各コース情報の出力用クラス
class Factory:
def __init__(self, courses_factory = None):
self.course_factory = courses_factory
def show_course(self):
course = self.course_factory()
print(f'コース名: {course}')
print(f'授業料:¥{course.price()}')
print(f'授業日程: {course.schedule()}')
print(f'--------------------------------------')
class DEV:
def price(self):
return 300000
def schedule(self):
return "土日"
def __str__(self):
return "DEV"
class LAB:
def price(self):
return 500000
def schedule(self):
return "火〜金"
def __str__(self):
return "LAB"
class BIZ:
def price(self):
return 150000
def schedule(self):
return "水"
def __str__(self):
return 'BIZ'
if __name__ == "__main__":
# 各コースのリスト
courses = [DEV, LAB, BIZ]
# 配列に含んだコースの情報を表示
for i in courses:
Factory(i).show_course()
コース名: DEV
授業料: ¥300000
授業日程: 毎週 土日
--------------------------------------
コース名: LAB
授業料: ¥500000
授業日程: 毎週 月〜金
--------------------------------------
コース名: BIZ
授業料: ¥150000
授業日程: 毎週 水
--------------------------------------
クラス種類 | ソファー | テーブル | 椅子 |
モデル | ・ソファベッド ・リクライニング ・フロアソファー | ・ダイニング ・こたつ / 座卓 ・オフィス | ・デッキ・ダイニング ・ロッキング ・オフィス・ワーク |
パターン | ・人数 ・肘掛け 有 / 無 ・脚 有 / 無 | ・四角 / 丸形 ・脚の本数 ・折りたたみ式 | ・肘掛け 有 / 無 ・背もたれ 有/ 無 ・キャスター 有 / 無 |
バリエーション | ・カバー色 / 柄 ・カバー生地 | ・素材(木材 / 鉄) ・色 / 柄 | ・カバー色 ・素材 / 生地 |
# 抽象ファクトリー
class AbstractFactory(ABC):
@abstractmethod
def create_sofa(self) -> AbstractProductSofa: # ソファー
pass
@abstractmethod
def create_chiar(self) -> AbstractProductChiar: # 椅子
pass
@abstractmethod
def create_table(self) -> AbstractProductTable: # テーブル
pass
# 生産ライン1の具象クラス
class ConcreteFactory1(AbstractFactory):
def create_sofa(self) -> AbstractProductSofa: # ソファー
return ConcreteProductSofa1()
def create_chiar(self) -> AbstractProductChiar: # 椅子
return ConcreteProductChiar1()
def create_table(self) -> AbstractProductTable: # テーブル
return ConcreteProductTable1()
# 生産ライン2の具象クラス
class ConcreteFactory2(AbstractFactory):
def create_sofa(self) -> AbstractProductSofa: # ソファー
return ConcreteProductSofa2()
def create_chiar(self) -> AbstractProductChiar: # 椅子
pass
def create_table(self) -> AbstractProductTable: # テーブル
pass
# ソファー素材の具象クラス
class ConcreteProductSofa1(AbstractProductMaterial):
def valiation_material_cotton(self) -> str:
return "素材:綿"
def valiation_material_leather(self) -> str:
return "素材:革"
# ソファーカラーの具象クラス
class ConcreteProductSofa2(AbstractProductColor):
def valiation_color_black(self) -> str:
return "カラー:ブラック"
def valiation_color_blue(self) -> str:
return "カラー:ブルー"
def valiation_color_red(self) -> str:
return "カラー:レッド"
# ソファー生地の抽象クラス
class AbstractProductMaterial(ABC):
@abstractmethod
def valiation_material_cotton(self) -> str:
pass
@abstractmethod
def valiation_material_leather(self) -> str:
pass
# ソファーカラーの抽象クラス
class AbstractProductColor(ABC):
@abstractmethod
def valiation_color_black(self) -> str:
pass
@abstractmethod
def valiation_color_blue(self) -> str:
pass
# 生産ラインの設定
def factory_setting(factory: AbstractFactory, item) -> None:
# オーダーによって生産ラインのインターフェースを分岐
if "ソファー" in item:
order = factory.create_sofa()
elif "テーブル" in item:
order = factory.create_table()
elif "椅子" in item:
order = factory.create_chiar()
return order
if __name__ == "__main__":
# 生産ライン1&2を「ソファー」の生産で稼働
line_1 = factory_setting(ConcreteFactory1(), "ソファー")
line_2 = factory_setting(ConcreteFactory2(), "ソファー")
# ライン1は「綿素材」で稼働
print(line_1.valiation_material_cotton()) # --> 素材:綿
# ライン2は「カバー黒」で稼働
print(line_2.valiation_color_black()) # --> カラー:ブラック
基本クラスとそれを拡張するサブクラスにインスタンスの作成メソッドが含まれる、またはその類が含まれそうなこう場合は、ファクトリメソッド
実行コマンドのオプションによって、コンテキストの出力パターンを振り分ける
$ python Main.py plain
======================
Greeting
*** From the morning to the afternoon ***
- Good morning
- Hello
*** In the evening ***
- Good evening
- Good night
- Good bye
======================
$ python Main.py html
[Greeting.html] was created.
「依頼」「監督」「内装」「建築」といった個別の構造を持ったインスタンスを段階的に分離して組み上げることで複雑なモノを構築させる
実行コマンドの引数によってテキスト出力モード(plain) または HTML出力モード(html)によって実行パターンを分ける
import sys
from builder.director import Director
from builder.textbuilder.text_builder import TextBuilder
from builder.htmlbuilder.html_builder import HTMLBuilder
def startMain(opt):
if opt == "plain":
builder = TextBuilder()
director = Director(builder)
director.construct()
result = builder.getResult()
print(result)
elif opt == "html":
builder = HTMLBuilder()
director = Director(builder)
director.construct()
result = builder.getResult()
print("[" + result + "]" + " was created.")
if __name__ == "__main__":
startMain(sys.argv[1])
インスタンスを作成するための共通のインタフェース
from abc import ABCMeta, abstractmethod
class Builder(metaclass=ABCMeta):
@abstractmethod
def makeTitle(self, title):
pass
@abstractmethod
def makeString(self, str):
pass
@abstractmethod
def makeItems(self, items):
pass
@abstractmethod
def close(self):
pass
Builder
)のインタフェースを使って、各インスタンスを生成
class Director(object):
def __init__(self, builder):
self.__builder = builder
def construct(self):
self.__builder.makeTitle("Greeting")
self.__builder.makeString("From the morning to the afternoon")
self.__builder.makeItems(["Good morning", "Hello"])
self.__builder.makeString("In the evening")
self.__builder.makeItems(["Good evening", "Good night", "Good bye"])
self.__builder.close()
from builder.builder import Builder
class TextBuilder(Builder):
def __init__(self):
self.buffer = []
def makeTitle(self, title):
self.buffer.append("======================\n")
self.buffer.append(title + "\n")
self.buffer.append("\n")
def makeString(self, str):
self.buffer.append("*** " + str + " ***" + "\n")
def makeItems(self, items):
for i in items:
self.buffer.append("- " + i + "\n")
def close(self):
self.buffer.append("======================\n")
def getResult(self):
return ''.join(self.buffer)
「タイトル」にはhtmlタグとbodyタグとtitleタグ、「文末」には各閉じタグで囲むような構成
from builder.builder import Builder
class HTMLBuilder(Builder):
def __init__(self):
self.buffer = []
self.filename = ""
self.f = None
self.makeTitleCalled = False
def makeTitle(self, title):
self.filename = title+".html"
self.f = open(self.filename, "w")
self.f.write("<html><head><title>"+title+"</title></head></html>")
self.f.write("<h1>"+title+"</h1>")
self.makeTitleCalled = True
def makeString(self, str):
if not self.makeTitleCalled:
raise RuntimeError
self.f.write("<p>"+str+"</p>")
def makeItems(self, items):
if not self.makeTitleCalled:
raise RuntimeError
self.f.write("<ul>")
for i in items:
self.f.write("<li>"+i+"</li>")
self.f.write("</ul>")
def close(self):
if not self.makeTitleCalled:
raise RuntimeError
self.f.write("</body></html>")
self.f.close()
def getResult(self):
return self.filename
文字列にデコレーション加工を加えるインスタンスを複製して「Hello World」をデコる
def startMain(managerObject):
upen = UnderlinePen("-")
mbox = MessageBox("*")
sbox = MessageBox("/")
managerObject.register("strong", upen) # アンダーバー加工を登録
managerObject.register("warning", mbox) # アスタリスク加工を登録
managerObject.register("slash", sbox) # スラッシュ加工を登録
p1 = managerObject.create("strong") # アンダーバー加工インスタンスを複製
p2 = managerObject.create("warning") # アスタリスク加工インスタンスを複製
p3 = managerObject.create("slash") # スラッシュ加工インスタンスを複製
p1.use("Hello World")
p2.use("Hello World")
p3.use("Hello World")
if __name__ == "__main__":
startMain(Manager())
"Hello World"
---------------------
***************
* Hello World *
***************
///////////////
/ Hello World /
///////////////
from abc import ABCMeta, abstractmethod
import copy
class Prototype(metaclass=ABCMeta):
@abstractmethod
def use(self, s):
pass
@abstractmethod
def createClone(self):
pass
# アンダーライン加工クラス
class UnderlinePen(Prototype):
def __init__(self, ulchar):
self.__ulchar = ulchar
def use(self, s):
length = len(s)
line = self.__ulchar * (length + 10)
print("\"{0}\"".format(s))
print("{0}\n".format(line))
def createClone(self):
clone = copy.deepcopy(self)
return clone
# デコレーション加工クラス
class MessageBox(Prototype):
def __init__(self, decochar):
self.__decochar = decochar
def use(self, s):
length = len(s)
line = self.__decochar * (length + 4)
print("{0}".format(line))
print("{0} {1} {2}".format(self.__decochar, s, self.__decochar))
print("{0}\n".format(line))
def createClone(self):
clone = copy.deepcopy(self)
return clone
Prototypeからインスタンスをコピーするメソッドを利用して、実際に新しいインスタンスを作成
class Manager(object):
def __init__(self):
self.__showcase = {}
def register(self, name, proto):
self.__showcase[name] = proto
def create(self, protoname):
p = self.__showcase[protoname]
return p.createClone()
from threading import Lock, Thread
class SingletonMeta(type):
_instances = {}
_lock: Lock = Lock()
def __call__(cls, *args, **kwargs):
with cls._lock:
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]
class Singleton(metaclass=SingletonMeta):
value: str = None
def __init__(self, value: str) -> None:
self.value = value
def some_business_logic(self):
pass
def test_singleton(value: str) -> None:
singleton = Singleton(value)
print(singleton.value)
if __name__ == "__main__":
# 初回発行
process1 = Thread(target=test_singleton, args=("FOO",))
process1.start()
# --> FOO
# 再利用
process2 = Thread(target=test_singleton, args=("BAR",))
process2.start()
# --> FOO
「シングルトンパターンの誘惑に負けない」によれば結構なネガキャン
パターンの多くは、もはやあたり前に存在するという印象で意識しなくても実は使っているというケースも多い(自分の場合はビルダーパターンは好んでよくやっている)
初期段階は実装パターンがぼんやりしたまま開発に入ることも多いので、ドヤ顔で「今回はこのパターンで行こう!」みたいな事はめったに無いと思われる。
開発過程で不安になってきたときに「それぞれのパターンの志向に沿っているか?」や「変更の必要がでてきてるんじゃないか?」などの確認時の際に言語化しやすくはなりそう