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 円
- 楽天で詳細を見る