📝

[学習メモ]Pytho3で学ぶデザインパターン~構造に関するデザインパターン~

2022/05/14に公開約20,900字

この記事について

構造に関するデザインパターン7種類について、個人の学習メモです。
(恥ずかしながら初勉強です・・・自身の整理のためにもまとめていこうと思います)

概要

デザインパターンについて

簡単にいうと設計のノウハウ集です。

OOPL(オブジェクト指向プログラミング)によって、ソフトウェアの再利用部品を作ることが可能になりました。クラスライブラリやフレームワークと呼ばれているものです。このクラスライブラリやフレームワークを作成する過程で、機能拡張が容易で再利用がしやすい先人たちの設計アイデアから共通部分を抜き出して(抽象化して)、設計パターンとしてまとめたものです。

ここでは広く一般的に知られている、優れた技術者4人GoF(ゴフ、ギャングオブフォー)が書いた「書籍:デザインパターン」にて発表されたGoFのデザインパターンについて扱います。

取り扱い注意事項

  1. 手段を目的にしない
    目的(問題を解決したい)解決する手段(デザインパターンを使用する) です。デザインパターンはよくある問題を解決する手段(糸口)です。
  2. デザインパターンを当てはめて問題を探さない(あら探し)
    問題を探す手段ではなく解決手段です。
  3. 銀の弾丸はありません

GoFのデザインパターン

生成に関するデザインパターン

  • Factory Method
  • Abstract Factory
  • Builder
  • Prototype
  • Singleton

構造に関するデザインパターン  <- この記事です。

  • Adapter
  • Bridge
  • Composite
  • Decorator
  • Facade
  • Flyweight
  • Proxy

振る舞いに関するデザインパターン

  • Chain of Responsibility
  • Command
  • Interpreter
  • Iterator
  • Mediator
  • Memento
  • Observer
  • State
  • Strategy
  • Template Method
  • Visitor

構造に関するデザインパターン

そもそも、「構造に関するデザインパターン」って何でしょうか?
まずはこの言葉について知ります。

2より引用

「構造に関するデザインパターン」の主題は、いかにして複数のオブジェクトから、より大きな新しいオブジェクトを構築するか、ということにある。「構造に関するデザインパターン」には3つのテーマがある。それは、「インターフェースの適合」「機能の追加」「オブジェクト集合の操作」の3つである。

5より引用

Structural patterns are mostly concerned with object composition or in other words how the entities can use each other. Or yet another explanation would be, they help in answering "How to build a software component?"

言いたいことを察するに、構造に関するデザインパターンは「生成したオブジェクトを組み合わせてより大きなオブジェクトを構築するためのオブジェクトの構成思想」について書かれたものだと思います。

つまり、構造に関するデザインパターンとは 「オブジェクト同士の関係で問題が起きているんだけど、オブジェクトの構成はどうすればいいんだろう?」といったことに対して解決する手段(糸口) をパターンとして示しています。

Adapterパターン

利用場面

4より引用

GoFによれば,Adapter パターンの目的は, 「あるクラスのインタフェースを,クライアントが求める他のインタフェースへ変換する。Adapter パターンは,インタフェースに互換性のないクラス同士を組み合わせることができるようにする。」
GoFによれば,Adapter パターンの別名は,Wrapper パターンだそうだ。
GoFによれば,Adapter パターンは,次のような状況で使うことができる。
・既存のクラスを利用したいが,そのインタフェースが必要なインタフェースと一致していない場合。
・まったく無関係で予想もつかないようなクラス(必ずしも互換性のあるインタフェースを持つとは限らない)とも協調していける,再利用可能なクラスを作成したい場合。
・(オブジェクトに適用する Adapter パターンのみ)既存のサブクラスを複数利用したいが,それらすべてのサブクラスをさらにサブクラス化することで,そのインタフェースを適合させることが現実的でない場合。オブジェクトに適用する Adapter パターンでは,その親クラスのインタフェースを適合させればよい。

2より引用

このパターンが役に立つケースは、例えば想定されなかったコンテキストのなかで、相手のクラスを利用したいけれども、そのクラスを変更できない場合である。

概要は「インターフェースを利用して互換性のないクラス同士を組み合わせる」です。
利用シーンとしては「クラス同士に互換性をもたせたい場合」です。

具体的に考えてみる

