Python Language
Ontwerp patronen
Zoeken…
Invoering
Strategie patroon
Dit ontwerppatroon wordt Strategiepatroon genoemd. Het wordt gebruikt om een familie van algoritmen te definiëren, deze in te kapselen en ze uitwisselbaar te maken. Door het ontwerppatroon van de strategie kan een algoritme onafhankelijk variëren van klanten die het gebruiken.
Dieren kunnen bijvoorbeeld op veel verschillende manieren "lopen". Lopen kan worden beschouwd als een strategie die wordt uitgevoerd door verschillende soorten dieren:
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))
Het uitvoeren van dit voorbeeld zou de volgende uitvoer produceren:
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
Merk op dat in talen zoals C ++ of Java dit patroon wordt geïmplementeerd met behulp van een abstracte klasse of een interface om een strategie te definiëren. In Python is het types.MethodType
om enkele functies extern te definiëren die dynamisch aan een klasse kunnen worden toegevoegd met behulp van types.MethodType
.
Inleiding tot ontwerppatronen en Singleton-patroon
Ontwerppatronen bieden oplossingen voor de commonly occurring problems
bij het ontwerpen van software. De ontwerppatronen werden voor het eerst geïntroduceerd door GoF(Gang of Four)
waar ze de gemeenschappelijke patronen beschreven als problemen die zich steeds opnieuw voordoen en oplossingen voor die problemen.
Ontwerppatronen hebben vier essentiële elementen:
-
The pattern name
is een handvat dat we kunnen gebruiken om een ontwerpprobleem, de oplossingen en de gevolgen ervan in een paar woorden te beschrijven. -
The problem
beschrijft wanneer het patroon moet worden toegepast. -
The solution
beschrijft de elementen waaruit het ontwerp bestaat, hun relaties, verantwoordelijkheden en samenwerkingen. -
The consequences
zijn de resultaten en afwegingen van het toepassen van het patroon.
Voordelen van ontwerppatronen:
- Ze zijn herbruikbaar voor meerdere projecten.
- Het architectonische niveau van problemen kan worden opgelost
- Ze zijn beproefd en beproefd, wat de ervaring is van ontwikkelaars en architecten
- Ze hebben betrouwbaarheid en afhankelijkheid
Ontwerppatronen kunnen in drie categorieën worden ingedeeld:
- Creatief patroon
- Structureel patroon
- Gedragspatroon
Creational Pattern
- Ze houden zich bezig met hoe het object kan worden gemaakt en isoleren de details van het maken van objecten.
Structural Pattern
- Ze ontwerpen de structuur van klassen en objecten zodat ze kunnen componeren om grotere resultaten te bereiken.
Behavioral Pattern
- Ze houden zich bezig met interactie tussen objecten en de verantwoordelijkheid van objecten.
Singleton-patroon :
Het is een soort creational pattern
dat een mechanisme biedt om slechts één en één object van een bepaald type te hebben en een globaal toegangspunt biedt.
bijvoorbeeld Singleton kan worden gebruikt bij databasebewerkingen, waarbij we willen dat databaseobjecten de gegevensconsistentie behouden.
Implementatie
We kunnen Singleton Pattern in Python implementeren door slechts één instantie van de Singleton-klasse te maken en hetzelfde object opnieuw te dienen.
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)
Output:
('Object created', <__main__.Singleton object at 0x10a7cc310>)
('Object2 created', <__main__.Singleton object at 0x10a7cc310>)
Merk op dat in talen zoals C ++ of Java dit patroon wordt geïmplementeerd door de constructor privé te maken en een statische methode te maken die de objectinitialisatie uitvoert. Op deze manier wordt één object gemaakt bij de eerste aanroep en retourneert de klasse daarna hetzelfde object. Maar in Python hebben we geen enkele manier om privéconstructeurs te maken.
Fabriek patroon
Fabriekspatroon is ook een Creational pattern
. De term factory
betekent dat een klasse verantwoordelijk is voor het maken van objecten van andere typen. Er is een klasse die fungeert als een fabriek waaraan objecten en methoden zijn gekoppeld. De client maakt een object door de methoden met bepaalde parameters aan te roepen en in de fabriek wordt het object van het gewenste type gemaakt en aan de client geretourneerd.
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)
Output:
Which music you want to play Mp3 or Ogg"Ogg"
Playing .ogg music!
MusicFactory
is hier de fabrieksklasse die een object van het type Mp3
of Ogg
afhankelijk van de keuze die de gebruiker biedt.
volmacht
Proxy-object wordt vaak gebruikt om beveiligde toegang tot een ander object te garanderen, welke interne bedrijfslogica we niet willen vervuilen met veiligheidseisen.
Stel dat we willen garanderen dat alleen gebruikers met specifieke machtigingen toegang hebben tot bronnen.
Proxy-definitie: (het zorgt ervoor dat alleen gebruikers die daadwerkelijk reserveringen kunnen zien, consumer_service kunnen gebruiken)
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)
VOORDELEN
- we vermijden wijzigingen in
ReservationService
wanneer toegangsbeperkingen worden gewijzigd. - we
date_from
geen bedrijfsgerelateerde gegevens (date_from
,date_to
,reservations_count
) met domeingerelateerde concepten (gebruikersrechten) in de service. - Consumer (
StatsService
) is ook vrij van logica met betrekking tot machtigingen
WAARSCHUWINGEN
- De proxy-interface is altijd exact hetzelfde als het object dat het verbergt, zodat de gebruiker die service verpakt door proxy gebruikt niet eens op de hoogte was van de aanwezigheid van proxy.