![見出し画像](https://assets.st-note.com/production/uploads/images/51185796/rectangle_large_type_2_776136b8085bb40081ca6bec2ad18eff.png?width=1200)
DP.02:オブジェクトの生成と利用を分離する。その2- AbstractFactory -【Python】
【1】Abstract Factory パターン概要
ざっくりいうと、Abstract FactoryはFactory Methodよりももう少し色々とカスタマイズできるようにした書き方。
(具体的にはFactoryオブジェクトとして、関数ではなくクラスオブジェクトを使うなど)
厳密な違いを知りたい人は、専門家をみつけてきいてみよう。ここでは以下のような観点で何となく呼び方が違う、位の軽い気持ちとらえておいて先に進もう。(あくまでもこんなプログラムの書き方がある、というバリエーションの一つとしてみよう)
■生成するインスタンスの数の観点
・ Factory Method → 1つのインスタンスを返す
・ Abstract Factory → 複数のインスタンスを生成してそれぞれ返すことができる(複数のインスタンスを作って最終的に1つのオブジェクトを作る)
■メソッドかクラスかの観点の観点
・Factory Method → メソッドを使う
・Abstract Factory → クラスを使う
【2】例:UIのウィジェット(部品)生成
サンプルとして「デスクトップアプリのUIウェジェット」をあげる。
「ウィンドウが立ち上がるデスクトップアプリ」は「ウィンドウ」だけでなく「ボタン」「テキストエディット」などなど、複数のウィジェットで構成される。
■イメージ図:Qt Designerより
![画像1](https://assets.st-note.com/production/uploads/images/51189208/picture_pc_94933c5930772500be4e76e3b9f1a47d.png?width=1200)
今回は「QTかGtkかを示す入力を受け付ける。受け付けた値を踏まえて、オブジェクトを生成するのに適したFactoryオブジェクト、をリターンする」という作りにする
※作成するAbstract Factoryの使い方イメージ
if __name__ == '__main__':
# このオブジェクト生成部分を分離してもいいが省略
word = input("select number [1]:gtk or [2]:qt >> ")
if word == '1':
ui = GtkUIFactory()
elif word == '2':
ui = QtUIFactory()
else:
print("UI create failed ")
sys.exit(0)
# Factoryで作られたオブジェクトが違っていても(どちらのFactoryオブジェクトでも)
# 同じメソッド名で複数のインスタンスを生成できる
text_edit = ui.getTextEdit()
button = ui.getButton()
main_window = ui.getMainWindow()
# Factoryから生成したインスタンスを確認する
print(f'{text_edit.getToolkit()}, {text_edit.getType()}')
print(f'{button.getToolkit()}, {button.getType()}')
print(f'{main_window.getToolkit()}, {main_window.getType()}')
(1)Factoryがリターンするオブジェクトを用意
###### Factoryからコールされる各種インスタンス ################
# ベースクラス
class Window:
def __init__(self, toolkit, purpose):
self.toolkit = toolkit
self.purpose = purpose
def getToolkit(self):
return self.toolkit
def getType(self):
return self.purpose
#### Gtk関連のウィジェット ####
class GtkTextEdit(Window):
def __init__(self):
super().__init__("Gtk","TextEdit")
class GtkButton(Window):
def __init__(self):
super().__init__("Gtk","Button")
class GtkMainWindow(Window):
def __init__(self):
super().__init__("Gtk","MainWindow")
#### Qt関連のウィジェット ####
class QtTextEdit(Window):
def __init__(self):
super().__init__("Qt","TextEdit")
class QtButton(Window):
def __init__(self):
super().__init__("Qt","Button")
class QtMainWindow(Window):
def __init__(self):
super().__init__("Qt","MainWindow")
(2)Factoryを作る
#共通項目に実装忘れがないようにabc.ABCMetaを仕込む
class UIFactory(metaclass = abc.ABCMeta):
@abc.abstractmethod
def getTextEdit(self):
pass
@abc.abstractmethod
def getButton(self):
pass
@abc.abstractmethod
def getMainWindow(self):
pass
#各種Factory本体
# GtkUI Factory
class GtkUIFactory(UIFactory):
def getTextEdit(self):
return GtkTextEdit()
def getButton(self):
return GtkButton()
def getMainWindow(self):
return GtkMainWindow()
# QtUI Factory
class QtUIFactory(UIFactory):
def getTextEdit(self):
return QtTextEdit()
def getButton(self):
return QtButton()
def getMainWindow(self):
return QtMainWindow()
▲ こんな感じでAbstract Factory内に生成できるウェジェットを返すメソッドを複数持たせておく。こうすると『GtkUIFactory, QTUIFactoryともにウェジェットとして生成できるのは「TextEdit」「Button」「MainWindow」だけとなる。他のウェジェットを生成させるコードを書こうとしてもエラーを出してはじくことができる』
※コードのイメージ
ui = QtUIFactory()
radio_box = ui.getRadioBox() # getRadioBoxを定義していないのでエラーでコードミスに気が付ける
# 関数で定義された場合
def getMyRadioBox():
... ラジオボックスウェジェットを返す ...
radio_box = getMyRadioBox() # 本来はラジオボックスは作らせたくないのにコードが通ってしまう
なお、abc(abstract base classes)については以下参照。
くどくどと書いたが、ようは
・Factoryクラスオブジェクトを作る
・オブジェクト全体のパーツとなるインスタンスを返すメソッドを作る
・使うときはFactoryクラスを経由することでコーディングミスを抑えやすくなる
っていう感じ。
【3】全体コード
import abc
import sys
###### Factoryからコールされる各種インスタンス ################
# ベースクラス
class Window:
def __init__(self, toolkit, purpose):
self.toolkit = toolkit
self.purpose = purpose
def getToolkit(self):
return self.toolkit
def getType(self):
return self.purpose
#### Gtk関連のウィジェット
class GtkTextEdit(Window):
def __init__(self):
super().__init__("Gtk","TextEdit")
class GtkButton(Window):
def __init__(self):
super().__init__("Gtk","Button")
class GtkMainWindow(Window):
def __init__(self):
super().__init__("Gtk","MainWindow")
#### Qt関連のウィジェット
class QtTextEdit(Window):
def __init__(self):
super().__init__("Qt","TextEdit")
class QtButton(Window):
def __init__(self):
super().__init__("Qt","Button")
class QtMainWindow(Window):
def __init__(self):
super().__init__("Qt","MainWindow")
######### 以下Factory #########
#共通項目に実装忘れがないようにabc.ABCMetaを仕込む
class UIFactory(metaclass = abc.ABCMeta):
@abc.abstractmethod
def getTextEdit(self):
pass
@abc.abstractmethod
def getButton(self):
pass
@abc.abstractmethod
def getMainWindow(self):
pass
#各種Factory本体
# GtkUI Factory
class GtkUIFactory(UIFactory):
def getTextEdit(self):
return GtkTextEdit()
def getButton(self):
return GtkButton()
def getMainWindow(self):
return GtkMainWindow()
# QtUI Factory
class QtUIFactory(UIFactory):
def getTextEdit(self):
return QtTextEdit()
def getButton(self):
return QtButton()
def getMainWindow(self):
return QtMainWindow()
#### 使用例 ####
if __name__ == '__main__':
# このオブジェクト生成部分を分離してもいいが省略
word = input("select number [1]:gtk or [2]:qt >> ")
if word == '1':
ui = GtkUIFactory()
elif word == '2':
ui = QtUIFactory()
else:
print("UI create failed ")
sys.exit(0)
# Factoryで作られたオブジェクトが違っていても
# 同じメソッド名で複数のインスタンスを生成できる
text_edit = ui.getTextEdit()
button = ui.getButton()
main_window = ui.getMainWindow()
# Factoryから生成したインスタンスを確認する
print(f'{text_edit.getToolkit()}, {text_edit.getType()}')
print(f'{button.getToolkit()}, {button.getType()}')
print(f'{main_window.getToolkit()}, {main_window.getType()}')
#実行結果
select number [1]:gtk or [2]:qt >> 1
Gtk, TextEdit
Gtk, Button
Gtk, MainWindow
select number [1]:gtk or [2]:qt >> 2
Qt, TextEdit
Qt, Button
Qt, MainWindow
もっと応援したいなと思っていただけた場合、よろしければサポートをおねがいします。いただいたサポートは活動費に使わせていただきます。