アダプターは言葉の意味そのものだと思います。
例えば、「USB->HDMIの変換アダプター」、「Type-C->HDMI変換アダプター」、「通訳」、「翻訳機」など
つまり、互換性のないものを使えるように変換しているイメージです。

作成方法

    1. Adapteeを作る。AdapteeはAdaptorにつなぎたいクラスの具像です
    1. Adapterを作る。Adapterは異なるクラスに変換する具像です(インターフェース)

オブジェクト思考による作成方法のイメージ

今、TEXTを生成するクラスが存在するとします(TextCreater)。
クラスを変更せずにエクセル形式で保存したいとします。

実装方法としては2つあります。

  • 継承を利用する
  • 委譲を利用する

python3で書いてみる(継承)

from abc import ABC, abstractmethod
import csv

class TextCreater:
    '''Adaptee
    '''
    def __init__(self):
        self.contents = []

    def write_to_txt(self):
        with open("sample.txt", mode="w") as f:
            f.writelines(self.contents)

    def add_contet(self,content):
        self.contents.append(content)


class Adapter(ABC):
    """Adapter(インターフェース)
    """

    @abstractmethod
    def write_to_csv(self):
        pass

class CsvAdapter(Adapter, TextCreater):
    """Adapterの具像
    """    
    def write_to_csv(self):
        with open("sample.csv", mode='w', encoding='utf-8') as f:
            writer = csv.writer(f)
            writer.writerow(self.contents)

csvadapter = CsvAdapter()
csvadapter.add_contet("Hoge Fuga.")
csvadapter.add_contet("Piyo Poyo.")
csvadapter.write_to_txt()
csvadapter.write_to_csv()

python3で書いてみる(委譲)

from abc import ABC, abstractmethod
import csv

class TextCreater:
    '''Adaptee
    '''
    def __init__(self):
        self.contents = []

    def write_to_txt(self):
        with open("sample.txt", mode="w") as f:
            f.writelines(self.contents)

    def add_contet(self,content):
        self.contents.append(content)


class Adapter(ABC):
    """Adapter(インターフェース)
    """

    @abstractmethod
    def write_to_csv(self):
        pass

class CsvAdapter(Adapter):
    """Adapterの具像
    """
    def __init__(self, text_creater: TextCreater):
        self.text_creater = text_creater
    
    def write_to_csv(self):
        with open("sample.csv", mode='w', encoding='utf-8') as f:
            writer = csv.writer(f)
            writer.writerow(self.text_creater.contents)

# 以下、Client役
text = TextCreater()
text.add_contet("Hoge Fuga.")
text.add_contet("Piyo Poyo.")
text.write_to_txt()

csv_adapter = CsvAdapter(text)
csv_adapter.write_to_csv()

Bridgeパターン

利用場面

4より引用

GoFによれば,Bridge パターンの目的は, 「抽出されたクラスと実装を分離して,それらを独立に変更できるようにする。」
GoFによれば,Bridge パターンの別名は,Handle/Body パターンだそうだ。
GoFによれば,次のような場合に,Bridge パターンを利用する。
・抽出されたクラスとその実装を永続的に結合することを避けたい場合。たとえば,実装を実行時に選択したり交換したりしなければならないときに,このような場合が起こり得る。
・抽出されたクラスとその実装の両方を,サブクラスの追加により拡張可能にすべき場合。この場合,Bridge パターンを用いることで,抽出されたクラスに異なる実装を結合したり,それぞれを独立に拡張することが可能になる。
・抽出されたクラスの実装における変更が,クライアントに影響を与えるべきではない場合。すなわち,クライアントのコードを再コンパイルしなくても済むようにすべき場合。
(以下省略)

2より引用

機能と実装を分離したい場合、Bridgeパターンが用いられる

概要は「継承を使用せずに集約を利用して、機能と実装を分離し機能拡張の柔軟性を担保する」です。
利用シーンとしては「機能と実装を分離したい場合(機能拡張を柔軟にしたい)」です。

具体的に考えてみる

継承して機能追加することを想像するとわかりやすいかと思います。
ここでは3のWikiより引用した説明内容と同じです。
食器にはボール機能があります。ボール機能を継承して、ガラス製で作る機能と木製で作る機能があるとします。(左の図)
ここに皿機能を追加しました。皿機能を継承して、ガラス製で作る機能と木製で作る機能を作成しました。(右の図)

