1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

デザインパターンについて勉強してみた(個人的メモ)その3(Prototypeパターン、Builderパターン)

Posted at

はじめに

この記事は個人的な勉強メモです。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パターンのまとめ

class_image

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 などを用意しておくことで、同じ文書を要求に合わせて、異なる表現形式で出力することができるようになる。
    class_image1

Builderパターンのまとめ

class_image2

1
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?