サーチ…


前書き

デザインパターンは、ソフトウェア開発における一般的な問題の一般的な解決策です。このドキュメントのトピックは、特にPythonで一般的なデザインパターンの例を提供することを目的としています。

戦略パターン

このデザインパターンは戦略パターンと呼ばれます。アルゴリズムのファミリを定義し、それぞれをカプセル化し、それらを交換可能にするために使用されます。戦略設計パターンは、アルゴリズムを使用するクライアントから独立してアルゴリズムを変えることを可能にする。

例えば、動物はさまざまな方法で「歩く」ことができます。歩行は、異なる種類の動物によって実施される戦略と考えることができる。

from types import MethodType


class Animal(object):
    
    def __init__(self, *args, **kwargs):
        self.name = kwargs.pop('name', None) or 'Animal'
        if kwargs.get('walk', None):
            self.walk = MethodType(kwargs.pop('walk'), self)

    def walk(self):
        """
        Cause animal instance to walk
        
        Walking funcionallity is a strategy, and is intended to
        be implemented separately by different types of animals.
        """
        message = '{} should implement a walk method'.format(
            self.__class__.__name__)
        raise NotImplementedError(message)


# Here are some different walking algorithms that can be used with Animal
def snake_walk(self):
    print('I am slithering side to side because I am a {}.'.format(self.name))

def four_legged_animal_walk(self):
    print('I am using all four of my legs to walk because I am a(n) {}.'.format(
        self.name))

def two_legged_animal_walk(self):
    print('I am standing up on my two legs to walk because I am a {}.'.format(
        self.name))

この例を実行すると、次の出力が生成されます。

generic_animal = Animal()
king_cobra = Animal(name='King Cobra', walk=snake_walk)
elephant = Animal(name='Elephant', walk=four_legged_animal_walk)
kangaroo = Animal(name='Kangaroo', walk=two_legged_animal_walk)

kangaroo.walk()
elephant.walk()
king_cobra.walk()
# This one will Raise a NotImplementedError to let the programmer
# know that the walk method is intended to be used as a strategy.
generic_animal.walk()

    # OUTPUT:
    #
    # I am standing up on my two legs to walk because I am a Kangaroo.
    # I am using all four of my legs to walk because I am a(n) Elephant.
    # I am slithering side to side because I am a King Cobra.
    # Traceback (most recent call last):
    #   File "./strategy.py", line 56, in <module>
    #     generic_animal.walk()
    #   File "./strategy.py", line 30, in walk
    #     raise NotImplementedError(message)
    # NotImplementedError: Animal should implement a walk method 

C ++やJavaなどの言語では、このパターンは抽象クラスまたはインタフェースを使用して戦略を定義して実装されています。 Pythonではtypes.MethodTypeを使ってクラスに動的に追加できる関数を外部に定義する方が意味があります。

デザインパターンとシングルトンパターンの紹介

デザインパターンは、ソフトウェア設計におけるcommonly occurring problems解決策を提供します。デザインパターンはGoF(Gang of Four)最初に導入されましGoF(Gang of Four)そこでは、一般的なパターンが何度も繰り返される問題とその問題の解決策として記述されていました。

デザインパターンには4つの重要な要素があります。

  1. The pattern nameは、設計上の問題、その解決法、および結果を1つまたは2つの単語で記述するために使用できるハンドルです。
  2. The problemは、パターンをいつ適用するかを記述します。
  3. The solutionは、デザイン、その関係、責任、およびコラボレーションを構成する要素を記述します。
  4. The consequencesはパターンを適用する結果とトレードオフです。

デザインパターンの利点:

  1. 複数のプロジェクトにわたって再利用可能です。
  2. 問題のアーキテクチャレベルを解決することができます
  3. 彼らは、開発者や建築家の経験である、時間をかけて検証され、十分に実証されています
  4. 信頼性と依存性があります

デザインパターンは3つのカテゴリに分類できます。

  1. 創造的なパターン
  2. 構造パターン
  3. 行動パターン

Creational Pattern - オブジェクトの作成方法と、オブジェクト作成の詳細を分離することに関係しています。

Structural Pattern - クラスとオブジェクトの構造を設計し、より大きな結果を得るように構成します。

Behavioral Pattern - オブジェクト間の相互作用とオブジェクトの責任に関係しています。

シングルトンパターン

それは与えられたタイプの1つのオブジェクトと1つのオブジェクトだけを持つメカニズムを提供し、グローバルなアクセスポイントを提供する一種のcreational patternです。

例えば、シングルトンはデータベース操作で使用できます。ここでは、データベースオブジェクトがデータの一貫性を維持するようにします。

実装

SingletonパターンをPythonで実装するには、Singletonクラスのインスタンスを1つだけ作成し、同じオブジェクトを再び提供する必要があります。

class Singleton(object):
    def __new__(cls):
        # hasattr method checks if the class object an instance property or not.
        if not hasattr(cls, 'instance'):
            cls.instance = super(Singleton, cls).__new__(cls)
        return cls.instance