ここで木製で作る機能を修正することにしました。すると、修正箇所は2箇所となります。保守性が低そうです。
この例だと2階層でしたが、木製で作る機能を継承して漆器作成機能を作成して・・・などと階層が深くなると修正が他の機能にまで影響を与えてしまいます。

また、食器の下にプラスチックの機能を追加するたびにクラスが横展開していき修正する箇所も倍になります。つまり、機能拡張するのが大変になります。

以下のようにクラスを分けて機能追加すれば、機能と実装を分離することが可能になります。
材料の情報を食器に渡す(橋渡しのイメージ)ようにすれば、機能と実装を分離できます。

作成方法

    1. Implementerを作成する。Implementerは機能のインターフェースです。
    1. ConcreteImplementerを作成する。Implementerを継承した具像クラス
    1. Abstractionを作成する。追加実装される機能のインターフェース
    1. RefinedAbstractionを作成する。Abstractionの具像クラス

オブジェクト思考による作成方法のイメージ


ガラス製か、木製かの機能が追加されたイメージです。

python3で書いてみる

from abc import ABC,abstractclassmethod

class Material(ABC):
    """Abstraction
    """
    def __init__(self,material):
        self.material = material
    @abstractclassmethod
    def get_material_info(self):
        pass

class Wood(Material):
    """RefinedAbstraction
    """
    def get_material_info(self):
        return self.material

class Glass(Material):
    """RefinedAbstraction
    """
    def get_material_info(self):
        return self.material

class Dishware(ABC):
    '''Implementer
    '''
    def __init__(self, material: Material):
        self._material = material

    @abstractclassmethod
    def create(self):
        pass

class Plate(Dishware):
    '''ConcreteImplementer
    '''
    def create(self):
        return print('{}で皿を作成しました。'.format(self._material.get_material_info()))

class Bowl(Dishware):
    '''ConcreteImplementer
    '''
    def create(self):
        return print('{}でボールを作成しました。'.format(self._material.get_material_info()))
wood = Wood('木材')
glass = Glass('ガラス')

wood_plate = Plate(wood)
glass_plate = Plate(glass)
wood_plate.create()
glass_plate.create()
wood_bowl = Bowl(wood)
glass_bowl = Bowl(glass)
wood_bowl.create()
glass_bowl.create()

Compositeパターン

利用場面

4より引用

Composite とは合成物や複合体のようなニュアンスがある。Composite パターンはツリー構造の一種であるが,ノードが2種類あるので Composite パターンと言われる。

GoFによれば,Composite パターンの目的は, 「部分―全体階層を表現するために,オブジェクトを木構造に組み立てる。Composite パターンにより,クライアントは,個々のオブジェクトとオブジェクトを合成したものを一様に扱うことができるようになる。」
GoFによれば,次のような場合に,Composite パターンを使用する。
・オブジェクトの部分―全体階層を表現したい場合。
・クライアントが,オブジェクトを合成したものと個々のオブジェクトの違いを無視できるようにしたい場合。このパターンを用いることで,クライアントは,composite 構造内のすべてのオブジェクトを一様に扱うことができるようになる。

2より引用(一部抜粋)

Compositeパターンを用いれば、階層構造からなるオブジェクトに対して統一した操作を行える。

概要は「ツリー構造を表現し容器と中身を同一視して扱う」です。
利用シーンとしては「ツリー構造に対して再起的な処理をしたい場合」です。

具体的に考えてみる

ファイルシステムの「フォルダ(容器)とファイル(中身)の関係と操作方法」を想像してもらえると良いと思います。

作成方法

    1. Componentの作成。CompositeとLeafの共通機能を持ったクラス(抽象)
    1. Compositeの作成。Componentを継承した容器を表すクラス(具像)
    1. Leafの作成。Componentを継承した中身を表すクラス(具像)
    1. Compositeをインスタンス化し、Composite、Leafを子要素として追加してく

オブジェクト思考による作成方法のイメージ

ここではファイルシステムを例とします。

python3で書いてみる

from abc import ABC, abstractmethod

class Component(ABC):
    @property
    @abstractmethod
    def name(self):
        pass

    @property
    @abstractmethod
    def size(self):
        pass

    @abstractmethod
    def print_list(self, path):
        pass
    
    def __str__(self):
        return "{} ({})".format(self.name, self.size)

