見出し画像

DP.10:複雑な裏側の処理にはシンプルなフロントエンドIFを設置する。- Facade(ファサード) -【Python】

【1】Facadeパターン概要

ざっくりいうと、「プログラム実行(アプリ起動)にあたり、裏側では複数のオブジェクトメソッドをあれこれコールする必要があるが、コード上は1つのオブジェクトからコールする」ような書き方。
 (※ようは高水準のAPI・インターフェースを用意する書き方)

例えば、

・OSが様々なプロセスブートする(マイクロカーネル設計)
 → ファイルシステム関連のプロセス、ネットワーク関連のプロセス、ユーザアカウント関連のプロセス、、、などなど。システムとして必要となる様々なプロセスをOSからコールさせる。
・BIOSやブートローダ
・車のエンジンをかけるときの処理(車のキーで起動する処理)

【2】例:マイクロカーネル風システム

OSクラスに「ファイルサービス(ファイル操作関連)クラス」や「プロセスサービス(その他の処理関連)」を実行させることを考えてみる。

■Facade未適用版

from enum import Enum

State = Enum('State', 'new running sleeping restart zombie')


###### ファイルサービスクラス
class FileServer:
   

   def __init__(self):
       self.name = 'FileServer'
       self.state = State.new

   
   def boot(self):
       print(f'booting the {self}')
       self.state = State.running
   

   def kill(self, restart=True):
       print(f'killing {self}')
       self.state = State.restart if restart else State.zombie
   

   def create_file(self, user, name, permissions):
       print(f"creating file '{name}', user '{user}' with permisions '{permissions}' ")

###### プロセスサービスクラス
class ProcessServer:
   

   def __init__(self):
       self.name = 'ProcessServer'
       self.state = State.new
   

   def boot(self):
       print(f'booting the {self}')
       self.state = State.running
   
   
   def kill(self, restart=True):
       print(f'Killing {self}')
       self.state = State.restart if restart else State.zombie


   def create_process(self, user, name):
       print(f"creating process '{name}', for user '{user}'")
   


## 動作テスト
def main():

   fs = FileServer()
   ps = ProcessServer()

   fs.boot()
   ps.boot()

   print('------')

   #各サービスに対するメソッドコール
   fs.create_file('fz5050','hello.txt','-rw-r-r')
   ps.create_process('fz9999','ls /tmp') 


if __name__ == '__main__':
   main()

#実行結果例
booting the <__main__.FileServer object at 0x000001AFBD358FD0>
booting the <__main__.ProcessServer object at 0x000001AFBD358D30>
------
creating file 'hello.txt', user 'fz5050' with permisions '-rw-r-r'
creating process 'ls /tmp', for user 'fz9999'

画像1

▲システムとしてすべて起動したいオブジェクトではあるが、1つ1つ記述してオブジェクト生成、メソッドコールをしている。

これをFacadeパターンで書き直してみる。

【3】Facadeパターン用オブジェクトの用意

今回は「OperatingSystemオブジェクト」を用意して、このオブジェクト経由で各オブジェクトを操作する。

####### facade(ファサード)パターンのフロントエンドになるクラス
class OperatingSystem:
   def __init__(self):
       self.fs = FileServer()
       self.ps = ProcessServer()
   
   # 各クラスのbootをまとめて起動
   def start(self):
       [ i.boot() for i in (self.fs, self.ps)]
   

   # FileServerオブジェクトの処理をコール
   def create_file(self, user, name, permissions):
       return self.fs.create_file(user, name, permissions)
   
   # ProcessServerオブジェクトの処理をコール
   def create_process(self,user, name):
       return self.ps.create_process(user,name)

■使用例

## 動作テスト
def main():

   os = OperatingSystem()
   os.start() # bootメソッドをコール

   print('------')

   #各サービスに対するメソッドコール
   os.create_file('fz5050','hello.txt','-rw-r-r')
   os.create_process('fz9999','ls /tmp') 

#実行結果例
booting the <__main__.FileServer object at 0x000002339A4F8FA0>
booting the <__main__.ProcessServer object at 0x000002339A4F4970>
------
creating file 'hello.txt', user 'fz5050' with permisions '-rw-r-r'
creating process 'ls /tmp', for user 'fz9999'

画像2

▲OperatingSystemオブジェクトを経由させることで、記述量が少なくなり、必要となる関連オブジェクトコールもまとめられる。

■追加修正ポイント
今回は「手動で、共通している処理のメソッド名は統一」して、フロントエンド側の記述が少し楽になるようにした。

画像3

この部分は「abc(abstract base classe)」を使うことで、メソッド名の統一を強制させることもできる。

from abc import ABCMeta, abstractmethod

... ...

class Server(metaclass = ABCMeta):

   def __init__(self):
       pass

   def __str__(self):
       return self.name
   
   @abstractmethod
   def boot(self):
       pass

   @abstractmethod
   def kill(self, restart=True):
       pass
       
       
       
... ...


###### ファイルサービスクラス
class FileServer(Server):

   ...(略)...


###### プロセスサービスクラス
class ProcessServer(Server):
   
   ...(略)...

【4】全体コード

from enum import Enum
from abc import ABCMeta, abstractmethod

State = Enum('State', 'new running sleeping restart zombie')


###### 色々なServerクラスのベースとなる抽象クラス
###### facadeパターンのために最低限必要となるメソッドを決めておく
class Server(metaclass = ABCMeta):
   

   def __init__(self):
       pass

   def __str__(self):
       return self.name
   

   @abstractmethod
   def boot(self):
       pass

   @abstractmethod
   def kill(self, restart=True):
       pass


###### ファイルサービスクラス
class FileServer(Server):
   

   def __init__(self):
       self.name = 'FileServer'
       self.state = State.new

   
   def boot(self):
       print(f'booting the {self}')
       self.state = State.running
   

   def kill(self, restart=True):
       print(f'killing {self}')
       self.state = State.restart if restart else State.zombie
   

   def create_file(self, user, name, permissions):
       print(f"creating file '{name}', user '{user}' with permisions '{permissions}' ")

###### プロセスサービスクラス
class ProcessServer(Server):
   

   def __init__(self):
       self.name = 'ProcessServer'
       self.state = State.new
   

   def boot(self):
       print(f'booting the {self}')
       self.state = State.running
   
   
   def kill(self, restart=True):
       print(f'Killing {self}')
       self.state = State.restart if restart else State.zombie


   def create_process(self, user, name):
       print(f"creating process '{name}', for user '{user}'")
   

####### facade(ファサード)パターンのフロントエンドになるクラス
class OperatingSystem:
   def __init__(self):
       self.fs = FileServer()
       self.ps = ProcessServer()
   
   # 各クラスのbootをまとめて起動
   def start(self):

       [ i.boot() for i in (self.fs, self.ps)]
   
   # FileServerオブジェクトの処理をコール
   def create_file(self, user, name, permissions):
       return self.fs.create_file(user, name, permissions)
   
   # ProcessServerオブジェクトの処理をコール
   def create_process(self,user, name):
       return self.ps.create_process(user,name)


## 動作テスト
def main():

   os = OperatingSystem()

   os.start() # bootメソッドをコール


   print('------')

   #各サービスに対するメソッドコール
   os.create_file('fz5050','hello.txt','-rw-r-r')
   os.create_process('fz9999','ls /tmp') 


if __name__ == '__main__':
   main()

#実行結果例
booting the FileServer
booting the ProcessServer
------
creating file 'hello.txt', user 'fz5050' with permisions '-rw-r-r'
creating process 'ls /tmp', for user 'fz9999'

複雑にオブジェクト同士が紐づいているシステムを使用するときに、facade(ファサード)パターンを適用すれば、シンプルなインターフェースをクライアントコード側に提供できる。また、システムの複雑さを気にする負担が軽減するかもしれない。

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