Python Language
Wzorce projektowe
Szukaj…
Wprowadzenie
Wzór strategii
Ten wzorzec projektu nazywa się Wzorzec strategii. Służy do definiowania rodziny algorytmów, enkapsulacji każdego z nich i umożliwienia ich zamiany. Wzór strategii pozwala algorytmowi zmieniać się niezależnie od klientów, którzy go używają.
Na przykład zwierzęta mogą „chodzić” na wiele różnych sposobów. Chodzenie można uznać za strategię wdrażaną przez różne typy zwierząt:
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))
Uruchomienie tego przykładu spowoduje wygenerowanie następującego wyniku:
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
Zauważ, że w językach takich jak C ++ lub Java ten wzorzec jest implementowany przy użyciu klasy abstrakcyjnej lub interfejsu w celu zdefiniowania strategii. W Pythonie bardziej sensowne jest po prostu zdefiniowanie zewnętrznych funkcji, które można dynamicznie dodawać do klasy za pomocą types.MethodType
.
Wprowadzenie do wzorców projektowych i wzorca Singleton
Wzorce projektowe zapewniają rozwiązania commonly occurring problems
w projektowaniu oprogramowania. Wzory projektowe zostały po raz pierwszy wprowadzone przez GoF(Gang of Four)
których opisali wspólne wzorce jako problemy, które pojawiają się w kółko i rozwiązania tych problemów.
Wzory projektowe mają cztery podstawowe elementy:
-
The pattern name
to uchwyt, którego możemy użyć do opisania problemu projektowego, jego rozwiązań i konsekwencji w jednym lub dwóch słowach. -
The problem
opisuje, kiedy zastosować wzór. -
The solution
opisuje elementy, które składają się na projekt, ich relacje, obowiązki i współpracę. -
The consequences
są wyniki i kompromisy związane z zastosowaniem wzorca.
Zalety wzorów projektowych:
- Można je wykorzystywać wielokrotnie w wielu projektach.
- Architektoniczny poziom problemów można rozwiązać
- Są sprawdzone i sprawdzone w czasie, co stanowi doświadczenie deweloperów i architektów
- Mają niezawodność i zależność
Wzory projektowe można podzielić na trzy kategorie:
- Wzór kreacyjny
- Wzór strukturalny
- Wzór behawioralny
Creational Pattern
- zajmują się sposobem tworzenia obiektu i izolują szczegóły tworzenia obiektu.
Structural Pattern
- projektują strukturę klas i obiektów, aby mogli komponować w celu uzyskania większych wyników.
Behavioral Pattern
- dotyczą interakcji między przedmiotami i odpowiedzialności za przedmioty.
Wzór singletonu :
Jest to rodzaj creational pattern
który zapewnia mechanizm posiadania tylko jednego i jednego obiektu danego typu i zapewnia globalny punkt dostępu.
np. Singleton może być wykorzystywany w operacjach bazy danych, w których chcemy, aby obiekt bazy danych zachował spójność danych.
Realizacja
Możemy zaimplementować wzorzec Singleton w Pythonie, tworząc tylko jedną instancję klasy Singleton i ponownie obsługując ten sam obiekt.
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)
Wynik:
('Object created', <__main__.Singleton object at 0x10a7cc310>)
('Object2 created', <__main__.Singleton object at 0x10a7cc310>)
Zauważ, że w językach takich jak C ++ lub Java ten wzorzec jest implementowany poprzez uczynienie konstruktora prywatnym i utworzenie statycznej metody inicjującej obiekt. W ten sposób jeden obiekt jest tworzony przy pierwszym wywołaniu, a klasa zwraca ten sam obiekt później. Ale w Pythonie nie mamy możliwości tworzenia prywatnych konstruktorów.
Wzór fabryczny
Wzorzec fabryczny jest również Creational pattern
. Termin factory
oznacza, że klasa jest odpowiedzialna za tworzenie obiektów innych typów. Istnieje klasa, która działa jak fabryka, z którą są powiązane obiekty i metody. Klient tworzy obiekt, wywołując metody o określonych parametrach, a fabryka tworzy obiekt pożądanego typu i zwraca go do klienta.
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)
Wynik:
Which music you want to play Mp3 or Ogg"Ogg"
Playing .ogg music!
MusicFactory
to klasa fabryczna, która tworzy obiekt typu Mp3
lub Ogg
zależności od wyboru użytkownika.
Pełnomocnik
Obiekt proxy jest często używany do zapewnienia strzeżonego dostępu do innego obiektu, którego wewnętrznej logiki biznesowej nie chcemy zanieczyszczać wymogami bezpieczeństwa.
Załóżmy, że chcielibyśmy zagwarantować, że tylko użytkownik określonych uprawnień może uzyskać dostęp do zasobów.
Definicja serwera proxy: (zapewnia, że tylko użytkownicy, którzy faktycznie widzą rezerwacje, będą mogli korzystać z usługi booking_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)
KORZYŚCI
- unikamy jakichkolwiek zmian w
ReservationService
przy zmianie ograniczeń dostępu. - my nie mieszanie danych biznesowych powiązanych (
date_from
,date_to
,reservations_count
) z domeny niepowiązanych pojęć (uprawnienia użytkownika) w służbie. - Konsument (
StatsService
) jest również wolny od logiki związanej z uprawnieniami
PRZESTROGI
- Interfejs proxy jest zawsze dokładnie taki sam jak obiekt, który ukrywa, więc użytkownik korzystający z usługi opakowanej przez proxy nie był nawet świadomy obecności proxy.