class File(Component):
    '''Leaf
    '''
    def __init__(self, name, size):
        self._name = name
        self._size = size

    @property
    def name(self):
        return self._name
    
    @property
    def size(self):
        return self._size
    
    def print_list(self, path=''):
        print(path + '/' + str(self))

class Folder(Component):
    def __init__(self, name):
        self._name = name
        self.folders = []
    
    @property
    def name(self):
        return self._name
    
    @property
    def size(self):
        file_size = 0
        for folder in self.folders:
            file_size += folder.size
        return file_size
    
    def add_child(self, child):
        self.folders.append(child)
    
    def print_list(self, path=''):
        print(path + '/' + str(self))
        for folder in self.folders:
            folder.print_list(path + '/' + self._name)

root_dir = Folder("root")
bin_dir = Folder("bin")
user_dir = Folder("user")
root_dir.add_child(bin_dir)
root_dir.add_child(user_dir)

root_dir.add_child(File("sample.txt", 100))
user_dir.add_child(File("sample.csv", 500))
root_dir.print_list()

実行結果

/root (600)
/root/bin (0)
/root/user (500)
/root/user/sample.csv (500)
/root/sample.txt (100)

Decoratorパターン

利用場面

4より引用

簡単に言うと,継承を使わないで,既存クラスをインスタンス化してそれのメソッドを呼び出すことによって機能を追加することが Decorator パターンです。

GoFによれば,Decorator パターンの目的は, 「オブジェクトに責任を動的に追加する。Decorator パターンは,サブクラス化よりも柔軟な機能拡張方法を提供する。」
GoFによれば,Decorator パターンの別名は,Wrapper パターンである。
GoFによれば,次のような場合に Decorator パターンを利用する。
・個々のオブジェクトに責任を動的,かつ透明に(すなわち,他のオブジェクトには影響を与えないように)追加する場合。
・責任を取りはずすことができるようにする場合。
・サブクラス化による拡張が非実用的な場合。非常に多くの独立した拡張が起こり得ることがある。このような場合,サブクラス化によりすべての組み合わせの拡張に対応しようとすると,莫大な数のサブクラスが必要になるだろう。また,クラス定義が隠ぺいされている場合や入手できない場合にも,このパターンを利用できる。

6より引用

Decorator pattern lets you dynamically change the behavior of an object at run time by wrapping them in an object of a decorator class.
(翻訳)Decoratorパターンでは、オブジェクトをdecorator classのオブジェクトでラップすることで、実行時にオブジェクトの振る舞いを動的に変更することができる

機能追加に関するパターンのようです。
概要は「継承を利用せずに機能を追加する」です。
利用シーンとしては「既存のクラスの持っている機能を別のクラスに追加したい場合(機能をクラスを跨いで使用したい)」です。

具体的に考えてみる

コーヒーを作成する機能があるとして、そこにミルクを追加してコーヒー牛乳を作成する新たな機能を追加するとします。コーヒーにミルクをデコレート(付け足す)してコーヒー牛乳機能を作成するイメージがDecoratorパターンです。

また、python3の標準機能であるdecoratorもdecoratorパターンで作成されています

作成方法

    1. Componentを作成する。インターフェース
    1. ConcreteComponentを作成する。Componentを継承した、Componentの具像
    1. Decoratorを作成する。ComPonentを継承したクラス。Componentをプロパティに持つ
    1. ConcreteDecoratorを作成する。Decoratorの具像

オブジェクト思考による作成方法のイメージ

コーヒーとコーヒー牛乳の例について、作成方法をベースに図示すると以下のような関係になります。

python3で書いてみる

from abc import ABC, abstractmethod

class Coffee(ABC):
    '''Component(インターフェース)
    '''
    @abstractmethod
    def get_cost(self):
        pass
    @abstractmethod
    def get_description(self):
        pass

class SimpleCoffee(Coffee):
    '''ConcreteComponent(具像クラス)
    '''
    def get_cost(self):
        return 10
    def get_description(self):
        return 'Simple coffee'

class MilkDecorator(Coffee):
    '''Decorator
    '''
    def __init__(self, coffee:Coffee):
        self.coffee = coffee

class MilkCoffee(MilkDecorator):
    '''ConcreteDecorator(具像クラス)
    '''
    def __init__(self, coffee:Coffee):
        super().__init__(coffee)

    def get_cost(self):
        return self.coffee.get_cost()+5
    def get_description(self):
        return 'Milk Coffee'

