Pythonでデザインパターン 陸ノ型「Prototype」
今回はPythonの勉強しながらデザインパターンのPrototypeをやってみました。
他のパターンにつきましては、以下の目次記事をご参照ください。
Prototypeとは
直訳すると「原型」や「模範」という意味になります。
wikiには「生成されるオブジェクトの種別がプロトタイプ(典型)的なインスタンスであるときに使用され、このプロトタイプを複製して新しいオブジェクトを生成する」とあります。
Prototypeパターンとは「オブジェクトからオブジェクト(複製)を作成するパターン」です。
javaやC#とかでいうとclone()のことですね(・`ω´・)b
試行錯誤の時間
Pythonにおけるclone()は標準ライブラリである「copy」を使うことで簡単に実現することができます。
ここで「copy」には浅いコピーであるcopy()と深いコピーであるdeepcopy()があるようです。
copy.copy(x) x の浅い (shallow) コピーを返します。 copy.deepcopy(x[, memo]) x の深い (deep) コピーを返します。
・・・な ん だ そ れ は ?(゚Д゚≡゚Д゚)?
説明には以下のように書かれています。
浅いコピー (shallow copy) は新たな複合オブジェクトを作成し、 その後 (可能な限り) 元のオブジェクト中に見つかったオブジェクトに 対する 参照 を挿入します。 深いコピー (deep copy) は新たな複合オブジェクトを作成し、 その後元のオブジェクト中に見つかったオブジェクトの コピー を挿入します。
これによるとオブジェクトの中に別のオブジェクトが含まれていた場合は、
- 浅いコピーだと中にあるオブジェクトは「参照型」(Cとかでいうポインタ型)になる
- 深いコピーだと中にあるオブジェクトは「値型」になる
・・・ということかな?(;^ν^)
説明だとよくわからないのでいつもどおりコードを書いて試してみます\(^o^)/
まず、以下のようなクラスを用意しました。
この例ではOuterというアウタークラスの中にInnerというインナークラスがあります。
アウタークラスの中でインナークラスのインスタンスを作成して、get/setを介してアウタークラスからインナークラスにアクセスできます。
class Outer(object) : def __init__(self) : self.__value = 0 self.__inner = self.Inner() def getOuterValue(self): return self.__value def setOuterValue(self, value): self.__value = value def getInnerValue(self): return self.__inner.getValue() def setInnerValue(self, value): self.__inner.setValue(value) class Inner(object) : def __init__(self) : pass def getValue(self): return self.__value def setValue(self, value): self.__value = value
このクラスを使ったテストコードは以下のとおりです。
if __name__ == "__main__" : origin = Outer() origin.setOuterValue(1) origin.setInnerValue(2) clone = copy.copy(origin) # 浅いコピー #clone = copy.deepcopy(origin) # 深いコピー print("現在のオブジェクト値") print("origin - Outer:", origin.getOuterValue()) print("origin - Inner:", origin.getInnerValue()) print("clone - Outer:", clone.getOuterValue()) print("clone - Inner:", clone.getInnerValue()) print("クローンで書き換え後のオブジェクト値") clone.setOuterValue(3) clone.setInnerValue(4) print("origin - Outer:", origin.getOuterValue()) print("origin - Inner:", origin.getInnerValue()) print("clone - Outer:", clone.getOuterValue()) print("clone - Inner:", clone.getInnerValue())
まずは浅いコピーcopy.copy(origin)の実行結果です。
クローンで書き換え後のオブジェクト値を見ると、「origin - Inner」の値が「clone - Inner」と同じになっています。
「origin - Outer」は書き換え前のままです。
現在のオブジェクト値 origin - Outer: 1 origin - Inner: 2 clone - Outer: 1 clone - Inner: 2 クローンで書き換え後のオブジェクト値 origin - Outer: 1 origin - Inner: 4 clone - Outer: 3 clone - Inner: 4
続いて深いコピーcopy.deepcopy(origin)の実行結果です。
これによると「clone - Inner」は書き換わっていますが、「origin - Inner」は書き換わっていません。
現在のオブジェクト値 origin - Outer: 1 origin - Inner: 2 clone - Outer: 1 clone - Inner: 2 クローンで書き換え後のオブジェクト値 origin - Outer: 1 origin - Inner: 2 clone - Outer: 3 clone - Inner: 4
これらの結果から浅いコピーだとやはり「参照型」となっているようです。
そのため、cloneを使ってInnerの値を書きかえると、originのInnerも書き換わってしまいます。
したがって、Prototypeパターンを実現するには中のオブジェクトすべてをコピーすべきなので、深いコピー (deep copy)を採用すべきでしょうかね?
サンプルコード
いつものごとくサンプルコードです!
# coding: utf-8 import copy from abc import ABC, ABCMeta, abstractmethod class Cloneable(metaclass=ABCMeta) : @abstractmethod def createClone(self) : pass class Prototype(Cloneable) : def __init__(self, name: str) : self.__name = name def createClone(self) : return copy.deepcopy(self) def getName(self) : return self.__name if __name__ == "__main__" : origin = Prototype("AAA") clone = origin.createClone() print("origin:", origin.getName()) print("clone :", clone.getName())
以下、実行結果。
originに付けた名前がcloneにも引き継がれていますね(・`ω´・)
python Prototype.py origin: AAA clone : AAA
- 価格: 2640 円
- 楽天で詳細を見る
Pythonでデザインパターン 伍ノ型「Singleton」
今回はPythonの勉強しながらデザインパターンのSingletonをやってみました。
他のパターンにつきましては、以下の目次記事をご参照ください。
Singletonとは
直訳すると「単一要素」みたいな意味になります。
wikiには「そのクラスのインスタンスが1つしか生成されないことを保証するデザインパターンのこと」とあります。
Singletonパターンはwikiにも説明があるとおり、クラスのインスタンスが複数作られないようにするためのメカニズムです。
アプリケーション全体で統一しなければならない仕様を実装しなければならないときに役に立ちます。
私はデザインパターンの中でこのパターンが一番お気に入りで、オブジェクト指向言語でなにか作るときにを頻繁に利用しています。
実際の開発現場でも使っている人をチラホラ見かけたので習得しておいても損はないパターンかもしれないですね(・`ω´・)b
Singletonパターンが欲しくなるとき
それではいつものごとく、いろいろコードを書きながら考えてみます\(^ω^)/
まず以下のようなConfigクラスがあったとします。
class Config(object) : def __init__(self) : self.ParameterA = 1 self.ParameterB = 2 self.ParameterC = 3
このクラスを普通に使おうとするとこんな感じでしょうか?
def moduleA(): conf = Config() pramA = conf.ParameterA
ひとつのモジュールで使うだけならこれでもいいのですが、
複数のモジュールでこのクラスのパラメータを共有しなければならなくなったとします。
そこでモジュール毎にインスタンスを作ってみます。
こうしてしまうとインスタンスがそれぞれ独立しているので、あるインスタンスのパラメータを書き換えても別のインスタンスには反映されません。
def moduleA(): conf = Config() pramA = conf.ParameterA conf.ParameterA = 5 def moduleB(): conf = Config() pramA = conf.ParameterA conf.ParameterA = 6
今度はConfigをグローバル変数にしてみます。
こうすればmoduleAとmoduleBでインスタンスを共有することができます。
conf = Config() def moduleA(): pramA = conf.ParameterA conf.ParameterA = 5 def moduleB(): pramA = conf.ParameterA conf.ParameterA = 6
しかし、プログラムの規模が大きくなって複数人で開発するようになってきたりすると、
誰かがどこかでまたConfigのインスタンスを作ってしまったり、confに新しいインスタンスで上書きしてしまうかもしれません。
そうするとまた独立したインスタンスが複数できてしまうことになります。
conf = Config() ・ ・ ・ config = Config() # confの存在に気がつかずに新しいインスタンスを作成 def moduleA(): pramA = conf.ParameterA conf.ParameterA = 5 def moduleB(): pramA = conf.ParameterA conf = Config() # 誤ってインスタンスの上書き conf.ParameterA = 6 def moduleC(): pramA = conf.ParameterA config.ParameterA = 7 # confではないインスタンスを使用
このようなことを回避するには、一度インスタンスを作成したら、それ以降は新しいインスタンスを作成できないようにしなければなりません(;゚Д゚)
そこでSingletonパターンの登場です。イメージとしては以下のような感じです。
ConfigにクラスメソッドgetInstance()を実装しておきます。
getInstance()は、クラス内にインスタンスがなければクラス変数に自身のインスタンスを作成してそれを返します。
クラス変数に自身のインスタンスがすでに存在すれば、それをそのまま返します。
そしてConfig()でインスタンスを作成しようとしたときエラーを出力するようにします。
このようにしておけばConfigクラス内だけに存在する単一のインスタンスを作ることができます。
#conf = Config() # 新しいインスタンス生成はエラー def moduleA(): conf = Config.getInstance() # インスタンス取得 pramA = conf.ParameterA conf.ParameterA = 5 def moduleB(): #conf = Config() # 新しいインスタンス生成はエラー conf = Config.getInstance() # インスタンス取得 pramA = conf.ParameterA conf.ParameterA = 6
試行錯誤の時間
まずConfig()でインスタンスを作成できないようにしてみます。
Pythonではraise Exceptionで例外をスローできます。
そこで以下のようにしてコンストラクタが呼び出されたときにエラーを出力できるようにしてみました。
class Config(object) : def __init__(self) : raise Exception("Singletonクラス")
これでインスタンスを作成しようとするとエラーになります。
conf = Config() # エラーになる
つぎにインスタンスを取得するためのクラスメソッドgetInstance()を実装してみます。
この例ではクラス変数__mInstanceが空っぽだった場合、Config() で自身のインスタンスを作成するようにしています。
もうお気づきかもしれませんが、これでは困ったことがおきます。
先の実装で、Config()でインスタンスを作成しようとするとエラーを発生させるようにしてしまったからです(・´ω`・;)
class Config(object) : __mInstance = None def __init__(self) : raise Exception("Singletonクラス") @classmethod def getInstance(cls) : if cls.__mInstance == None : cls.__mInstance = Config() # エラー return cls.__mInstance
そこで__new__というものを利用します。
参考)__new__ ってなに?
def __new__(cls)は、インスタンスオブジェクトが作成される前に呼び出さる特殊なメソッドです。
以下、動作確認用のコードを書いてみたのでこれで動きを見てみます。
# coding: utf-8 class parent(object) : def __new__(cls) : print("new") return super().__new__(cls) def __init__(self) : print("init") def __del__(self) : print("del") if __name__ == '__main__': obj = parent()
これの実行結果は以下のようになります。
先に__new__が呼び出されて、__init__が呼び出されているのがわかります。
引数であるclsはクラスオブジェクトを示しており、super().__new__(cls)でクラスオブジェクトに対するインスタンスオブジェクトを作成することができます。
__new__(cls)のあとに__init__(self)が呼び出さるわけですが、selfはのsuper().__new__(cls)で作ったインスタンスオブジェクトだったわけですね。
(ちなみにreturn super().__new__(cls)を消せば、__init__(self)が呼び出されなくなります。当然ながら)
new init del
__new__を応用して以下のようにしてみました(・`ω´・)v
この例では、def __new__(cls)が呼び出されたときにraise Exceptionで例外をスローするようにしました。
そしてdef getInstance(cls)が呼び出されたとき、クラス変数__mInstanceが空っぽだった場合、
クラスオブジェクトを示すclsを使って、super().__new__(cls)でインスタンスを作成するようにしています。
class Config(object) : __mInstance = None def __new__(cls) : print("new") raise Exception("Singletonクラス") def __init__(self) : print("init") def __del__(self) : pass @classmethod def getInstance(cls) : if cls.__mInstance == None : cls.__mInstance = super().__new__(cls) return cls.__mInstance
これでConfig()でインスタンスを作成しようとすると以下のようなエラーを出力するようになります。
File "Singleton.py", line 7, in __new__ raise Exception("Singletonクラス") Exception: Singletonクラス
インスタンスの取得(未作成の場合は作成も)するには、以下のようにしてやらなければならなくなるはずです。
conf = Config.getInstance()
サンプルプログラム
以下、サンプルプログラムです。
# coding: utf-8 class _Singleton(object) : __mInstance = None def __new__(cls) : print("new parent") raise Exception("Singletonクラス") def __init__(self) : print("init parent") @classmethod def getInstance(cls) : if cls.__mInstance == None : cls.__mInstance = super().__new__(cls) return cls.__mInstance class myClass(_Singleton) : def __new__(cls) : print("new child") return super().__new__(cls) def __init__(self) : print("init child") def setValue(self, value) : self.__mValue = value def getValue(self) : return self.__mValue if __name__ == '__main__': #obj = Singleton() # エラー #obj = Singleton.getInstance() #obj1 = myClass() # エラー obj1 = myClass.getInstance() obj1.setValue(100) print(obj1.getValue()) obj2 = myClass.getInstance() print(obj2.getValue()) obj2.setValue(50) print(obj1.getValue())
実行結果はこのようになります!
myClass.getInstance()でobj1とobj2で同じインスタンを取得しています。
その後、それぞれでgetValue/setValueを行っていますが、printすればobj1とobj2で値が共有できていることがわかります。
$ python Singleton.py 100 100 50
- 価格: 2728 円
- 楽天で詳細を見る
Pythonでデザインパターン 肆ノ型「FactoryMethod」
今回はPythonの勉強しながらデザインパターンのFactoryMethodをやってみました。
他のパターンにつきましては、以下の目次記事をご参照ください。
FactoryMethodとは
直訳すると「工場手続き」みたいな意味になります。
wikiには「他のクラスのコンストラクタをサブクラスで上書き可能な自分のメソッドに置き換えることで、 アプリケーションに特化したオブジェクトの生成をサブクラスに追い出し、クラスの再利用性を高める」とあります。
FactoryMethodについて考えてみる
wikiの文言だと私にはよくわからなかったので、
いつものごとく色々コードを書いて自分なりの理解を書いてみたいと思います(;^ω^)
まず以下のようなProductインタフェースとそれを実装したProductA クラスとProductB クラスがあったとします。
class Product(metaclass=ABCMeta) : @abstractmethod def functionA(self) : pass class ProductA(Product) : def functionA(self) : # 機能Aを実装(処理内容は割愛) class ProductB(Product) : def functionA(self) : # 機能Aを実装(処理内容は割愛)
これを使ったmainは以下のようなイメージになります。
このときProductAもProductBもインスタンスを作成後に、それぞれの専用初期化メソッドInitializeA、B、Cを順番に呼び出さなければならないものとします。
そうするとmainにProductA、ProductBに対するそれぞれの初期化メソッドを実装しなければならなくなります。
またProductのインスタンスを作成するたびに、これらのメソッドを選択して順番に呼ばなければならなくなります。
これではProductCとかProductDとかできるたびにmainがどんどん肥大化して、複雑になっていきますね。。。(´ε`;)
def ProductAInitializeA(product : Product) : # ProductAの初期化処理A def ProductAInitializeB(product : Product) : # ProductAの初期化処理B def ProductAInitializeC(product : Product) : # ProductAの初期化処理C def ProductBInitializeA(product : Product) : # ProductBの初期化処理A def ProductBInitializeB(product : Product) : # ProductBの初期化処理B def ProductBInitializeC(product : Product) : # ProductBの初期化処理C if __name__ == '__main__' : product = ProductA() ProductAInitializeA(product) ProductAInitializeB(product) ProductAInitializeC(product) product.functionA() #これでProductAが使えるようになる product = ProductB() ProductBInitializeA(product) ProductBInitializeB(product) ProductBInitializeC(product) product.functionA() #これでProductBが使えるようになる
これを回避するもっとも単純な方法は、ProductAクラス、ProductBクラスそれぞれに専用の初期化メソッドを実装し、コンストラクタなどで初期化処理を呼び出すことが考えられます。
こうするとmainはとてもシンプルになりますね(・`ω´・)
しかし、この方法ではProductAクラス、ProductBクラスに直接手を加えなければなりません。
もし「InitializeA、B、Cがこのアプリケーションのみで有効な初期化処理だった場合」 かつ 「ProductAクラスやProductBクラスを他のシステムで使いまわしたい場合」を考えると
InitializeA、B、Cを他のシステムに合わせて変更しなければならなくなるかもしれません。
そうするとProductAクラス、ProductBクラスの再利用性は低下してしまいますね。。(;゚Д゚)
class ProductA(Product) : def functionA(self) : # 処理A def __InitializeA(self) : # 初期化処理A def __InitializeB(self) : # 初期化処理B def __InitializeC(self) : # 初期化処理C def __init__(self) : self.__InitializeA() self.__InitializeB() self.__InitializeC() class ProductB(Product) : def functionA(self) : # 処理A def __InitializeA(self) : # 初期化処理A def __InitializeB(self) : # 初期化処理B def __InitializeC(self) : # 初期化処理C def __init__(self) : self.__InitializeA() self.__InitializeB() self.__InitializeC() if __name__ == '__main__' : product = ProductA() product.functionA() product = ProductB() product.functionA()
そこで以下のようにしてみました(・`ω´・)b
この例では、まずFactoryという抽象クラスを用意しています。
このクラスには以下のものがあります。
- 初期化処理を実装するためのInitializeA、B、Cインタフェース
- Productのインスタンスを生成するためのcreateインスタンス
- 初期化手順を実装したcreateProduct()メソッド
そしてFactoryAとFactoryBは、Factoryを継承して、それぞれ以下の実装が作れらています。
- InitializeA、B、Cインタフェースを使ってProduct固有の初期化処理を実装
- createインタフェースを使ってProductのインスタンスを生成・返却するように実装
これによりFactoryのcreateProductメソッドは、createでインスタンスを取得し、InitializeA、B、Cで初期化したインスタンスを返すことができるようになります。
これで、wikiにあるように「mainからProductの生成と初期化をFactoryクラスに追い出し」、そして「Productに手を加えないため再利用性を高めることができる」
ということが実現できそうな気がします。
class Factory(metaclass=ABCMeta) : @abstractmethod def _create(self) -> Product : pass @abstractmethod def _InitializeA(self, product : Product) : pass @abstractmethod def _InitializeB(self, product : Product) : pass @abstractmethod def _InitializeC(self, product : Product) : pass def createProduct(self) : product = self._create() self._InitializeA(product) self._InitializeB(product) self._InitializeC(product) return product class FactoryA(Factory) : @abstractmethod def _create(self) -> Product : return FactoryA() @abstractmethod def _InitializeA(self, product : Product) : # 初期化処理A @abstractmethod def _InitializeB(self, product : Product) : # 初期化処理B @abstractmethod def _InitializeC(self, product : Product) : # 初期化処理C class FactoryB(Factory) : @abstractmethod def _create(self) -> Product : return FactoryB() @abstractmethod def _InitializeA(self, product : Product) : # 初期化処理A @abstractmethod def _InitializeB(self, product : Product) : # 初期化処理B @abstractmethod def _InitializeC(self, product : Product) : # 初期化処理C if __name__ == '__main__' : factory = FactoryA() product = factory.createProduct() product.functionA() factory = FactoryB() product = factory.createProduct() product.functionA()
こんな感じなのがFactoryMethodパターンかなと思っています\(^ω^)/
サンプルプログラム
以下、PythonでFactoryMethodパターンのサンプルプログラムです。
# coding: utf-8 from abc import ABC, ABCMeta, abstractmethod class Product(metaclass=ABCMeta) : @abstractmethod def use(self) -> None: pass class Account(Product) : def __init__(self, owner: str) -> None : print("Create account: ", owner) self.__owner = owner def use(self) -> None: print("Use account: ", self.__owner) def getOwner(self) -> str : return self.__owner class Factory(metaclass=ABCMeta) : def create(self, owner: str) -> Account : product = self.createProduct(owner) self.registerProduct(product) return product @abstractmethod def createProduct(self, owner: str) -> Account : pass @abstractmethod def registerProduct(self, product: Account) -> None : pass class AccountFactory(Factory) : def __init__(self) : self.__owners: list[str] = [] def createProduct(self, owner) -> Account : return Account(owner) def registerProduct(self, product: Account) -> None : self.__owners.append(product.getOwner()) def getOwners(self) -> list : return self.__owners if __name__ == '__main__' : factory = AccountFactory() account1 = factory.create("AAA") account2 = factory.create("BBB") account3 = factory.create("CCC") account4 = factory.create("DDD") account1.use() account2.use() account3.use() account4.use()
以下は実行結果です!
$ python FactoryMethod.py Create account: AAA Create account: BBB Create account: CCC Create account: DDD Use account: AAA Use account: BBB Use account: CCC Use account: DDD
- 価格: 2948 円
- 楽天で詳細を見る
Pythonでデザインパターン 参ノ型「TemplateMethod」
今回はPythonの勉強しながらデザインパターンのTemplateMethodをやってみました。
他のパターンにつきましては、以下の目次記事をご参照ください。
TemplateMethodとは
直訳すると「型板手続き」みたいな意味になります。
wikiには「ある処理のおおまかなアルゴリズムをあらかじめ決めておいて、そのアルゴリズムの具体的な設計をサブクラスに任せること」とあります。
TemplateMethodについて考えてみる
TemplateMethodパターンは抽象クラスを使用することで実現することができます。
いつもどおり、ざっくりした例で説明してみます。
以下はテンプレートクラス:Templateです。
このクラスではinterfaceA()、interfaceB()、interfaceC()というインタフェースがあります。
また、methodA()というメソッドがあり、このメソッドではinterfaceA()、interfaceB()、interfaceC()を順番に呼び出しています。
これがテンプレートメソッドとなります。
class Template(metaclass=ABCMeta) : @abstractmethod def _interfaceA(self) : pass @abstractmethod def _interfaceB(self) : pass @abstractmethod def _interfaceC(self) : pass def methodA(self) : self._interfaceA() self._interfaceB() self._interfaceC()
そして以下がTemplateクラスを継承した実体クラス:Hogeです。
このクラスではTemplateクラスのinterfaceA()、interfaceB()、interfaceC()をオーバライドして実装しています。
処理内容は割愛で(;^ω^)
class Hoge(Template) : def _interfaceA(self) : # 処理A def _interfaceB(self) : # 処理B def _interfaceC(self) : # 処理C
最後にHogeクラスを使用するmainの例です。
この例ではHogeクラスのインスタンスを作成して、methodA()を呼び出しています。
if __name__ == '__main__' : hogo = Hogo() hogo.methodA() # interfaceA, B, Cが順番に呼び出される
HogeクラスはTemplateクラスを継承しているので、TemplateクラスのmethodA()が呼び出されます。
したがってHogeクラスで実装したinterfaceA()、interfaceB()、interfaceC()が順番に呼び出されます。
この例をwikiの言葉を借りてまとめると、
methodA()のように、「ある処理のおおまかなアルゴリズムをあらかじめ決めておいて」、
interfaceA()、interfaceB()、interfaceC()のように、別クラスで実装することで「そのアルゴリズムの具体的な設計をサブクラスに任せる」
という感じがTemplateMethodパターンとなります(・`ω´・)
サンプルプログラム
以下、サンプルプログラムです。
AbstractDisplayクラスがテンプレートクラス、displey()がテンプレートメソッドになります。
そしてCharDisplayとStringDisplayクラスがAbstractDisplayクラスを継承した実体クラスです。
CharDisplayは1文字、StringDisplayは文字列をdispley()で指定したように表示するようになっています。
そのために、それぞれAbstractDisplayクラスのインタフェースであるmyOpen()、myPrint()、myClose()をオーバライドして実装しています。
CharDisplayクラスのmyOpen()、myClose()は"***"、myPrint()ではコンストラクタで指定された文字を表示するようになっています。
またStringDisplayクラスではmyClose()は"---"、myPrint()ではコンストラクタで指定された文字列を表示するようになっています。
# coding: utf-8 from abc import ABC, ABCMeta, abstractmethod class AbstractDisplay(metaclass=ABCMeta) : @abstractmethod def myOpen(self) : pass @abstractmethod def myPrint(self) : pass @abstractmethod def myClose(self) : pass def displey(self) : self.myOpen() for i in range(3) : self.myPrint() self.myClose() class CharDisplay(AbstractDisplay) : def __init__(self, ch) : self.__ch = ch def myOpen(self) : print("***") def myPrint(self) : print(self.__ch) def myClose(self) : print("***") class StringDisplay(AbstractDisplay) : def __init__(self, str) : self.__str = str def myOpen(self) : print("---") def myPrint(self) : print(self.__str) def myClose(self) : print("---") if __name__ == '__main__' : cd = CharDisplay('T') sd = StringDisplay("Design Pattern") sd2 = StringDisplay("Template Method") cd.displey() sd.displey() sd2.displey()
実行結果は以下になります!
CharDisplayのdispley()とStringDisplayで表示結果は異なるけれど(myOpen()、myPrint()、myClose()の実装は異なるけれど)、アルゴリズムは同じ(displey()でmyOpen()、myPrint()、myClose()を使用する手順は同じ)であるとわかります(・`ω´・)v
$ python TemplateMethod.py *** T T T *** --- Design Pattern Design Pattern Design Pattern --- --- Template Method Template Method Template Method ---
- 価格: 2860 円
- 楽天で詳細を見る
Pythonでデザインパターン 弐ノ型「Adapter」
今回はPythonの勉強しながらデザインパターンのAdapterをやってみました。
他のパターンにつきましては、以下の目次記事をご参照ください。
Adapterとは
直訳すると「適合させるもの」という意味になりますが、
直訳だと少しわかりにくいですね(;´Д`)
wikiには「既存のクラスに対して修正を加えることなく、インタフェースを変更することができる」とあります。
Adapterについて考えてみる
アダプタと言われると、身近なものではACアダプタが思いつきます。
ACアダプタはコンセントからとったAC(交流)をDC(直流)に変換し、電子機器に電源を供給するための装置です。
このことからアダプタとは、何かからの入力を、変換して、別の何かで使えるようにするための仲介役であると捉えることができますね。
デザインパターンにおけるAdapterパターンもこの考え方を当てはめると、
クラスAがクラスBを使用したい場合、AとBの仲介役として、Bからの入出力を吸収(変換)して、AにBの機能を提供するためのパターンという感じです。
サンプルプログラム
AdapterパターンをPythonで実装してみました。
この例ではHumanクラスのStudentインタフェースでラップしたAdapterを作っています。
Adapterパターンの実現方法には継承を利用するパターンと委譲を利用するパターンがあります。
HumanAdapterクラスは継承を利用するパターンです。
こちらはHumanクラスを継承しています。
そしてStudentインタフェースshowName()とshowAge()を実装するために、親クラスであるHumanクラスのprintName()とprintAge()を呼び出しています。
一方、HumanAdapter2クラスは委譲を利用するパターンです。
こちらはコンストラクタ内でHumanクラスのインスタンスを作成しています。
そしてStudentインタフェースshowName()とshowAge()を実装するために、HumanクラスのインスタンスのprintName()とprintAge()を呼び出しています。
# coding: utf-8 from abc import ABC, ABCMeta, abstractmethod class Human(object) : def __init__(self, name, age) : self.__name = name self.__age = age def printName(self) : print(self.__name) def printAge(self) : print(self.__age) class Student(metaclass=ABCMeta) : @abstractmethod def showName() : pass @abstractmethod def showAge() : pass class HumanAdapter(Human, Student) : def __init__(self, name, age) : Human.__init__(self, name, age) def showName(self): self.printName() def showAge(self) : self.printAge() class HumanAdapter2(Student) : def __init__(self, name, age) : self.__human = Human(name, age) def showName(self): self.__human.printName() def showAge(self) : self.__human.printAge() if __name__ == "__main__" : obj = HumanAdapter("AAA", 123) obj.showName() obj.showAge() obj2 = HumanAdapter2("BBB", 987) obj2.showName() obj2.showAge()
実行結果は以下のとおりです!
継承でも委譲でもAdapterの使い方は同じです。
$ python Adapter.py AAA 123 BBB 987
- 価格: 2640 円
- 楽天で詳細を見る
Pythonでデザインパターン 壱ノ型「Iterator」
今回はPythonの勉強目的でデザインパターンのIteratorをやってみました。
他のパターンにつきましては、以下の目次記事をご参照ください。
Iteratorとは
直訳だと「繰り返す」や「反復」という意味になります。
wikiには「コンテナオブジェクトの要素を列挙する手段を独立させることによって、コンテナの内部仕様に依存しない反復子を提供する」とあります。
Iteratorについて考えてみる
プログラミング言語には配列、リスト、ハッシュテーブルなどのデータを1つのオブジェクト(集合体)にまとめる仕組みがあります。
しかし、データへアクセスする方法はオブジェクト毎に異なるので、オブジェクトを利用するユーザは「オブジェクト毎にデータにアクセスする処理」を書かなければいけなくなります。
例えば、以下のような感じです。
添え字を数値としてアクセスする一般的な配列 = arry[N] 添え字をキーワードとしてアクセスする連想配列 = arry["keyword"] アクセッサーをもつオブジェクト = arry.get(N) などなど
そこで「共通の方法でオブジェクト(集合体)にアクセスしてデータを取り出す仕組みを提供するよ!」といっているのがIteratorパターンかなと思っています。
イメージとしては以下のような感じです。
if __name__ == '__main__': dataA = list(range(5)) # 0, 1, 2, 3, 4 dataB = {"AAA" : 1, "BBB" : 2, "CCC" : 3, "DDD" : 4} # dataAに対するIteratorを作成 itr = Iterator(dataA) while itr.hasNext() : # 次のデータがなくなるまで繰り返し print(itr.next()) # データを取り出して次へ # dataBに対するIteratorを作成 itr = Iterator(dataB) while itr.hasNext() : # 次のデータがなくなるまで繰り返し print(itr.next()) # データを取り出して次へ
整数型の配列dataAと文字列型をキーとする連想配列集dataBがあったとします。
このイメージだと、まずdataAに対するIteratorというオブジェクトを作成しています。
dataAへのアクセスはIteratorオブジェクトが持つhasNext()とnext()というメソッドを使用して行います。
この例では、hasNext()は集合体から読み出せるデータがあるかどうかを判定する機能、
next()は集合体から次のデータを取り出す機能を提供しています。
hasNext()を使ってデータが無くなるまで繰り返しnext()を呼び出しています。
その後、集合体dataBに対するIteratorオブジェクトを作成して同様のことを行います。
これによりユーザは、dataAでもdataBでも共通の方法で集合体にアクセスできるようになります。
サンプルプログラム
以下、サンプルプログラムです。
StudentListが集合体クラスで、Studentが集合体で管理するデータクラスです。
MyStudentListはStudentListを継承しているため集合体としても機能するようにもなっており、
iterator()メソッドで自分自身を渡してMyStudentListIteratorを生成するようになっています。
MyStudentListIteratorは生成されたタイミングでMyStudentListのインスタンスを保持します。
このインスタンスを使って、next()やhasNext()で集合体を操作する機能を実現しています。
# coding: utf-8 from abc import ABC, ABCMeta, abstractmethod # Iteratorインタフェース class Iterator(metaclass=ABCMeta) : @abstractmethod def hasNext() : pass @abstractmethod def next() : pass # Aggregateインタフェース class Aggregate(metaclass=ABCMeta) : @abstractmethod def iterator() : pass # データクラス class Student(object) : def __init__(self, name, sex) : self.__name = name self.__sex = sex def getName(self) : return self.__name def getSex(self) : return self.__sex # データリストクラス(集合体) class StudentList(object) : def __init__(self) : self.__students = [] def add(self, student) : self.__students.append(student) def getStudentAt(self, index) : return self.__students[index] def getLastNum(self) : return len(self.__students) # Aggregateインタフェースを実装したクラス class MyStudentList(StudentList, Aggregate) : def __init__(self) : StudentList.__init__(self) def iterator(self) : return MyStudentListIterator(self) # Iteratorインタフェースを実装したクラス class MyStudentListIterator(Iterator) : def __init__(self, list) : self.__myStudentList = list self.__index = 0 def hasNext(self) : if self.__myStudentList.getLastNum() > self.__index : return True else : return False def next(self) : student = self.__myStudentList.getStudentAt(self.__index) self.__index += 1 return student # ユースケース if __name__ == '__main__': list = MyStudentList() list.add(Student("AAA", 1)) list.add(Student("BBB", 0)) list.add(Student("CCC", 1)) itr = list.iterator() while itr.hasNext() : print(itr.next().getName())
実行結果は以下のとおりです。
$ python Iterator.py AAA BBB CCC
Pythonを勉強しながらデザインパターンを勉強してみる
最近、Pythonの勉強を始めてみました。
私は新しい言語を覚えるとき、とにかく真似てもいいから色んなコードを書いて、その言語の雰囲気を味わうようにしています。
そんな勉強方法が良いかはさておき(・`ω´・;)
今回も色んなコードを書いてみるところから始めたいと思いますが、基本的な文法や考え方は他の言語を使っているからなんとなくわかる気がします。
なので、今回は題材としてデザインパターンをPythonで書いていってみようかと思います。
実は、長らくプログラマーをやっていたのにデザインパターンも真面目に勉強したことがなかったのでこの機会に一緒にお勉強してみたいと思います(;´Д`)
使うかどうかはさておき(・`ω´・;)
デザインパターンとは
かつて、プログラミングの達人たちが自らの経験のもとに、「こういうときはこういう作り方をしたほうがいいよね!」といった実装のパターンを集めたノウハウ集です。
23の基本パターンから成ります。
その全てのパターンが、状況によっては必ずしも最善ではないかもしれませんが、プログラムを齧っている人ならば全パターンを知っておきたいところです。
使うかどうか、役に立つかどうか、はさておき(・`ω´・;)
Pythonでデザインパターン一覧
※他の記事を追加したときはここにリンクを貼っていきたいと思います。
今、書けているのは、以下のとおり。
ゆっくり増やしていきたいと思うので温かい目で見守っていただけると助かります
参考書籍
以下を参考にさせてきただきました。
Java言語で学ぶデザインパターン入門増補改訂版 [ 結城浩 ]
- 価格: 4180 円
- 楽天で詳細を見る