Python Language
デザインパターン
サーチ…
前書き
戦略パターン
このデザインパターンは戦略パターンと呼ばれます。アルゴリズムのファミリを定義し、それぞれをカプセル化し、それらを交換可能にするために使用されます。戦略設計パターンは、アルゴリズムを使用するクライアントから独立してアルゴリズムを変えることを可能にする。
例えば、動物はさまざまな方法で「歩く」ことができます。歩行は、異なる種類の動物によって実施される戦略と考えることができる。
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つの重要な要素があります。
-
The pattern name
は、設計上の問題、その解決法、および結果を1つまたは2つの単語で記述するために使用できるハンドルです。 -
The problem
は、パターンをいつ適用するかを記述します。 -
The solution
は、デザイン、その関係、責任、およびコラボレーションを構成する要素を記述します。 -
The consequences
はパターンを適用する結果とトレードオフです。
デザインパターンの利点:
- 複数のプロジェクトにわたって再利用可能です。
- 問題のアーキテクチャレベルを解決することができます
- 彼らは、開発者や建築家の経験である、時間をかけて検証され、十分に実証されています
- 信頼性と依存性があります
デザインパターンは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_from
、date_to
、reservations_count
)をドメインの無関係な概念(ユーザー権限)と組み合わせてサービスしていません。 - Consumer(
StatsService
)にはパーミッション関連のロジックもありStatsService
警告
- プロキシインターフェイスは、常に非表示のオブジェクトとまったく同じです。したがって、プロキシによってラップされたサービスを使用するユーザーは、プロキシの存在を認識していませんでした。