def client():
    coffee = SimpleCoffee()
    coffee_cost = coffee.get_cost()
    coffee_description = coffee.get_description()
    print(coffee_cost, coffee_description)
    milk_coffee = MilkCoffee(coffee)
    milk_coffee_cost = milk_coffee.get_cost()
    milk_coffee_description = milk_coffee.get_description()
    print(milk_coffee_cost, milk_coffee_description)

client()

実行結果

10 Simple coffee
15 Milk Coffee

Facadeパターン

利用場面

4より引用

Facade ファサードは仏語で建物の正面という意味です。Facade パターンは,複雑な手順の作業を統一した手段でできるようにいわば「入口(窓口)を1つにする」ということを可能にします。

GoFによれば,Facade パターンの目的は, 「サブシステム内に存在する複数のインタフェースに1つの統一インタフェースを与える。Facade パターンはサブシステムの利用を容易にするための高レベルインタフェースを定義する。」

6より引用

In plain words
Facade pattern provides a simplified interface to a complex subsystem.
(翻訳)簡単に言うと、ファサードパターンは、複雑なサブシステムへの簡素化されたインターフェイスを提供します

概要は「複雑なサブシステム達を利用するための入口を1つに統一する」です。
利用シーンとしては「複雑なサブシステムを取り扱う際に、シンプルなインターフェースを提供したい場合。サブシステムを階層化したい場合」です。

*非常に単純なので「具体的に考えてみる」は省略します。

作成方法

    1. 各クラスを利用するクラス(シンプルなインターフェース)を作成する。

オブジェクト思考による作成方法のイメージ

3のwikiにある。ドライブシュミレータの例を使わせていただきます。

ドライブシュミレータ(入口)がサブクラス(Car,Driver)を利用することで1つに統一されています。

python3で書いてみる

class Car:
    def __init__(self):
        self.sppeed = 0
        self.distance = 0
    
    def set_speed(self,speed):
        self.sppeed = speed

    def run(self, minutes):
        self.distance += minutes * self.sppeed
    
    def get_distance(self):
        return print('距離:{}m'.format(self.distance))

class Driver:
    def __init__(self,car:Car):
        self.car = car
    
    def push_pedal(self,speed):
        self.car.set_speed(speed)

    def drive(self,minutes):
        self.car.run(minutes)
        
class DrivingSimulator:
    '''Facade
    '''
    def __init__(self, car:Car):
        self.car = car
        self.driver = Driver(car)
    
    def simulate(self):
        self.driver.push_pedal(100)
        self.driver.drive(30)
        self.car.get_distance()
car = Car()
drive_simulator = DrivingSimulator(car)
drive_simulator.simulate()

実行結果

距離:3000m

Flyweightパターン

利用場面

4より引用

GoFによれば,Flyweight パターンの目的は, 「多数の細かいオブジェクトを効率よくサポートするために共有を利用する。」

Flyweight とはボクシングの最軽量級のことです。 ~中略~ Flyweight パターンの目的はオブジェクトを軽くすること,つまり,メモリリーク対策なのです。実は「GoFの23のデザインパターン」の中で最重要なのです。

概要は「オブジェクトを効率よく共有してメモリ使用量を少なくする」です。
利用シーンとしては「重複するオブジェクトを効率よく操作したい場合、メモリの節約をしたい場合」です。

具体的に考えてみる

6より引用

Real world example
Did you ever have fresh tea from some stall? They often make more than one cup that you demanded and save the rest for any other customer so to save the resources e.g. gas etc. Flyweight pattern is all about that i.e. sharing.

(機械翻訳)屋台で淹れたてのお茶を飲んだことがありますか?ガスなどの資源を節約するため、お客様が必要とされる1杯分以上を作り、残りは他のお客様のために取っておくこともよくあるそうです。フライウェイトパターンはそれがすべてです。つまり共有です。

読んだとき「もう淹れたてじゃないよね?」ってつっこみたくなりましたが・・・重要なのは共有するところです(*余談:日本だと屋台でお茶を楽しめる場所は見たことがないですが、海外だと普通なのかもしれません)

上記のイメージが理解できないのであれば、ラーメン屋の例が良いかもしれません。共通のベースとなるスープから各ラーメン(味噌、塩、醤油)に派生させているラーメン屋を想像してもらえるとわかりやすいです。ベースを作成し共有することで後はお客のオーダーに合わせて味を変化させるだけでいいので効率が良くなります。

