はじめに
この記事は個人的な勉強メモです。inputしたものはoutputしなくてはという強迫観念に駆られて記事を書いています。
あわよくば詳しい人に誤りの指摘やアドバイスを頂ければいいなという思いを込めてQiitaの記事にしています。
エンジニアとして社会人生活を送っていますが、デザインパターンについてちゃんと学んだことがなかったので勉強してみました。
ここに記載している内容は
https://github.com/ck-fm0211/notes_desigh_pattern
にuploadしています。
過去ログ
デザインパターンについて勉強してみた(個人的メモ)その1
デザインパターンについて勉強してみた(個人的メモ)その2
Prototypeパターン
- あらかじめ用意しておいた「原型」からインスタンスを生成するようにするためのパターン
- 例:
- 「直線を描く」機能しか持たない図形エディターを考える。
- この図形エディターで星型を書きたいときには、直線を組み合わせることで、星の形を作成していくことになる。
- 星型がいくつも欲しいときは、直線を組み合わせて星型を描くという作業を何度も繰り返す必要が出てくる。
- こんなとき、最初に作成した星型(直線の集まり)を「プロトタイプ」として登録しておき、これをコピーすることで星型が作成できれば、作業が簡略化できる
実際に使ってみる
題材
- インスタンスの中身ごとコピーしたい
# -*- coding:utf-8 -*-
import copy
def main():
report1 = Report("title1", "content")
report2 = copy.deepcopy(report1)
print("title:", report1.title, report2.title)
print("content:", report1.content, report2.content)
class Report:
def __init__(self, title, content):
self.title = title
self.content = content
if __name__ == "__main__":
main()
title: title1 title1
content: content content
- pythonの場合、
copy.deepcopy
を利用することでインスタンスの中身ごとコピーができる - さらに拡張して、文字列を囲ったりするプログラムを書いてみる
# -*- coding:utf-8 -*-
import copy
class Product(object):
def use(self, s):
pass
def createClone(self):
pass
class Manager(object):
__showcase = dict()
def register(self, name, proto):
self.__showcase[name] = proto
def create(self, protoname):
p = self.__showcase.get(protoname)
return p.createClone()
class MessageBox(Product):
def __init__(self, decochar):
self.decochar = decochar
def use(self, s):
length = len(s)
deco = self.decochar * (length + 4)
print(deco)
print(self.decochar, s, self.decochar)
print(deco)
def createClone(self):
p = copy.deepcopy(self)
return p
class Underline(Product):
def __init__(self, ulchar):
self.ulchar = ulchar
def use(self, s):
length = len(s)
print('"%s"' % s)
print(" %s " % (self.ulchar * length))
def createClone(self):
p = copy.deepcopy(self)
return p
if __name__ == "__main__":
manager = Manager()
uline = Underline(".")
mbox = MessageBox("*")
sbox = MessageBox("/")
pbox = MessageBox("+")
uline2 = Underline("=")
manager.register("strong message", uline)
manager.register("star box", mbox)
manager.register("slash box", sbox)
manager.register("plus box", pbox)
manager.register("under line", uline2)
p1 = manager.create("strong message")
p1.use("Hello, world.")
p2 = manager.create("star box")
p2.use("Hello, world.")
p3 = manager.create("slash box")
p3.use("Hello, world.")
p4 = manager.create("plus box")
p4.use("Hello, world.")
p5 = manager.create("under line")
p5.use("Hello, world.")
- 結果
"Hello, world."
.............
*****************
* Hello, world. *
*****************
/////////////////
/ Hello, world. /
/////////////////
+++++++++++++++++
+ Hello, world. +
+++++++++++++++++
"Hello, world."
=============
Prototypeパターンのまとめ
Builderパターン
- 同じ作成過程で異なる表現形式の結果を得るためのパターン
- 例:
- 家を建てることを考える
- 家を建てるには「何を使って(=材料)」「どう建てるか(=工程)」が必要
- 材料には木材、コンクリート、瓦・・・・など色々ある
- 工程には平屋、2階建て、変わった家・・・・など色々ある
- これらのパターンを予め用意しておくことで『 "ちょっと変わった平屋を建てる作成過程" で "柱は鉄で壁と屋根はコンクリート" の家を建ててください』という要望に柔軟に応えられる
- Builder パターンとは、このような、「作成過程」を決定する Director と呼ばれるものと「表現形式」を決定する Builder と呼ばれるものを組み合わせることで、オブジェクトの生成をより柔軟にし、そのオブジェクトの「作成過程」をもコントロールすることができるようにするためのパターン
実際に使ってみる
題材
- 理科の実験で、食塩水と砂糖水を作ることを考える
- 食塩水 をあらわすクラスは以下のソースコードで与えられているものとする(砂糖水をあらわすクラスも同様とする)
# -*- coding:utf-8 -*-
class SaltWater:
salt = None
water = None
@staticmethod
def salt_water(water, salt):
SaltWater.salt = salt
SaltWater.water = water
-
さまざまな要求が考えられるが、Builderパターンを利用することで、以下のような要求に応えることができるようになる。
- 同様の作成過程で得られる溶液を何度も利用したい。
- 同じ方法で砂糖水を作成したい。
-
このような要求に応えるため、Builder パターンでは、Director と Builder となるクラスを作成する。
- Director の役割は「作成過程」を決定することで、Builder の役割は「表現形式」を決定すること。
-
サンプルケースでは、Director の役割は、「100g の溶媒に 40g の溶質を溶かし、うち 70g を捨てた後、溶媒を 100g 追加し、最後に溶質を 15g 加える」ということを決定すること。
-
また、Builder の役割は、「溶媒に水、溶質に食塩」を使うことを決定すること。
-
Builderインタフェースは以下のようになる
from abc import ABCMeta, abstractmethod
class Builder(metaclass=ABCMeta):
@abstractmethod
def add_solute(self):
pass
@abstractmethod
def add_solvent(self):
pass
@abstractmethod
def avandon_solution(self):
pass
@abstractmethod
def get_result(self):
pass
- Builder インタフェースでは、溶質を追加するための addSolute メソッド、溶媒を追加するための addSolvent メソッド、溶液を捨てるための abandonSolution メソッド、そして、生成物を得るための、getResult メソッドを定義している。
- Director クラスでは Builder インタフェースを利用して、「作成過程」にのっとって、インスタンスを組み立てていく。
class Director:
def __init__(self):
self.builder = Builder()
def constract(self):
self.builder.add_solvent(100)
self.builder.add_solute(40)
self.builder.abandon_solution(70)
self.builder.add_solvent(100)
self.builder.add_solute(15)
- Director クラスは、Builder インタフェースを実装するものが与えられるということを知っているだけで、実際には、どの Builder 実装クラスが渡されるのかということを知っている必要がない。これにより、Builder のすげ替えが簡単に行えるようになっている。
- Builder実装クラスである、SaltWaterBuilder クラスは以下のようになる。
class SaltWaterBuilder(Builder):
def __init__(self):
self._salt_water = SaltWater(0,0)
def add_solute(self, salt_amount):
self._salt_water.salt += salt_amount
def add_solvent(self, water_amount):
self._salt_water.water += water_amount
def abandon_solution(self, salt_water_amount):
salt_delta = salt_water_amount * (self._salt_water.salt / (self._salt_water.salt + self._salt_water.water))
water_delta = salt_water_amount * (self._salt_water.water / (self._salt_water.salt + self._salt_water.water))
self._salt_water.salt -= salt_delta
self._salt_water.water -= water_delta
def get_result(self):
return self._salt_water
-
このような設計にしておくことで、Director と Builder を自由に組み合わせ、より柔軟にインスタンスを生成することができるようになる。
-
例えば、文書の作成手順を知る Director と、HTML用に出力する HTMLBuilder、プレーンテキストを出力する PlainTextBuilder などを用意しておくことで、同じ文書を要求に合わせて、異なる表現形式で出力することができるようになる。