見出し画像

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

今回は「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


もっと応援したいなと思っていただけた場合、よろしければサポートをおねがいします。いただいたサポートは活動費に使わせていただきます。