作成方法

    1. Flyweightに該当するクラスを作成する: 共有した方がいいもの
    1. FlyweightFactoryを作成する:  FlyweightFactoryを使用するとオブジェクトは共有される状態

オブジェクト思考による作成方法のイメージ

引用元6の例を参考にしております。

コーヒーメーカー(CoffeeMaker)はFlyweightFactoryの役です。ここではコーヒー(Flyweight)が作成されます。
コーヒーはFlyweightの役です。つまり、共有したいものです。
コーヒーショップ(CoffeeShop)はコーヒーメーカーを利用するクラスです。

python3で書いてみる

class Coffee:
    '''FlyWeight
    '''
    pass

class CoffeeMaker:
    '''FlyWeightFactory
    '''
    __available_coffee = {}

    @classmethod
    def get_flyWeight(cls, preference):
        if preference not in cls.__available_coffee:
            coffee = Coffee()
            cls.__available_coffee[preference] = coffee
        return cls.__available_coffee[preference]

class CoffeeShop:
    __order = {}
    def __init__(self,coffee_maker:CoffeeMaker):
        self.coffee_maker = coffee_maker
        
    def take_order(self, coffee_type, table):
        self.__order[table] = self.coffee_maker.get_flyWeight(coffee_type)

    def serve(self):
        for key in self.__order.keys():
            print("テーブル{}にコーヒーを出す".format(key))
coffee_maker = CoffeeMaker()
shop = CoffeeShop(coffee_maker)
print(id(shop))
shop.take_order('砂糖なし', 1) 
shop.take_order('ミルクなし', 2)
shop.take_order('ミルクあり', 5)
shop.take_order('甘さ控えめ', 6)
shop.serve()
print(id(shop))

実行結果 
idは環境によって変わります。大切なのは番号が変わってないことです。

4369377504
テーブル1にコーヒーを出す
テーブル2にコーヒーを出す
テーブル5にコーヒーを出す
テーブル6にコーヒーを出す
4369377504

コーヒーを共有しコーヒーメーカーで味の変化(例えば、砂糖抜きなど)をつけてコーヒーを作成できるとしました。後は、コーヒーショップの定員は注文があったテーブル番号を覚えておけば大丈夫そうです。

Proxyパターン

利用場面

4より引用

GoFによれば,Proxy パターンの目的は, 「あるオブジェクトへのアクセスを制御するために,そのオブジェクトの代理,または入れ物を提供する。」

2より引用

Proxyパターンは、あるオブジェクトを別のオブジェクトの”代理人”としたい時に使用する。

概要は「代理処理するオブジェクトを立てる」です。
利用シーンとしては「オブジェクトへのアクセス制限したい場合。重いオブジェクトを必要な時に呼び出す構造にして軽くしたい場合(インスタンス化の処理が重い)」などです。

具体的に考えてみる

引用元6の例を参考にしております。
玄関のドアをスマートロックに対応することにします。今までは左図のように鍵を人がかけていました。スマートロックを導入したことでスマホが鍵の代わりとなります(右図)。

スマホ(実際はスマホの中に入ったスマートロックのアプリ)が「鍵と人の鍵をかけるという動作」の代理を行っていると言えます。このように代理処理するオブジェクト(スマホ)を利用するパターンがProxyパターンの一種となります。

一種と言っているのは、オブジェクトへのアクセス制限したい場合だけではないからです。GoFには4つの使用例が記載されています。

以下で簡単に紹介しますが、詳しくは調べていただくと良いと思います。

  • remote proxy:ネットワークの向こう側にいるリモートオブジェクトの代理をする形式
  • virtual proxy: 重いオブジェクトを生成する代わりに最初に軽いオブジェクトを作成する形式
  • protection proxy: オブジェクトへのアクセス制限する形式
  • smart reference:オブジェクトがアクセスされた時に追加処理をする形式

作成方法

    1. Subjectを作成する。インターフェース
    1. RealSubjectを作成する。Subjectを継承して具体化した処理を書く具像クラス
    1. Proxyを作成する。RealSubjectの代理でクライアントから求めに応じて処理を返す

オブジェクト思考による作成方法のイメージ

スマートロックドアを解錠する例で説明します。