s = Singleton()
print ("Object created", s)

s1 = Singleton()
print ("Object2 created", s1)

出力:

('Object created', <__main__.Singleton object at 0x10a7cc310>)
('Object2 created', <__main__.Singleton object at 0x10a7cc310>)

C ++やJavaなどの言語では、このパターンは、コンストラクタをプライベートにしてオブジェクトの初期化を行う静的メソッドを作成することで実装されます。この方法では、1つのオブジェクトが最初の呼び出しで作成され、クラスはその後同じオブジェクトを返します。しかし、Pythonではプライベートコンストラクタを作成する方法はありません。

工場パターン

工場のパターンもまた、 Creational patternです。 「 factoryという用語は、クラスが他のタイプのオブジェクトを作成することを意味します。オブジェクトとメソッドが関連付けられたファクトリとして動作するクラスがあります。クライアントは、特定のパラメータを持つメソッドを呼び出してオブジェクトを作成し、ファクトリを作成してクライアントに返します。

from abc import ABCMeta, abstractmethod

class Music():
    __metaclass__ = ABCMeta
    @abstractmethod
    def do_play(self):
        pass

class Mp3(Music):
    def do_play(self):
        print ("Playing .mp3 music!")
    
class Ogg(Music):
    def do_play(self):
        print ("Playing .ogg music!")
    
class MusicFactory(object):
    def play_sound(self, object_type):
        return eval(object_type)().do_play()
    
if __name__ == "__main__":
    mf = MusicFactory()
    music = input("Which music you want to play Mp3 or Ogg")
    mf.play_sound(music)

出力:

Which music you want to play Mp3 or Ogg"Ogg"
Playing .ogg music!

MusicFactoryは、ユーザが提供する選択に応じて、 Mp3タイプまたはOggタイプのオブジェクトを作成するファクトリクラスです。

プロキシ

プロキシオブジェクトは、他のオブジェクトへのアクセスを確実に保護するためによく使用されます。内部的なビジネスロジックは、安全上の必要性によって汚染されたくありません。

特定のアクセス許可のユーザーだけがリソースにアクセスできることを保証したいとします。

プロキシ定義:(実際には予約を見ることができるユーザーだけがconsumer_serviceにアクセスできるようになります)

from datetime import date
from operator import attrgetter

class Proxy:
    def __init__(self, current_user, reservation_service):
        self.current_user = current_user
        self.reservation_service = reservation_service

    def highest_total_price_reservations(self, date_from, date_to, reservations_count):
        if self.current_user.can_see_reservations:
            return self.reservation_service.highest_total_price_reservations(
                date_from,
                date_to,
                reservations_count
              )
        else:
            return []

#Models and ReservationService:

class Reservation:
    def __init__(self, date, total_price):
        self.date = date
        self.total_price = total_price

class ReservationService:
    def highest_total_price_reservations(self, date_from, date_to, reservations_count):
        # normally it would be read from database/external service
        reservations = [
            Reservation(date(2014, 5, 15), 100),
            Reservation(date(2017, 5, 15), 10),
            Reservation(date(2017, 1, 15), 50)
        ]

        filtered_reservations = [r for r in reservations if (date_from <= r.date <= date_to)]

        sorted_reservations = sorted(filtered_reservations, key=attrgetter('total_price'), reverse=True)

        return sorted_reservations[0:reservations_count]


class User:
    def __init__(self, can_see_reservations, name):
        self.can_see_reservations = can_see_reservations
        self.name = name

#Consumer service:

class StatsService:
    def __init__(self, reservation_service):
        self.reservation_service = reservation_service

    def year_top_100_reservations_average_total_price(self, year):
        reservations = self.reservation_service.highest_total_price_reservations(
            date(year, 1, 1),
            date(year, 12, 31),
            1
        )

        if len(reservations) > 0:
            total = sum(r.total_price for r in reservations)

            return total / len(reservations)
        else:
            return 0

#Test:
def test(user, year):
    reservations_service = Proxy(user, ReservationService())
    stats_service = StatsService(reservations_service)
    average_price = stats_service.year_top_100_reservations_average_total_price(year)
    print("{0} will see: {1}".format(user.name, average_price))

test(User(True, "John the Admin"), 2017)
test(User(False, "Guest"),         2017)

利点
  • アクセス制限が変更されたときのReservationService変更は避けています。
  • 私たちはビジネス関連のデータ( date_fromdate_toreservations_count )をドメインの無関係な概念(ユーザー権限)と組み合わせてサービスしていません。
  • Consumer( StatsService )にはパーミッション関連のロジックもありStatsService

警告
  • プロキシインターフェイスは、常に非表示のオブジェクトとまったく同じです。したがって、プロキシによってラップされたサービスを使用するユーザーは、プロキシの存在を認識していませんでした。


Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow