Pythonでデザインパターン 拾参ノ型「Visitor」
今回はPythonの勉強しながらデザインパターンのVisitorをやってみました。
他のパターンにつきましては、以下の目次記事をご参照ください。
Visitorとは
直訳すると「訪問者」ですね。
wikiには「 アルゴリズムをオブジェクトの構造から分離するためのデザインパターンである。分離による実用的な結果として、既存のオブジェクトに対する新たな操作を構造を変更せずに追加することができる。」とあります。
wikiの説明から解釈するに、
既存のオブジェクトからアルゴリズムを分離。
これで既存のオブジェクトはデータ構造だけを持ったオブジェクトになる分離したアルゴリズムは別のオブジェクトに。
データ構造の変更をさせずにこのオブジェクトに新しいアルゴリズムを追加していける
・・・みたいな感じでしょうか?難しいですね(;^ω^)
Visitorについて考えてみる
例えば"会社"に"Aさん"が出勤する構図を考えてみます。
(いつものごとくわかりにくい例ですがお許しください。。)
そこで以下のように"会社"クラスと"Aさん"クラスを用意してみました。
"会社"クラスにはAさんを受け入れるaccept()メソッドがあります。
"Aさん"クラスには会社を訪問するvisit()メソッドがあります。
class 会社: def __init__(self): self.__name = "会社名" def getName(self): return self.__name def accept(self, Aさん): # Aさんを受け入れる print(Aさん.gatName(), "が", self.__name, "に出勤しました") class Aさん: def __init__(self): self.__name = "Aさん名" def getName(self): return self.__name def visit(self, 会社): # 会社を訪れる print(self.__name, "が", 会社.getName(), "に出勤しました") # 会社がAさんを受け入れる場合 会社A().accept(Aさん()) # Aさんが会社を訪問する場合 Aさん().visit(会社A()) # いずれも結果は同じ
見ていただければわかると思いますがaccept()もvisit()も意味合いは違えどやりたいことは同じです。
なのでなんとかaccept()とvisit()の処理をなんとか一つにまとめたいですよね
これを実現するのがVisitorパターンかな?
やってみます。
まず訪問者を指すVisitorクラスを用意します。
class Visitor(metaclass=ABCMeta): @abstractmethod def visit(self, company): pass
次に訪問先を指すAcceptorクラスを用意します。
class Acceptor(metaclass=ABCMeta): @abstractmethod def accept(self, visitor): pass
Visitorクラスを使って訪問者を指すVisitorAクラスを実装します。
visit()メソッドはAcceptorクラスの引数を持ち、訪問先の名前を取得しています。
class VisitorA(Visitor): def __init__(self): self.__name = "Aさん" def visit(self, company: Acceptor) -> None: print(self.__name, "が", company.getName(), "を訪れました。")
Acceptorクラスを使って訪問先を指すCompanyクラスを実装します。
accept()メソッドはVisitorクラスの引数を持ち、Visitorクラスのvisit()を呼び出しています。
class Company(Acceptor): def __init__(self): self.__name = "Company" def getName(self) -> str: return self.__name def accept(self, visitor: Visitor) -> None: visitor.visit(self)
これのクラスを使用すると以下のようになります。
accept()メソッドを使用しても、visit()メソッドを使用しても結果は同じです。
しかし実装は1カ所にまとめられるようになりました!
> Company().accept(VisitorA()) Aさん が Company を訪れました。 > VisitorA().visit(Company()) Aさん が Company を訪れました。
この例ですとaccept()にあった実装をvisit()に追い出しています。
これによりCompanyクラスは会社名というデータ構造だけになっていますね。
サンプルコード
以下、サンプルコードです。
今度は訪問者としてAさんとBさん、訪問先として会社とデパートを追加してみました。
# coding: utf-8 from abc import ABCMeta, abstractmethod # 訪問者クラス class Visitor(metaclass=ABCMeta): @abstractmethod def visit(self, company): pass @abstractmethod def visit(self, depart): pass # 訪問先クラス class Acceptor(metaclass=ABCMeta): @abstractmethod def accept(self, visitor): pass # 会社クラス class Company(Acceptor): def __init__(self): self.__name = "Company" def getName(self) -> str: return self.__name def accept(self, visitor) -> None: visitor.visit(self) # デパートクラス class Depart(Acceptor): def __init__(self): self.__name = "Depart" def getName(self) -> str: return self.__name def accept(self, visitor) -> None: visitor.visit(self) # 訪問者Aクラス class VisitorA(Visitor): def __init__(self): self.__name = "Aさん" def visit(self, company: Company) -> None: print(self.__name, "が", company.getName(), "を訪れました。") def visit(self, depart: Depart) -> None: print(self.__name, "が", depart.getName(), "を訪れました。") # 訪問者Bクラス class VisitorB(Visitor): def __init__(self): self.__name = "Bさん" def visit(self, company: Company) -> None: print(self.__name, "が", company.getName(), "を訪れました。") def visit(self, depart: Depart) -> None: print(self.__name, "が", depart.getName(), "を訪れました。") if __name__ == "__main__": Company().accept(VisitorA()) Company().accept(VisitorB()) Depart().accept(VisitorA()) Depart().accept(VisitorB()) print("") VisitorA().visit(Company()) VisitorA().visit(Depart()) VisitorB().visit(Company()) VisitorB().visit(Depart())
CompanyやDepartにAさんやBさんが来たとしても、AさんやBさんがCompanyやDepartに行ったとしても結果は同じですね。
$ python Visitor.py Aさん が Company を訪れました。 Bさん が Company を訪れました。 Aさん が Depart を訪れました。 Bさん が Depart を訪れました。 Aさん が Company を訪れました。 Aさん が Depart を訪れました。 Bさん が Company を訪れました。 Bさん が Depart を訪れました。
Pythonでデザインパターン 拾弐ノ型「Decorator」
今回はPythonの勉強しながらデザインパターンのDecoratorをやってみました。
他のパターンにつきましては、以下の目次記事をご参照ください。
Decoratorとは
直訳すると「飾り付けをする人」ですかね。
wikiには「既存のオブジェクトに新しい機能や振る舞いを動的に追加することを可能にする」とあります。
直訳やwikiから推測するにいまあるオブジェクトに飾り付けを行うためのパターンのようです。
゚+.(◕ฺ ω◕ฺ )゚+.
Decoratorについて考えてみる
今回は顔文字をデコってみたいと思います(・`ω´・)b
まずはデコレーションするベースクラスComponentを用意します。
class Component(metaclass=ABCMeta): @abstractmethod def getTxt(self): pass
そしてComponentを使って顔文字クラスEmoticonを作ります。
class Emoticon(Component): def getTxt(self): return "(^ω^)"
Emoticonクラスだけ使うとこんな感じです。
派手さが足りませんね(・´ω`・)
> print(Emoticon().getTxt())
(^ω^)
次にComponentクラスをデコレートするためのベースクラスDecoratorを用意します。
DecoratorクラスにはComponentクラスを継承させます。
class Decorator(Component): def __init__(self, component: Component) : self._component = component
そしてDecoratorクラスをもとにデコレートするクラスを準備します。
class BanzaiDecorator(Decorator): def __init__(self, component: Component) : super().__init__(component) def getTxt(self): return "ヽ" + self._component.getTxt() + "/"
あとは使うだけですね!
EmoticonクラスをBanzaiDecoratorクラスでラッピングしてデコレートしていますヽ(^ω^)/
> print(BanzaiDecorator(Emoticon()).getTxt())
ヽ(^ω^)/
こんな感じでオブジェクトをラップして機能をデコレートしていくパターンになります。
サンプルコード
顔文字に万歳、Yeah、きらきらをデコレーションしてみます。
# coding: utf-8 from abc import ABCMeta, abstractmethod # コンポーネントクラス class Component(metaclass=ABCMeta): @abstractmethod def getTxt(self): pass # デコレータクラス class Decorator(Component): def __init__(self, component: Component) : self._component = component # 顔文字クラス class Emoticon(Component): def __init__(self, text: str): self._text = text def getTxt(self): return self._text # 万歳デコレータクラス class BanzaiDecorator(Decorator): def __init__(self, component: Component) : super().__init__(component) def getTxt(self): return "ヽ" + self._component.getTxt() + "/" # イエーイデコレータクラス class YeahDecorator(Decorator): def __init__(self, component: Component) : super().__init__(component) def getTxt(self): return "イエェェェェ" + self._component.getTxt() + "ェェェェイィ" # きらきらデコレータクラス class GlitterDecorator(Decorator): def __init__(self, component: Component) : super().__init__(component) def getTxt(self): return "。*:゜☆" + self._component.getTxt() + "☆゜:。*。" if __name__ == "__main__": print(Emoticon("(*´Д`)").getTxt()) print("") print(BanzaiDecorator( Emoticon("(*´Д`)")).getTxt()) print("") print(YeahDecorator( BanzaiDecorator( Emoticon("(*´Д`)"))).getTxt()) print("") print(GlitterDecorator( YeahDecorator( BanzaiDecorator( Emoticon("(*´Д`)")))).getTxt())
結果はこんな感じ。
きれいにデコレーションできましたか???
$ python Decorator.py (*´Д`) ヽ(*´Д`)/ イエェェェェヽ(*´Д`)/ェェェェイィ 。*:゜☆イエェェェェヽ(*´Д`)/ェェェェイィ☆゜:。*。
Pythonでデザインパターン 拾壱ノ型「Composite」
今回はPythonの勉強しながらデザインパターンのCompositeをやってみました。
他のパターンにつきましては、以下の目次記事をご参照ください。
Compositeパターンとは
直訳すると「複合物」や「混合物」みたいな意味でしょうか。
wikiには「ディレクトリとファイルなどのような、木構造を伴う再帰的なデータ構造を表すことができる」とあります。
このパターンはwikiにもあるとおりオブジェクトで木構造を表現するためのものです!
Compositeパターンについて考えてみる
木構造というとファイル構造が一番イメージしやすいですかね?
今回はCompositeパターンを使ってディレクトリツリーを作ってみたいと思います(・`ω´・)b
まずファイルやディレクトリのベースとなる抽象クラスEntryを用意します。
from abc import ABCMeta, abstractmethod class Entry(metaclass=ABCMeta) : @abstractmethod def getName(self) : pass @abstractmethod def printList(self, prefix) : pass
Entryを継承してファイルを示すクラスFileを用意します。
class File(Entry): def __init__(self, name) -> None: self.__name = name def getName(self) -> str: return self.__name def printList(self, prefix: str) -> None: uri = prefix + "/" + self.__name print(uri)
Entryを継承してディレクトリを示すクラスDirectoryを用意します。
Directoryクラスには新しいEntryを追加するaddEntryメソッドを備えます。
FileクラスにはaddEntryメソッドがないので新しいEntryを追加できません。
似たようなコードが世の中にはいっぱいあるのでこのあたりの説明は不要ですかね(;^ω^)
class Directory(Entry): def __init__(self, name) -> None: self.__name = name self.__directory = [Entry] def getName(self) -> str: return self.__name def addEntry(self, entry: Entry) -> [Entry]: self.__directory.append(entry) return self.__directory def printList(self, prefix: str) -> None: uri = prefix + "/" + self.__name print(uri) entryNum = len(self.__directory) for i in range(entryNum): self.__directory[i].printList(uri)
あとはDirectoryクラスを作成してその中にDirectoryクラスやFileクラスを追加していきます。
# ディレクトリを作成 dir = Directory("ディレクトリ名") # ファイルを追加 dir.addEntry(File("ファイル名")) # ディレクトリを追加 dir.addEntry(Directory("ディレクトリ名")) # 再帰的にディレクトリとファイルを追加 dir.addEntry(Directory("ディレクトリ名").addEntry(File("ファイル名")))
サンプルコード
これまでのコードの全体像です。
from abc import ABCMeta, abstractmethod class Entry(metaclass=ABCMeta): @abstractmethod def getName(self) -> str: pass @abstractmethod def printList(self, prefix: str) -> None: pass def printList(self): pass class File(Entry): def __init__(self, name) -> None: self.__name = name def getName(self) -> str: return self.__name def printList(self, prefix: str) -> None: uri = prefix + "/" + self.__name print(uri) class Directory(Entry): def __init__(self, name) -> None: self.__name = name self.__directory = [Entry] def getName(self) -> str: return self.__name def addEntry(self, entry: Entry) -> [Entry]: self.__directory.append(entry) return self.__directory def printList(self, prefix: str) -> None: uri = prefix + "/" + self.__name print(uri) entryNum = len(self.__directory) for i in range(entryNum): self.__directory[i].printList(uri) if __name__ == "__main__" : # rootディレクトリを作成 rootDir = Directory("root") # サブフォルダを作成 folder1 = Directory("Folder1") folder2 = Directory("Folder2") folder3 = Directory("Folder3") folder4 = Directory("Folder4") # フォルダを追加 rootDir.addEntry(folder1) folder1.addEntry(folder2) rootDir.addEntry(folder3) folder3.addEntry(folder4) # ファイルを作成 file1 = File("file1") file2 = File("file2") file3 = File("file3") file4 = File("file4") file5 = File("file5") file6 = File("file6") # ファイルを追加 rootDir.addEntry(file1) folder1.addEntry(file2) folder2.addEntry(file3) folder3.addEntry(file4) folder4.addEntry(file5) folder4.addEntry(file6) # ディレクトリツリーを表示 rootDir.printList(".")
実行結果はこんな感じです!
$ python Composite.py ./root ./root/Folder1 ./root/Folder1/Folder2 ./root/Folder1/Folder2/file3 ./root/Folder1/file2 ./root/Folder3 ./root/Folder3/Folder4 ./root/Folder3/Folder4/file5 ./root/Folder3/Folder4/file6 ./root/Folder3/file4 ./root/file1
Pythonでデザインパターン 拾ノ型「Strategy」
今回はPythonの勉強しながらデザインパターンのStrategyをやってみました。
他のパターンにつきましては、以下の目次記事をご参照ください。
Strategyとは
直訳すると「戦略」ですね。
wikiには「アルゴリズムを使用者から独立したまま様々に変化させることができるようになる」とあります。
このパターンはBridgeパターンとよく似ていますが、
Bridgeパターンは機能拡張を目的としているのに対して、Strategyパターンは機能切り替えを目的にしている?と勝手に認識しています笑
いずれにしてもjavaやC#など、インタフェースがある言語ではあまり使わないパターンかな(;^ω^)
Strategyについて考えてみる
Bridgeパターンと同様の例ですが、以下のようなインタフェースがあったとします。
from abc import ABCMeta, abstractmethod class Hoge(metaclass=ABCMeta) : @abstractmethod def functionA(self) : pass
このインタフェースを実装したクラスは以下のような感じでしょうか?
class Foo(Hoge) def functionA(self) : print("Foo機能") class Bar(Hoge) def functionA(self) : print("Bar機能")
そしてこのクラスを使用するとすればこのような感じですかね?
if __name__ == "__main__" : foo = Foo() bar = Bar() foo.functionA() bar.functionA()
これでもいいのですが、Strategyパターンですと以下のようなStrategyクラスを作成します。
class Strategy(Hoge) def __init__(self, hoge: Hoge): self.setHogeHoge(hoge) def setHogeHoge(self, hoge) : self._hoge = hoge def functionA(self) : self._hoge.functionA()
そしてStrategyクラスにインスタンを設定することで機能切り替えが行えるようになります!
if __name__ == "__main__" : obj = Strategy(Foo()) obj.functionA() # Foo機能 obj.setHogeHoge(Bar()) obj.functionA() # Bar機能
サンプルコード
Bridgeパターンと同様にポケモンでピカピカ言ってるアイツを倒します。(๑◕ܫ◕๑)
BridgeパターンではSkillクラスを拡張するクラスを使ってダメージを与えていました。
今回は、"効果抜群クラス"と"効果は今ひとつクラス"を"効果クラス"で切り替えられるようにしています。
微妙な違いでわかりにくいかもですがお許しください(´ε`;)
# coding: utf-8 from abc import ABCMeta, abstractmethod # 技クラス class Skill(metaclass=ABCMeta) : def __init__(self, name : str, type : str) : self.__name = name self.__type = type def getName(self) -> str: return self.__name def getType(self) -> str: return self.__type @abstractmethod def getDamege(self) : pass # 効果クラス class EffectiveInterface(metaclass=ABCMeta) : @abstractmethod def getDamege(self, skill : Skill) : pass # Strategyパターン class Effective(EffectiveInterface) : def __init__(self, effect): self.setEffect(effect) def setEffect(self, effect) : self._effect = effect def getDamege(self, skill : Skill) : return self._effect.getDamege(skill) # 効果抜群クラス class superEffective(EffectiveInterface): def getDamege(self, skill : Skill): print("効果抜群だ!!") return skill.getDamege() * 1.5 # 効果は今ひとつクラス class notVeryEffective(EffectiveInterface): def getDamege(self, skill : Skill): print("効果は今ひとつのようだ...") return skill.getDamege() * 0.75 # 技:地震 class earthquake(Skill): def __init__(self): super().__init__("じしん", "じめん") def getDamege(self): return 100 # 技:たいあたり class taiatari(Skill): def __init__(self): super().__init__("たいあたり", "ノーマル") def getDamege(self): return 20 # 技:かぜおこし class gust(Skill): def __init__(self): super().__init__("かぜおこし", "ひこう") def getDamege(self): return 40 class Pikachu(object): def __init__(self, name, hp): self.__name = name self.__hp = hp def Damege(self, skill): if skill.getType() == 'じめん': damege = Effective(superEffective()).getDamege(skill) elif skill.getType() == 'ひこう': damege = Effective(notVeryEffective()).getDamege(skill) else: damege = skill.getDamege() self.__hp = self.__hp - damege if self.__hp <= 0 : print(self.__name, "は たおれた") print("め の まえ が まっしろ に なった") if __name__ == "__main__" : skill_taiatari = taiatari() skill_gust = gust() skill_earthquake = earthquake() pikachu = Pikachu(name = "ピカチュウ", hp = 75) # たいあたりしてみる print("------------") print(skill_taiatari.getName(), "を つかった") pikachu.Damege(skill_taiatari) # かぜおこしを使ってみる print("------------") print(skill_gust.getName(), "を つかった") pikachu.Damege(skill_gust) # じしんを使ってみる print("------------") print(skill_earthquake.getName(), "を つかった") pikachu.Damege(skill_earthquake)
結果はこんな感じです。
今回も上手に倒せましたか???
$ python Strategy.py ------------ たいあたり を つかった ------------ かぜおこし を つかった 効果は今ひとつのようだ... ------------ じしん を つかった 効果抜群だ!! ピカチュウ は たおれた め の まえ が まっしろ に なった
Pythonでデザインパターン 玖ノ型「Bridge」
今回はPythonの勉強しながらデザインパターンのBridgeをやってみました。
他のパターンにつきましては、以下の目次記事をご参照ください。
Bridgeとは
直訳すると「橋」ですね。
wikiには「"橋渡し"のクラスを用意することによって、クラスを複数の方向に拡張させることを目的とする」とあります。
このパターンは、オブジェクトとオブジェクトの間にワンクッション挟むイメージです。
JavaやC#みたいにインタフェース機能がある言語だとあまり使わない気もしますね(;´瓜`)
Bridgeについて考えてみる
いつものごとくいい例が思いつきませんが、以下のようなインタフェースがあったとします。
from abc import ABCMeta, abstractmethod class Hoge(metaclass=ABCMeta) : @abstractmethod def functionA(self) : pass
普通にこのインタフェースを使おうとするとこんな感じでしょうか?
class Foo(Hoge) : def functionA(self) : print("機能A")
ここまではよく見かける書き方ですね
では、このインタフェースを実装したクラスに手を加えずに機能拡張して"機能Aを3回実行"するようにしたいと思います。
単純に考えればこんな感じでしょうか?
if __name__ == "__main__" : foo = Foo() for num in range(3): foo.functionA()
これでもよいですがユーザ視点から考えると毎回functionA()を3回実行する処理を書くのは手間ですね。。。
そこで以下のようなクラスを用意します。
class Bar(Hoge) : def __init__(self, hoge): self._hoge = hoge def functionA(self) : for num in range(3): self._hoge.functionA()
これを使えば以下のようにすることができます。
fooインスタンスをそのまま使えば通常機能となり、Barオブジェクトに入れて実行すれば拡張機能となりますね(・`ω´・)v
if __name__ == "__main__" : foo = Foo() foo.functionA() # 通常機能 bar = Bar(foo) bar.functionA() # 拡張機能
サンプルコード
今回はポケモンでピカピカ言っているアイツを倒してみたいと思います(๑◕ܫ◕๑)
このサンプルでは技のダメージをBridgeパターンで"効果抜群だ"と"効果は今ひとつだ"に拡張しています。
# coding: utf-8 from abc import ABCMeta, abstractmethod # 技クラス class Skill(metaclass=ABCMeta) : def __init__(self, name : str, type : str) : self.__name = name self.__type = type def getName(self) -> str: return self.__name def getType(self) -> str: return self.__type @abstractmethod def getDamege(self) : pass # 効果抜群クラス class superEffective(Skill): def __init__(self, skill) : self._skill = skill def getDamege(self): print("効果抜群だ!!") return self._skill.getDamege() * 1.5 # 効果は今ひとつクラス class notVeryEffective(Skill): def __init__(self, skill) : self._skill = skill def getDamege(self): print("効果は今ひとつのようだ...") return self._skill.getDamege() * 0.75 # 技:地震 class earthquake(Skill): def __init__(self): super().__init__("じしん", "じめん") def getDamege(self): return 100 # 技:たいあたり class taiatari(Skill): def __init__(self): super().__init__("たいあたり", "ノーマル") def getDamege(self): return 20 # 技:かぜおこし class gust(Skill): def __init__(self): super().__init__("かぜおこし", "ひこう") def getDamege(self): return 40 class Pikachu(object): def __init__(self, name, hp): self.__name = name self.__hp = hp def Damege(self, skill): if skill.getType() == 'じめん': damege = superEffective(skill).getDamege() elif skill.getType() == 'ひこう': damege = notVeryEffective(skill).getDamege() else: damege = skill.getDamege() self.__hp = self.__hp - damege if self.__hp <= 0 : print(self.__name, "は たおれた") print("め の まえ が まっしろ に なった") if __name__ == "__main__" : skill_taiatari = taiatari() skill_gust = gust() skill_earthquake = earthquake() pikachu = Pikachu(name = "ピカチュウ", hp = 75) # たいあたりしてみる print("------------") print(skill_taiatari.getName(), "を つかった") pikachu.Damege(skill_taiatari) # かぜおこしを使ってみる print("------------") print(skill_gust.getName(), "を つかった") pikachu.Damege(skill_gust) # じしんを使ってみる print("------------") print(skill_earthquake.getName(), "を つかった") pikachu.Damege(skill_earthquake)
実行結果は以下のとおりです!
ちゃんと倒せましたか?
$ python Brigge.py ------------ たいあたり を つかった ------------ かぜおこし を つかった 効果は今ひとつのようだ... ------------ じしん を つかった 効果抜群だ!! ピカチュウ は たおれた め の まえ が まっしろ に なった
Pythonでデザインパターン 捌ノ型「AbstractFactory」
今回はPythonの勉強しながらデザインパターンのAbstractFactoryをやってみました。
他のパターンにつきましては、以下の目次記事をご参照ください。
AbstractFactoryとは
直訳すると「抽象的な工場」になります。
wikiには「関連するインスタンス群を生成するための API を集約することによって、複数のモジュール群の再利用を効率化することを目的とする」とあります。
AbstractFactoryについて考えてみる
突然だが醤油ラーメンを作ろうと思う(・`ω´・;)
# スープを作る soup = Soup(SoySauce()) # 醤油ベース # 麺を作る noodle = Noodle(hardness="H", thickness=5) # 硬め、やや太麺 # トッピングを用意 topping = Topping() topping.add(PorkFillet()) # チャーシュー topping.add(Leek()) # ネギ # ラーメン鉢を用意して盛り付け ramenBowl = RamenBowl() ramenBowl.add(soup) ramenBowl.add(noodle) ramenBowl.add(topping)
一般的な醤油ラーメンの出来上がりだ!
これが一般的な醤油ラーメンかはさておき(・`ω´・;)
でも別の人が作ればこうならないかもしれない。
例えば醤油ベースのスープに豚骨ラーメンのトッピングをしてしまうかも(;^ω^)
# 豚骨ラーメンのトッピングを用意 topping = Topping() topping.add(Tororo()) # とろろ topping.add(Kelp()) # 昆布 topping.add(Bonito()) # かつお節
お客がラーメン鉢の中に好き勝手盛りできるセルフサービスラーメン屋ならいいけれど、
お客からの注文を受けて店員が間違えて盛り付けようならクレーム必至だろう(;゚Д゚)
だから、そうならないようにするために以下のような醤油ラーメンクラスを用意してみました!
class SoySauceRamen(object) : def __init__(self) : # スープ self.soup = Soup(SoySauce()) # 醤油ベース # 麺 self.noodle = Noodle(hardness="H", thickness=5) # トッピング self.topping = Topping() self.topping.add(PorkFillet()) # チャーシュー self.topping.add(Leek()) # ネギ def getSoup(self) -> Soup : return self.soup def getNoodle(self) -> Noodle : return self.noodle def getTopping(self) -> Topping : return self.topping if __name__ == "__main__" : # 醤油ラーメンの元 soySauceRamen = SoySauceRamen() # ラーメン鉢を用意して盛り付け ramenBowl = RamenBowl() ramenBowl.add(soySauceRamen.getSoup()) ramenBowl.add(soySauceRamen.getNoodle()) ramenBowl.add(soySauceRamen.getTopping())
同じように豚骨ラーメンを作るときはまた専用のクラスを用意しておけば材料を間違わないね!!
class PigBonesRamen(object) : def __init__(self) : # 材料準備処理は割愛 def getSoup(self) -> Soup : return self.soup def getNoodle(self) -> Noodle : return self.noodle def getTopping(self) -> Topping : return self.topping if __name__ == "__main__" : pigBonesRamen = PigBonesRamen() # ラーメン鉢を用意して盛り付け ramenBowl = RamenBowl() ramenBowl.add(pigBonesRamen.getSoup()) ramenBowl.add(pigBonesRamen.getNoodle()) ramenBowl.add(pigBonesRamen.getTopping())
ここまでくればお気づきかもですが醤油ラーメンクラスと豚骨ラーメンクラスで共通部分をまとめられそうです。
class RamenBase(metaclass=ABCMeta) : @abstractmethod def getSoup(self) -> Soup : pass @abstractmethod def getNoodle(self) -> Noodle : pass @abstractmethod def getTopping(self) -> Topping : pass
そして以下のようなクラスを用意すればユーザは好きなラーメンの材料を手に入れることができ、盛り付けミスしなくなりますね(・`ω´・)b
class RamenFactory(object) : deg getRamen(ramenType : str) -> RamenBase : if ramenType == "PigBonesRamen" : return PigBonesRamen() elif ramenType == "MisoRamen" : return MisoRamen() elif ramenType == "SaltRamen" : return SaltRamen() else : return SoySauceRamen()
こんな感じがAbstractFactoryだと思っています
サンプルコード
抽象的に書いたのでこのままでは動きませんがサンプルコードです。
class RamenBase(metaclass=ABCMeta) : @abstractmethod def getSoup(self) -> Soup : pass @abstractmethod def getNoodle(self) -> Noodle : pass @abstractmethod def getTopping(self) -> Topping : pass class SoySauceRamen(RamenBase) : # 処理内容は割愛 class PigBonesRamen(RamenBase) : # 処理内容は割愛 class MisoRamen(RamenBase) : # 処理内容は割愛 class SaltRamen(RamenBase) : # 処理内容は割愛 class RamenFactory(object) : deg getRamen(ramenType : str) -> RamenBase : if ramenType == "PigBonesRamen" : return PigBonesRamen() elif ramenType == "MisoRamen" : return MisoRamen() elif ramenType == "SaltRamen" : return SaltRamen() else : return SoySauceRamen() if __name__ == "__main__" : # ラーメン工場を設立 ramenFactory = RamenFactory() # 豚骨ラーメンの注文が入ったら? ramen = ramenFactory.getRamen("PigBonesRamen") # ラーメン鉢を用意して盛り付け ramenBowl = RamenBowl() ramenBowl.add(ramen.getSoup()) ramenBowl.add(ramen.getNoodle()) ramenBowl.add(ramen.getTopping()) # へい、お待ち!!
- 価格: 2948 円
- 楽天で詳細を見る
Pythonでデザインパターン 漆ノ型「Builder」
今回はPythonの勉強しながらデザインパターンのBuilderをやってみました。
他のパターンにつきましては、以下の目次記事をご参照ください。
Builderとは
直訳すると「建築者」という意味になります。
wikiには「 オブジェクトの生成過程を抽象化することによって、動的なオブジェクトの生成を可能にする」とあります。
Builderパターンは「オブジェクトを生成する過程は同じだけど、性質の異なるオブジェクトを作成する」ためのパターンのようです。
・・・(´・ω・`)?
Builderパターンについて考えてみる
いい例かどうかわかりませんが、RESTful Web APIのレスポンスを題材に考えてみたいと思います。
よくあるRESTful Web APIは、なにかリクエストを送るとXMLやJSONなどのフォーマットは異なるけど同じ内容のレスポンスを得ることができますね。
例えば以下のような感じです。これは人物のプロフィールを取ってくるようなAPIのつもりです^^;
{ "name": "hoge", "age": 15 "job":"student" }
<xml> <name>hoge</name> <age>15</age> <job>student</job> </xml>
このレスポンスを作るためのクラスを単純に書いてみるとこんな感じでしょうか?
class Response(object) : def __init__(self, name="AAA", age=15, job="student" ): self.__name = name self.__age = age self.__job = job def getXML(self) -> str : respnse = "<xml>\n" respnse += "\t<name>" + self.__name + "</name>\n" respnse += "\t<age>" + str(self.__age) + "</age>\n" respnse += "\t<job>" + self.__job + "</job>\n" respnse += "</xml>" return respnse def getJSON(self) -> str : respnse = "{\n" respnse += "\t\"name\":\"" + self.__name + "\"\n" respnse += "\t\"age\":\"" + str(self.__age) + "\"\n" respnse += "\t\"job\":\"" + self.__job + "\"\n" respnse += "}" return respnse if __name__ == "__main__": obj = Response("AAA", 15, "student") resXML = obj.getXML() resJSON = obj.getJSON() print(resXML) print(resJSON)
いまはデータが少ないのでそのままでもいいのですが、将来的に扱うデータが増えたりするとフォーマットも修正しないといけなくなり、 どんどん複雑になっていく可能性があります。 できればXMLとJSONでクラスを分けたいところです。そこで以下のようにしてみました。
class Response(metaclass=ABCMeta) : def Name(self, name) : self._name = name def Age(self, age) : self._age = age def Job(self, job) : self._job = job @abstractmethod def getResponse() -> str : pass class XMLResponse(Response) : def __init__(self, name="AAA", age=15, job="student" ): self.Name(name) self.Age(age) self.Job(job) def getResponse(self) -> str : # 処理内容は割愛 return respnse class JSONResponse(Response) : def __init__(self, name="AAA", age=15, job="student" ): self.Name(name) self.Age(age) self.Job(job) def getResponse(self) -> str : # 処理内容は割愛 return respnse if __name__ == "__main__": objXML = XMLResponse(name = "AAA", age = 15, job = "student") objJSON = JSONResponse(name = "AAA", age = 15, job = "student") resXML = objXML.getResponse() resJSON = objJSON.getResponse() print(resXML) print(resJSON)
これで少しだけオブジェクト指向に近づいてきましたね。
でも、ユーザ視点から見るとインスタンスを作成するときに毎回引数を指定しないといけないのは大変。。。
もしかすると間違えてnameにjobを代入しちゃうかもしれないし、XMLResponseとJSONResponseで別々の値を入れてしまうかもしれません。
ここでXMLResponseとJSONResponseのコンストラクタを見るとやっていることは同じなので何とかまとめたいところです。
そこで以下のようなResponseBuilderクラスを作ってクッションにしてました!
class Response(metaclass=ABCMeta) : # 処理内容は割愛 class XMLResponse(Response) : # 処理内容は割愛 class JSONResponse(Response) : # 処理内容は割愛 class ResponseBuilder(object): def build(self, response: Response) -> Response: response.Name("hoge") response.Age(15) response.Job("student") return response if __name__ == "__main__": objXML = ResponseBuilder().build(XMLResponse()) objJSON = ResponseBuilder().build(JSONResponse()) resXML = objXML.getResponse() resJSON = objJSON.getResponse() print(resXML) print(resJSON)
これでResponseBuilderクラスのbuildメソッドにオブジェクトを入れれば、XMLResponseでもJSONResponseでも同じように初期化されます。
mainもだいぶシンプルにユーザに優しくなりました!
こんな感じでオブジェクトの初期化にワンクッション挟むのがBuilderパターンなのかな(´・ω・`)?
サンプルコード
以下、上記のコードの全体像です。
# coding: utf-8 from abc import ABC, ABCMeta, abstractmethod class Response(metaclass=ABCMeta) : def Name(self, name) : self._name = name def Age(self, age) : self._age = age def Job(self, job) : self._job = job @abstractmethod def getResponse() -> str : pass class XMLResponse(Response) : def getResponse(self) -> str : respnse = "<xml>\n" respnse += "\t<name>" + self._name + "</name>\n" respnse += "\t<age>" + str(self._age) + "</age>\n" respnse += "\t<job>" + self._job + "</job>\n" respnse += "</xml>" return respnse class JSONResponse(Response) : def getResponse(self) -> str : respnse = "{\n" respnse += "\t\"name\":\"" + self._name + "\"\n" respnse += "\t\"age\":\"" + str(self._age) + "\"\n" respnse += "\t\"job\":\"" + self._job + "\"\n" respnse += "}" return respnse class ResponseBuilder(object): def build(self, response: Response) -> Response: response.Name("hoge") response.Age(15) response.Job("student") return response if __name__ == "__main__": objXML = ResponseBuilder().build(XMLResponse()) objJSON = ResponseBuilder().build(JSONResponse()) resXML = objXML.getResponse() resJSON = objJSON.getResponse() print(resXML) print(resJSON)
実行結果は以下のとおりです!
$ python Builder.py <xml> <name>hoge</name> <age>15</age> <job>student</job> </xml> { "name":"hoge" "age":"15" "job":"student" }
- 価格: 4070 円
- 楽天で詳細を見る