Proxyはスマートロックの解錠アプリのようなものと考えてください。RealSubjectはスマートロック機能をつけたドアです。クライアントから施錠を求められたスマートロック解錠アプリはスマートロックドアの代理で鍵の解錠と施錠を行っています。

python3で書いてみる

from abc import ABC, abstractmethod

class Door(ABC):
    '''Subject
    '''
    @abstractmethod
    def open(self):
        pass

    @abstractmethod
    def close(self):
        pass

class SmartLockDoor(Door):
    '''RealSubject
    '''
    def open(self):
        print('ドアを解錠しました')


    def close(self):
        print('ドアを施錠しました')

class Security(Door):
    '''Proxy
    '''
    def __init__(self, smart_lock_door: SmartLockDoor):
        self.smart_lock_door = smart_lock_door

    def open(self, open_touch):
        if open_touch == '解錠ボタン':
            self.smart_lock_door.open()
        else:
            print('警告:ドアの解錠に失敗しました!')

    def close(self, close_touch):
        if close_touch == '施錠ボタン':
            self.smart_lock_door.close()
        else:
            print('警告:ドアの施錠に失敗しました!')

proxy = Security(SmartLockDoor())
proxy.open('解錠ボタン')
proxy.open('施錠ボタン')
proxy.close('施錠ボタン')
proxy.close('解錠ボタン')

実行結果

ドアを解錠しました
警告:ドアの解錠に失敗しました!
ドアを施錠しました
警告:ドアの施錠に失敗しました!

まとめ

パターン名称 概要 利用シーン
Adapter インターフェースを利用して互換性のないクラス同士を組み合わせる クラス同士に互換性をもたせたい場合
Bridge 継承を使用せずに集約を利用して、機能と実装を分離し機能拡張の柔軟性を担保する 機能と実装を分離したい場合(機能拡張を柔軟にしたい)
Composite ツリー構造を表現し容器と中身を同一視して扱う ツリー構造に対して再起的な処理をしたい場合
Decorator 継承を利用せずに機能を追加する 既存のクラスの持っている機能を別のクラスに追加したい場合(機能をクラスを跨いで使用したい)
Facade 複雑なサブシステム達を利用するための入口を1つに統一する 複雑なサブシステムを取り扱う際に、シンプルなインターフェースを提供したい場合。サブシステムを階層化したい場合
Flyweight オブジェクトを効率よく共有してメモリ使用量を少なくする 重複するオブジェクトを効率よく操作したい場合、メモリの節約をしたい場合
Proxy 代理処理するオブジェクトを立てる オブジェクトへのアクセス制限したい場合。重いオブジェクトを必要な時に呼び出す構造にして軽くしたい場合(インスタンス化の処理が重い)

感想

今回は「構造に関するデザインパターン」7種類についてPython3を使用して理解してみるチャレンジを行いました。初めてデザインパターンに触りましたが、こういったものを一度で理解するのは難しいと割り切って60点くらいの出来を目指しております。
今回、勉強するにあたり多くの参考資料を拝見させていただきました。どれも非常にわかりやすく理解が進みました!ありがとうございました。

デザインパターンを使用することを目的とせずに、これからも精進していければと思います。

参考にさせていただいたもの(学習教材)

  1. オブジェクト指向でなぜ作るのか? 3版
  2. 実践Python3
    一部引用させていただきました。著作権の問題があるので最低限の引用にとどめる形をとっています(できるかぎり引用しないようにしました)。興味がわいた方はせひ書籍を読んでください。
  3. Wikipedia
    各パターンのWikipediaを参照
  4. 機械学習 ベストプラクティス
    このHPのコンテンツはソースコードを含めて自由に使っていただいて結構です。というHP上の明言により多く引用させていただきました。情報も多くわかりやすくて感謝しております。
    自分が説明できてない部分も多いので、興味がわいた方はぜひ読んでみてください。
  5. design-patterns-for-humans
    CC BY 4.0に基づき文章の引用や、サンプルコード(PHP)をPythonコードに変換または編集しております。

イラスト素材

いらすとや

この記事(学習メモ)について

  • Zennのプラットフォーム上ですが、著者には収益化の意図は一切ありません。
  • 予告なく非公開になる場合があります。
  • 販売・転載・加工・引用など一切の行為を禁止いたします。(①個人の学習メモを公開しているだけで保証されたものでないため②引用元の力や先人の知恵で成り立っているため)

Discussion

ログインするとコメントできます