├── src ├── observer │ ├── __init__.py │ ├── decorator.py │ ├── main.py │ ├── notification.py │ └── shop.py ├── singleton.py ├── proxy2.py ├── adapter.py ├── adapter2.py ├── facade.py ├── strategy3.py ├── decorator2.py ├── iterator.py ├── state2.py ├── prototype2.py ├── factory.py ├── singleton2.py ├── prototype.py ├── proxy_dice.py ├── factory2.py ├── observer2.py ├── proxy_lazy.py ├── template_method.py ├── state.py ├── strategy.py ├── strategy_2.py ├── chain_of_responsibility.py ├── abstract_factory2.py ├── decorator.py ├── abstract_factory.py ├── builder.py ├── proxy.py ├── composit.py └── abstract_factory3.py ├── assets └── img │ ├── design-pattern-map.png │ └── design-patterns-python.png ├── .gitignore ├── SECURITY.md ├── LICENSE ├── .github └── workflows │ └── codeql-analysis.yml └── README.md /src/observer/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/img/design-pattern-map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrRezoo/design-patterns-python/HEAD/assets/img/design-pattern-map.png -------------------------------------------------------------------------------- /assets/img/design-patterns-python.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrRezoo/design-patterns-python/HEAD/assets/img/design-patterns-python.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/* 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | 9 | # Environments 10 | .env 11 | .venv 12 | env/ 13 | venv/ 14 | ENV/ 15 | env.bak/ 16 | venv.bak/ 17 | -------------------------------------------------------------------------------- /src/observer/decorator.py: -------------------------------------------------------------------------------- 1 | def notify_observers(message=''): 2 | def decorator_method(func): 3 | def wrapped(obj, *arg, **kwargs): 4 | result = func(obj, *arg, **kwargs) 5 | for observer in obj.observers: 6 | observer.send(message) 7 | return result 8 | 9 | return wrapped 10 | 11 | return decorator_method 12 | -------------------------------------------------------------------------------- /src/singleton.py: -------------------------------------------------------------------------------- 1 | class Singleton: 2 | 3 | @classmethod 4 | def __new__(cls, *args, **kwargs): 5 | if not hasattr(cls, 'instance'): 6 | cls.instance = super(*args, **kwargs) 7 | return cls.instance 8 | 9 | 10 | if __name__ == '__main__': 11 | s1 = Singleton() 12 | s2 = Singleton() 13 | s3 = Singleton() 14 | 15 | print(id(s1)) 16 | print(id(s2)) 17 | print(id(s3)) 18 | 19 | print(id(s1) == id(s2) == id(s3)) 20 | -------------------------------------------------------------------------------- /src/observer/main.py: -------------------------------------------------------------------------------- 1 | from src.observer.shop import Product, Purchase 2 | 3 | if __name__ == '__main__': 4 | p1 = Product() 5 | p2 = Product() 6 | p3 = Product() 7 | p4 = Product() 8 | p5 = Product() 9 | 10 | purchase = Purchase([p1, p2, p3, p4, p5]) 11 | purchase.checkout() 12 | """we can use this without any patterns and each time call this line.""" 13 | # EmailNotification.send('checkout is done ') 14 | """or write observer pattern using the decorator pattern like this""" 15 | purchase.cancel() 16 | -------------------------------------------------------------------------------- /src/proxy2.py: -------------------------------------------------------------------------------- 1 | """ 2 | Structural pattern : 3 | Proxy 4 | Examples : 5 | 1. use in orm 6 | """ 7 | 8 | 9 | class Db: 10 | def work(self): 11 | print('you are admin so you can work with database...') 12 | 13 | 14 | class Proxy: 15 | admin_password = 'secret' 16 | 17 | def check_admin(self, password): 18 | if password == self.admin_password: 19 | d1 = Db() 20 | d1.work() 21 | else: 22 | print('You are not admin so you cant work with database...') 23 | 24 | 25 | p1 = Proxy() 26 | p1.check_admin('secret') 27 | -------------------------------------------------------------------------------- /src/adapter.py: -------------------------------------------------------------------------------- 1 | """ 2 | Structural: 3 | Adapter => 1.Adaptee 2.Adapter 3.Client 4 | """ 5 | from abstract_factory import Rugs 6 | 7 | 8 | class PriceAdapter: 9 | def __init__(self, rate): 10 | self.rate = rate 11 | 12 | def exchange(self, product): 13 | return self.rate * product._price 14 | 15 | 16 | if __name__ == '__main__': 17 | r1 = Rugs('persian rugs', 1200) 18 | r2 = Rugs('persian rugs', 1500) 19 | r3 = Rugs('persian rugs', 1100) 20 | 21 | adapter = PriceAdapter(rate=20000) 22 | 23 | rugs = [r1, r2, r3] 24 | 25 | for rug in rugs: 26 | print(f"{rug._name}:{adapter.exchange(rug)}") 27 | -------------------------------------------------------------------------------- /src/observer/notification.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class Observer(ABC): 5 | @staticmethod 6 | @abstractmethod 7 | def send(message=''): 8 | pass 9 | 10 | 11 | class EmailNotification(Observer): 12 | @staticmethod 13 | def send(message=''): 14 | print(f"Sending email message : {message}") 15 | 16 | 17 | class SMSNotification(Observer): 18 | @staticmethod 19 | def send(message=''): 20 | print(f'sending sms massage {message}') 21 | 22 | 23 | class PushNotification(Observer): 24 | @staticmethod 25 | def send(message=''): 26 | print(f'sending push notification massage {message}') 27 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.1.x | :white_check_mark: | 11 | | 5.0.x | :x: | 12 | | 4.0.x | :white_check_mark: | 13 | | < 4.0 | :x: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | Use this section to tell people how to report a vulnerability. 18 | 19 | Tell them where to go, how often they can expect to get an update on a 20 | reported vulnerability, what to expect if the vulnerability is accepted or 21 | declined, etc. 22 | -------------------------------------------------------------------------------- /src/adapter2.py: -------------------------------------------------------------------------------- 1 | """ 2 | Structural: 3 | Adapter => 1.Adaptee 2.Adapter 3.Client 4 | """ 5 | 6 | 7 | class IranSocket: 8 | _type = "2" 9 | 10 | 11 | class Adapter: 12 | _socket = None 13 | _pinType = '3To2' 14 | 15 | def __init__(self, socket): 16 | self._socket = socket 17 | 18 | 19 | class Fridge: 20 | _adapter = None 21 | _pinType = '3' 22 | 23 | def __init__(self, adapter): 24 | self._adapter = adapter 25 | 26 | def freeze(self): 27 | if self._adapter._pinType == ( 28 | self._pinType + 'To' + self._adapter._socket._type): 29 | print("Done. . .") 30 | else: 31 | print('sorry not usable') 32 | 33 | 34 | if __name__ == '__main__': 35 | iran = IranSocket() 36 | adapt = Adapter(iran) 37 | fridge = Fridge(adapt) 38 | fridge.freeze() 39 | -------------------------------------------------------------------------------- /src/facade.py: -------------------------------------------------------------------------------- 1 | """ 2 | Structural : 3 | Facade 4 | """ 5 | 6 | 7 | class Raw: 8 | def raw(self): 9 | print('Buying raw foods from market...') 10 | 11 | 12 | class Transfer: 13 | def transfer(self): 14 | print('Transferring raw foods to restaurant...') 15 | 16 | 17 | class Cook: 18 | def cook(self): 19 | print('Cooking raw foods by chief...') 20 | 21 | 22 | class Serve: 23 | def server(self): 24 | print('Serving food to client...') 25 | 26 | 27 | class ItalianRestaurant: 28 | def get(self): 29 | r = Raw() 30 | r.raw() 31 | 32 | t = Transfer() 33 | t.transfer() 34 | 35 | c = Cook() 36 | c.cook() 37 | 38 | s = Serve() 39 | s.server() 40 | 41 | 42 | def order(): 43 | i = ItalianRestaurant() 44 | i.get() 45 | 46 | 47 | order() 48 | -------------------------------------------------------------------------------- /src/observer/shop.py: -------------------------------------------------------------------------------- 1 | from src.observer import notify_observers 2 | from src.observer import EmailNotification, SMSNotification, \ 3 | PushNotification 4 | 5 | 6 | class Product: 7 | pass 8 | 9 | 10 | class Payment: 11 | observers = [ 12 | SMSNotification, 13 | PushNotification, 14 | ] 15 | 16 | @notify_observers(message='purchase paid') 17 | def checkout(self): 18 | pass 19 | 20 | 21 | class Purchase: 22 | observers = [ 23 | EmailNotification, 24 | SMSNotification, 25 | PushNotification, 26 | ] 27 | 28 | def __init__(self, product_list): 29 | self.product_list = product_list 30 | self.payment = Payment() 31 | 32 | def checkout(self): 33 | self.payment.checkout() 34 | 35 | @notify_observers(message='purchase cancel') 36 | def cancel(self): 37 | pass 38 | -------------------------------------------------------------------------------- /src/strategy3.py: -------------------------------------------------------------------------------- 1 | """ 2 | Behavioral : 3 | Strategy 4 | """ 5 | from abc import ABC, abstractmethod 6 | 7 | 8 | class Context: 9 | def __init__(self, direction, sentence): 10 | self._direction = direction 11 | self.sentence = sentence 12 | 13 | @property 14 | def direction(self): 15 | return self._direction 16 | 17 | @direction.setter 18 | def direction(self, dir): 19 | self._direction = dir 20 | 21 | def sorting(self): 22 | return self._direction.direct(self.sentence) 23 | 24 | 25 | class Direction(ABC): 26 | @abstractmethod 27 | def direct(self, data): 28 | pass 29 | 30 | 31 | class Right(Direction): 32 | def direct(self, data): 33 | print(data[::-1]) 34 | 35 | 36 | class Left(Direction): 37 | def direct(self, data): 38 | print(data) 39 | 40 | 41 | c1 = Context(Left(), 'Hello World...') 42 | c1.sorting() 43 | -------------------------------------------------------------------------------- /src/decorator2.py: -------------------------------------------------------------------------------- 1 | """ 2 | Structural : 3 | Decorator => decorator pattern != python decorator 4 | """ 5 | 6 | 7 | class Article: 8 | def show_article(self): 9 | print(f"All articles . . .{self.__class__}") 10 | 11 | 12 | class Login: 13 | def check_login(self, name, password): 14 | if name == 'reza' and password == 'mr': 15 | return True 16 | return f"{self.__class__} dont have access" 17 | 18 | 19 | def login_decorator(func): 20 | def wrapper(): 21 | name = input("Enter your name . . .") 22 | password = input("Enter your password . . .") 23 | login = Login() 24 | result = login.check_login(name, password) 25 | if result: 26 | func() 27 | else: 28 | print('username or password is wrong') 29 | 30 | return wrapper 31 | 32 | 33 | @login_decorator 34 | def show_all_articles(): 35 | article = Article() 36 | article.show_article() 37 | 38 | 39 | show_all_articles() 40 | -------------------------------------------------------------------------------- /src/iterator.py: -------------------------------------------------------------------------------- 1 | """ 2 | Behavioral pattern: 3 | Iterator => 1.Iterable 2.Iteration 4 | 5 | Requirements that should know: 6 | __iter__ , __next__ 7 | """ 8 | 9 | 10 | class Iteration: 11 | def __init__(self, value): 12 | self.value = value 13 | 14 | def __next__(self): 15 | if self.value == 0: 16 | raise StopIteration('End of sequence') 17 | for item in range(0, self.value): 18 | value = self.value 19 | self.value -= 1 20 | return value 21 | 22 | 23 | class Iterable: 24 | def __init__(self, value): 25 | self.value = value 26 | 27 | def __iter__(self): 28 | return Iteration(self.value) 29 | 30 | 31 | if __name__ == '__main__': 32 | f1 = Iterable(5) 33 | f2 = iter(f1) 34 | 35 | print(next(f2)) 36 | print(next(f2)) 37 | print(next(f2)) 38 | print(next(f2)) 39 | print(next(f2)) 40 | """we add raise error that don't show later than zero """ 41 | print(next(f2)) 42 | -------------------------------------------------------------------------------- /src/state2.py: -------------------------------------------------------------------------------- 1 | """ 2 | Behavioral pattern: 3 | State 4 | Example: 5 | switch between several state 6 | """ 7 | 8 | 9 | class State: 10 | def operate(self): 11 | print(f'Turning Tv {self.status}') 12 | 13 | 14 | class TurnOn(State): 15 | def __init__(self, tv): 16 | self.tv = tv 17 | self.status = 'On' 18 | 19 | def change_state(self): 20 | print('Changing stat to off...') 21 | self.tv.state = self.tv.off 22 | 23 | 24 | class TurnOff(State): 25 | def __init__(self, tv): 26 | self.tv = tv 27 | self.status = 'Off' 28 | 29 | def change_state(self): 30 | print('Changing state to On...') 31 | self.tv.state = self.tv.on 32 | 33 | 34 | class Tv: 35 | def __init__(self): 36 | self.on = TurnOn(self) 37 | self.off = TurnOff(self) 38 | self.state = self.on 39 | 40 | def press(self): 41 | self.state.operate() 42 | self.state.change_state() 43 | 44 | 45 | if __name__ == '__main__': 46 | t = Tv() 47 | t.press() 48 | t.press() 49 | t.press() 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Reza Mobaraki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/prototype2.py: -------------------------------------------------------------------------------- 1 | """ 2 | Creational : 3 | Prototype 4 | """ 5 | from copy import deepcopy 6 | 7 | 8 | class Person: 9 | def __init__(self, name, age): 10 | self.name = name 11 | self.age = age 12 | 13 | 14 | class Prototype: 15 | def __init__(self): 16 | self._object = {} 17 | 18 | def register(self, name, obj): 19 | self._object[name] = obj 20 | 21 | def unregister(self, name): 22 | del self._object[name] 23 | 24 | def clone(self, name, **kwargs): 25 | cloned_obj = deepcopy(self._object.get(name)) 26 | cloned_obj.__dict__.update(kwargs) 27 | return cloned_obj 28 | 29 | 30 | if __name__ == '__main__': 31 | p1 = Person('reza', 20) 32 | pro1 = Prototype() 33 | pro1.register(name='person1', obj=p1) 34 | personCopy = pro1.clone(name='person1') 35 | 36 | """ sample test """ 37 | print(personCopy.name is p1.name) 38 | print(personCopy.age is p1.age) 39 | print(id(personCopy.name)) 40 | print(id(p1.name)) 41 | 42 | """ update keyword arguments """ 43 | personCopy = pro1.clone(name='person1', age=43) 44 | print(personCopy.age, personCopy.name) 45 | -------------------------------------------------------------------------------- /src/factory.py: -------------------------------------------------------------------------------- 1 | """ 2 | Creational : 3 | Factory 4 | """ 5 | 6 | 7 | from abc import ABC, abstractmethod 8 | from random import choice 9 | 10 | 11 | class PlayerBase(ABC): 12 | choices = ['r', 'p', 's'] 13 | 14 | @abstractmethod 15 | def move(self): 16 | pass 17 | 18 | 19 | class HumanPlayer(PlayerBase): 20 | def move(self): 21 | mov = input("Chose your next move...") 22 | return mov 23 | 24 | 25 | class SystemPlayer(PlayerBase): 26 | def move(self): 27 | return choice(self.choices) 28 | 29 | 30 | class Game: 31 | @staticmethod 32 | def start_game(): 33 | game_type = input( 34 | "Please chose game type(s -> single player , m -> multiple player)") 35 | if game_type == 's': 36 | p1 = HumanPlayer() 37 | p2 = SystemPlayer() 38 | elif game_type == 'm': 39 | p1 = HumanPlayer() 40 | p2 = HumanPlayer() 41 | else: 42 | print("invalid input") 43 | p1 = None 44 | p2 = None 45 | return p1, p2 46 | 47 | 48 | if __name__ == '__main__': 49 | player1, player2 = Game().start_game() 50 | 51 | for player in [player1,player2]: 52 | player.move() -------------------------------------------------------------------------------- /src/singleton2.py: -------------------------------------------------------------------------------- 1 | """ 2 | Creational: 3 | Singleton 4 | """ 5 | 6 | 7 | class Singleton1(type): 8 | _instance = None 9 | 10 | def __call__(cls, *args, **kwargs): 11 | if cls._instance is None: 12 | cls._instance = super().__call__() 13 | return cls._instance 14 | 15 | 16 | class Singleton2(type): 17 | """ 18 | Singleton metaclass 19 | Based on Python Cookbook 3rd Edition Recipe 9.13 20 | Only one instance of a class can exist. Does not work with __slots__ 21 | """ 22 | 23 | def __init__(self, *args, **kw): 24 | super(Singleton2, self).__init__(*args, **kw) 25 | self.__instance = None 26 | 27 | def __call__(self, *args, **kw): 28 | if self.__instance is None: 29 | self.__instance = super(Singleton2, self).__call__(*args, **kw) 30 | return self.__instance 31 | 32 | 33 | class DBMongo(metaclass=Singleton2): 34 | pass 35 | 36 | class DBPostgres(metaclass=Singleton2): 37 | pass 38 | 39 | 40 | if __name__ == '__main__': 41 | m1 = DBMongo() 42 | m2 = DBMongo() 43 | 44 | p1 = DBPostgres() 45 | p2 = DBPostgres() 46 | 47 | print(f"Mongo: {id(m1)} - {id(m2)} ", id(m1) == id(m2)) 48 | print(f"Postgres: {id(p1)} - {id(p2)} ", id(p1) == id(p2)) 49 | -------------------------------------------------------------------------------- /src/prototype.py: -------------------------------------------------------------------------------- 1 | """ 2 | Creational : 3 | Prototype 4 | """ 5 | 6 | class Cinema: 7 | pass 8 | 9 | 10 | class Movie: 11 | pass 12 | 13 | 14 | class Time: 15 | pass 16 | 17 | 18 | class Hall: 19 | def __init__(self, name, cinema, capacity): 20 | self.name = name 21 | self.cinema = cinema 22 | self.capacity = capacity 23 | 24 | 25 | class Seat: 26 | def __init__(self, number): 27 | self.number = number 28 | self.status = None 29 | self.customer = None 30 | 31 | 32 | class Sens: 33 | def __init__(self, cinema, movie, time, hall): 34 | self.cinema = cinema 35 | self.movie = movie 36 | self.time = time 37 | self.hall = hall 38 | self.seats = list() 39 | self.prototype_seats() 40 | 41 | def prototype_seats(self): 42 | """Prototype all seat of selected hall""" 43 | for item in range(self.hall.capacity): 44 | self.seats.append(Seat(item)) 45 | 46 | 47 | if __name__ == '__main__': 48 | cinema_instance = Cinema() 49 | movie_instance = Movie() 50 | time_instance = Time() 51 | hall_instance = Hall('hall name', cinema_instance, 60) 52 | sens = Sens(cinema_instance, movie_instance, time_instance, hall_instance) 53 | print(len(sens.seats)) 54 | print(type(sens.seats[0])) 55 | -------------------------------------------------------------------------------- /src/proxy_dice.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | 4 | class User: 5 | pass 6 | 7 | 8 | class Dice: 9 | @staticmethod 10 | def roll(): 11 | return random.randint(1, 6) 12 | 13 | 14 | class Game: 15 | def __init__(self, u1, u2): 16 | self.user1 = u1 17 | self.user2 = u2 18 | self.turn = u1 19 | self.dice = Dice 20 | 21 | def change_turn(self): 22 | self.turn = self.user2 if self.turn == self.user1 else self.user1 23 | 24 | 25 | def check_turn_and_roll_dice(game, user): 26 | if game.turn == user: 27 | game.change_turn() 28 | return game.dice.roll() 29 | return "It's not your turn" 30 | 31 | 32 | if __name__ == '__main__': 33 | user1 = User() 34 | user2 = User() 35 | 36 | game = Game(user1, user2) 37 | 38 | # print(game.roll_dice(user1)) 39 | print(check_turn_and_roll_dice(game,user2)) 40 | print(check_turn_and_roll_dice(game,user1)) 41 | print(check_turn_and_roll_dice(game,user2)) 42 | print(check_turn_and_roll_dice(game,user2)) 43 | print(check_turn_and_roll_dice(game,user1)) 44 | print(check_turn_and_roll_dice(game,user1)) 45 | print(check_turn_and_roll_dice(game,user1)) 46 | print(check_turn_and_roll_dice(game,user2)) 47 | print(check_turn_and_roll_dice(game,user1)) 48 | print(check_turn_and_roll_dice(game,user2)) 49 | 50 | -------------------------------------------------------------------------------- /src/factory2.py: -------------------------------------------------------------------------------- 1 | """ 2 | Factory 3 | - Factory is a creational design pattern that provides an interface for creating objects 4 | in a superclass, but allows subclasses to alter the type of objects that will be created. 5 | 6 | 3 component => 1. creator, 2. product, 3. client 7 | """ 8 | from abc import ABC, abstractmethod 9 | 10 | 11 | class File(ABC): # creator 12 | def __init__(self, file): 13 | self.file = file 14 | 15 | @abstractmethod 16 | def make(self): 17 | pass 18 | 19 | def call_edit(self): 20 | product = self.make() 21 | result = product.edit(self.file) 22 | return result 23 | 24 | 25 | class JsonFile(File): # creator 26 | def make(self): 27 | return Json() 28 | 29 | 30 | class XmlFile(File): # creator 31 | def make(self): 32 | return Xml() 33 | 34 | 35 | class Json: # product 36 | def edit(self, file): 37 | return f'Working on {file} Json...' 38 | 39 | 40 | class Xml: # product 41 | def edit(self, file): 42 | return f'Working on {file} Xml...' 43 | 44 | 45 | def client(file, format): # client 46 | formats = { 47 | 'json': JsonFile, 48 | 'xml': XmlFile 49 | } 50 | result = formats[format](file) 51 | return result.call_edit() 52 | 53 | 54 | if __name__ == '__main__': 55 | print(client('show', 'xml')) 56 | -------------------------------------------------------------------------------- /src/observer2.py: -------------------------------------------------------------------------------- 1 | """ 2 | Behavioral pattern : 3 | Observer 4 | 5 | Example : 6 | use in django and flask Signal 7 | """ 8 | 9 | 10 | class Observer: 11 | def __init__(self): 12 | self._observers = [] 13 | 14 | def attach(self, observer): 15 | self._observers.append(observer) 16 | 17 | def notify(self): 18 | for observer in self._observers: 19 | observer.update(self) 20 | 21 | 22 | class Data(Observer): 23 | """changing class""" 24 | 25 | def __init__(self, name): 26 | super().__init__() 27 | self.name = name 28 | self._data = 0 29 | 30 | @property 31 | def data(self): 32 | return self._data 33 | 34 | @data.setter 35 | def data(self, value): 36 | self._data = value 37 | self.notify() 38 | 39 | 40 | class One: 41 | """love to listen changing""" 42 | 43 | def update(self, subject): 44 | print(f'{subject.name} new {subject.data}') 45 | 46 | 47 | class Two: 48 | """love to listen changing""" 49 | 50 | def update(self, subject): 51 | print(f'{subject.name} new {subject.data}') 52 | 53 | 54 | if __name__ == '__main__': 55 | d1 = Data('first') 56 | d2 = Data('second') 57 | o = One() 58 | t = Two() 59 | 60 | d1.attach(o) 61 | d2.attach(t) 62 | 63 | d1.data = 5 64 | d2.data = 3 65 | -------------------------------------------------------------------------------- /src/proxy_lazy.py: -------------------------------------------------------------------------------- 1 | from time import sleep 2 | 3 | 4 | class LazyLoader: 5 | """ 6 | we write lazy that when we need object initialize this 7 | not when we create object 8 | """ 9 | 10 | def __init__(self, cls): 11 | self.cls = cls 12 | self.object = None 13 | 14 | def __getattr__(self, item): 15 | if self.object is None: 16 | self.object = self.cls() 17 | return getattr(self.object, item) 18 | 19 | 20 | class MySQLHandler: 21 | def __init__(self): 22 | sleep(1) 23 | 24 | def connect(self): 25 | return "connect to MySQL" 26 | 27 | 28 | class MongoHandler: 29 | def __init__(self): 30 | sleep(10) 31 | 32 | def get(self): 33 | return "Hello from Mongo" 34 | 35 | 36 | class NotificationCenterHandler: 37 | def __init__(self): 38 | sleep(1) 39 | 40 | def show(self): 41 | return "Hello from notif center" 42 | 43 | 44 | if __name__ == '__main__': 45 | mysql = LazyLoader(MySQLHandler) 46 | mongo = LazyLoader(MongoHandler) 47 | notification = LazyLoader(NotificationCenterHandler) 48 | 49 | print(mysql.connect()) 50 | print(notification.show()) 51 | print(mongo.get()) 52 | print(mysql.connect()) 53 | print(notification.show()) 54 | print(mysql.connect()) 55 | print(notification.show()) 56 | print(mysql.connect()) 57 | print(notification.show()) 58 | print(mongo.get()) 59 | -------------------------------------------------------------------------------- /src/template_method.py: -------------------------------------------------------------------------------- 1 | """ 2 | Behavioral pattern: 3 | Template method 4 | Example: 5 | when we have static job between several classes use one ABC class 6 | """ 7 | from abc import ABC, abstractmethod 8 | 9 | 10 | class Top(ABC): 11 | def template_method(self): 12 | self.first_common() 13 | self.second_common() 14 | self.third_require() 15 | self.fourth_require() 16 | self.hook() 17 | 18 | def first_common(self): 19 | print('I am first common...') 20 | 21 | def second_common(self): 22 | print('I am second common') 23 | 24 | @abstractmethod 25 | def third_require(self): 26 | pass 27 | 28 | @abstractmethod 29 | def fourth_require(self): 30 | pass 31 | 32 | def hook(self): 33 | pass 34 | 35 | 36 | class One(Top): 37 | def third_require(self): 38 | print('This is Third require from One...') 39 | 40 | def fourth_require(self): 41 | print('This is Fourth require from One...') 42 | 43 | def hook(self): 44 | print('This is Hook from One') 45 | 46 | 47 | class Two(Top): 48 | def third_require(self): 49 | print('This is Third require from Two...') 50 | 51 | def fourth_require(self): 52 | print('This is Fourth require from Two...') 53 | 54 | 55 | def client(class_): 56 | class_.template_method() 57 | 58 | 59 | if __name__ == '__main__': 60 | client(Two()) 61 | -------------------------------------------------------------------------------- /src/state.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class Message: 5 | def __init__(self, subject, body, sender): 6 | self.subject = subject 7 | self.body = body 8 | self.sender = sender 9 | self.flow = [sender] 10 | 11 | @property 12 | def current(self): 13 | return self.flow[-1] 14 | 15 | def send(self, to_user): 16 | 17 | if to_user.__class__ not in self.current.allowed: 18 | print( 19 | f"{self.current.__class__} is not allowed to send email to {to_user.__class__}") 20 | else: 21 | self.flow.append(to_user) 22 | print(f"message sent to {to_user.__class__}") 23 | 24 | 25 | class AbstractUser(ABC): 26 | @property 27 | @abstractmethod 28 | def allowed(self): 29 | pass 30 | 31 | 32 | class ManagerDirector(AbstractUser): 33 | allowed = [] 34 | 35 | 36 | class InternalManager(AbstractUser): 37 | allowed = [ManagerDirector] 38 | 39 | 40 | class Supervisor(AbstractUser): 41 | allowed = [InternalManager] 42 | 43 | 44 | class Operator(AbstractUser): 45 | allowed = [Supervisor] 46 | 47 | 48 | class Client(AbstractUser): 49 | allowed = [Operator] 50 | 51 | 52 | if __name__ == '__main__': 53 | opt = Operator() 54 | spr = Supervisor() 55 | inm = InternalManager() 56 | mnd = ManagerDirector() 57 | 58 | client = Client() 59 | message = Message("Issue #1234", "Issue description", client) 60 | 61 | message.send(opt) 62 | message.send(spr) 63 | message.send(inm) 64 | message.send(mnd) 65 | -------------------------------------------------------------------------------- /src/strategy.py: -------------------------------------------------------------------------------- 1 | """ 2 | Behavioral : 3 | Strategy 4 | """ 5 | 6 | 7 | class Product: 8 | def __init__(self, name, price): 9 | self.name = name 10 | self.price = price 11 | 12 | 13 | class Gateway: 14 | def __init__(self, name): 15 | self.name = name 16 | 17 | 18 | class Payment: 19 | gateway = (Gateway('G2'), Gateway('G1')) 20 | 21 | def __init__(self, purchase): 22 | self.purchase = purchase 23 | 24 | def get_gateway(self): 25 | return self.gateway[0] if self.purchase.total_price() < 100 else \ 26 | self.gateway[1] 27 | 28 | def pay(self): 29 | """if payment amount is less than 100,use G1 gateway otherwise use G2""" 30 | gateway = self.get_gateway() 31 | print(f"payment is being paid through {gateway.name}") 32 | 33 | 34 | class Purchase: 35 | def __init__(self): 36 | self.products = list() 37 | self.payment = Payment(self) 38 | 39 | def add_product(self, product): 40 | self.products.append(product) 41 | 42 | def total_price(self): 43 | return sum([p.price for p in self.products]) 44 | 45 | def checkout(self): 46 | self.payment.pay() 47 | 48 | 49 | if __name__ == '__main__': 50 | p1 = Product('p1', 40) 51 | p2 = Product('p1', 50) 52 | p3 = Product('p1', 20) 53 | 54 | purchase = Purchase() 55 | purchase.add_product(p1) 56 | print(purchase.total_price()) 57 | purchase.checkout() 58 | 59 | purchase.add_product(p2) 60 | print(purchase.total_price()) 61 | purchase.checkout() 62 | 63 | purchase.add_product(p3) 64 | print(purchase.total_price()) 65 | purchase.checkout() 66 | -------------------------------------------------------------------------------- /src/strategy_2.py: -------------------------------------------------------------------------------- 1 | """ 2 | Behavioral : 3 | Strategy 4 | """ 5 | from abc import ABC, abstractmethod 6 | 7 | ALLOWED_EXTENSIONS = ["html", 'csv', 'mp3', 'mp4', 'txt'] 8 | 9 | 10 | class AbstractRenderer(ABC): 11 | @abstractmethod 12 | def render(self): 13 | pass 14 | 15 | 16 | class HTMLRenderer(AbstractRenderer): 17 | def render(self): 18 | print("Render using HTMLRenderer") 19 | 20 | 21 | class MP3Renderer(AbstractRenderer): 22 | def render(self): 23 | print("Render using MP3Renderer") 24 | 25 | 26 | class MP4Renderer(AbstractRenderer): 27 | def render(self): 28 | print("Render using MP4Renderer") 29 | 30 | 31 | class FileHandler: 32 | def __init__(self, filename): 33 | self.filename = filename 34 | 35 | @property 36 | def extension(self): 37 | return self.filename.split('.')[-1] 38 | 39 | @classmethod 40 | def create(cls, filename): 41 | if filename.split('.')[-1] not in ALLOWED_EXTENSIONS: 42 | print("File extension not allowed") 43 | return cls(filename) 44 | 45 | def render(self): 46 | handler_dict = { 47 | 'html': HTMLRenderer, 48 | 'mp3': MP3Renderer, 49 | 'mp4': MP4Renderer 50 | } 51 | handler = handler_dict[self.extension] 52 | return handler().render() 53 | 54 | 55 | if __name__ == '__main__': 56 | f1 = FileHandler.create('doc.mp4') 57 | f2 = FileHandler.create('doc.html') 58 | f3 = FileHandler.create('doc.pdf') 59 | f4 = FileHandler.create('doc.mp3') 60 | 61 | files_list = [f1, f2, f4] 62 | for file_name in files_list: 63 | file_name.render() 64 | -------------------------------------------------------------------------------- /src/chain_of_responsibility.py: -------------------------------------------------------------------------------- 1 | """ 2 | Behavioral pattern: 3 | Chain of responsibility 4 | """ 5 | 6 | from abc import ABC, abstractmethod 7 | 8 | 9 | class AbstractHandler(ABC): 10 | def __init__(self, successor): 11 | self._successor = successor 12 | 13 | def handle(self, request): 14 | handled = self.process_request(request) 15 | if not handled: 16 | self._successor.handle(request) 17 | 18 | @abstractmethod 19 | def process_request(self, request): 20 | pass 21 | 22 | 23 | # ----------------------------------------------------------------- 24 | class HandlerOne(AbstractHandler): 25 | def process_request(self, request): 26 | if 0 < request <= 10: 27 | print(f'Handler One is processing this request... {request}') 28 | return True 29 | 30 | 31 | class HandlerTwo(AbstractHandler): 32 | def process_request(self, request): 33 | if 10 < request <= 20: 34 | print(f'Handler Two is processing this request... {request}') 35 | return True 36 | 37 | 38 | class DefaultHandler(AbstractHandler): 39 | def process_request(self, request): 40 | print( 41 | f'This request has no handler so default handler is processing ' 42 | f'this request... {request}') 43 | return True 44 | 45 | 46 | # ----------------------------------------------------------------- 47 | class Client: 48 | def __init__(self): 49 | self.handler = HandlerOne(HandlerTwo(DefaultHandler(None))) 50 | 51 | def delegate(self, requests): 52 | for request in requests: 53 | self.handler.handle(request) 54 | 55 | 56 | request = [3, 14, 34, 9] 57 | 58 | c1 = Client() 59 | c1.delegate(request) 60 | -------------------------------------------------------------------------------- /src/abstract_factory2.py: -------------------------------------------------------------------------------- 1 | """ 2 | Creational: 3 | abstract factory 4 | Car => Benz, Bmw => Suv, Coupe 5 | benz suv => gla, glc 6 | bmw suv => x1, x2 7 | 8 | benz coupe => cls, E-class 9 | bmw coupe => m2, m4 10 | """ 11 | from abc import ABC, abstractmethod 12 | 13 | 14 | class Car(ABC): 15 | @abstractmethod 16 | def call_suv(self): 17 | pass 18 | 19 | @abstractmethod 20 | def call_coupe(self): 21 | pass 22 | 23 | 24 | # ------------------------------------------------------------- 25 | class Benz(Car): 26 | def call_suv(self): 27 | return Gla() 28 | 29 | def call_coupe(self): 30 | return Cls() 31 | 32 | 33 | # ------------------------------------------------------------- 34 | class Bmw(Car): 35 | def call_suv(self): 36 | return X1() 37 | 38 | def call_coupe(self): 39 | return M2() 40 | 41 | 42 | # ------------------------------------------------------------- 43 | class Suv(ABC): 44 | @abstractmethod 45 | def creating_suv(self): 46 | pass 47 | 48 | 49 | class Coupe(ABC): 50 | @abstractmethod 51 | def creating_coupe(self): 52 | pass 53 | 54 | 55 | # ------------------------------------------------------------- 56 | class Gla(Suv): 57 | def creating_suv(self): 58 | print("This is your suv benz gla. . . ") 59 | 60 | 61 | class X1(Suv): 62 | def creating_suv(self): 63 | print("This is your suv bmw x1. . .") 64 | 65 | 66 | # ------------------------------------------------------------- 67 | class Cls(Coupe): 68 | def creating_coupe(self): 69 | print("This is your coupe benz cls. . .") 70 | 71 | 72 | class M2(Coupe): 73 | def creating_coupe(self): 74 | print("This is your coupe bmw m2. . .") 75 | 76 | 77 | # ------------------------------------------------------------- 78 | 79 | def client_suv(order): 80 | suv = order.call_suv() 81 | suv.creating_suv() 82 | 83 | 84 | def client_coupe(order): 85 | coupe = order.call_coupe() 86 | coupe.creating_coupe() 87 | 88 | 89 | # ------------------------------------------------------------- 90 | 91 | """Test the app""" 92 | 93 | client_coupe(Bmw()) 94 | client_coupe(Benz()) 95 | client_suv(Benz()) 96 | client_suv(Bmw()) 97 | -------------------------------------------------------------------------------- /src/decorator.py: -------------------------------------------------------------------------------- 1 | """ 2 | Structural : 3 | Decorator 4 | """ 5 | 6 | 7 | COUNTRIES = ['Iran', 'UAE'] 8 | VAT = {'Iran': 9, 'UAE': 15} 9 | 10 | 11 | class User: 12 | pass 13 | 14 | 15 | class Address: 16 | def __init__(self, country): 17 | self.country = country 18 | 19 | 20 | class Product: 21 | def __init__(self, name, price): 22 | self.name = name 23 | self.price = price 24 | 25 | 26 | class Purchase: 27 | def __init__(self, user, address): 28 | self.user = user 29 | self.address = address 30 | self.products_list = [] 31 | 32 | def add_product_list(self, product_list): 33 | if not isinstance(product_list, list): 34 | product_list = [product_list] 35 | self.products_list.extend(product_list) 36 | 37 | def total_price(self): 38 | result = 0 39 | for product in self.products_list: 40 | result += product.price 41 | return result 42 | 43 | 44 | def calculate_vat(func): 45 | def wrapped_func(purchase_item): 46 | vat = VAT[purchase_item.address.country] 47 | total_price = purchase_item.total_price() 48 | return total_price + total_price * vat / 100 49 | 50 | return wrapped_func 51 | 52 | 53 | def show_total_price(purchase_item): 54 | return purchase_item.total_price() 55 | 56 | 57 | @calculate_vat 58 | def show_vat_plus_price(purchase_item): 59 | return purchase_item.total_price() 60 | 61 | 62 | if __name__ == '__main__': 63 | user = User() 64 | add_iran = Address(country=COUNTRIES[0]) 65 | add_uae = Address(country=COUNTRIES[1]) 66 | 67 | p1 = Product('persian rug', 145) 68 | p2 = Product('uae rug', 160) 69 | p3 = Product('turk rug', 180) 70 | products = [p1, p2, p3] 71 | 72 | purchase_iran = Purchase(user=user, address=add_iran) 73 | purchase_uae = Purchase(user=user, address=add_uae) 74 | 75 | purchase_iran.add_product_list(p1) 76 | purchase_uae.add_product_list(p1) 77 | purchase_iran.add_product_list([p2, p3]) 78 | purchase_uae.add_product_list([p2, p3]) 79 | print(show_total_price(purchase_iran)) 80 | print(show_total_price(purchase_uae)) 81 | print(show_vat_plus_price(purchase_iran)) 82 | print(show_vat_plus_price(purchase_uae)) 83 | -------------------------------------------------------------------------------- /src/abstract_factory.py: -------------------------------------------------------------------------------- 1 | """ 2 | Creational : 3 | Abstract Factory 4 | """ 5 | 6 | from abc import ABC, abstractmethod 7 | 8 | 9 | class ProductBase(ABC): 10 | """ 11 | this class in Product Factory Class 12 | """ 13 | 14 | @abstractmethod 15 | def detail(self): 16 | pass 17 | 18 | @abstractmethod 19 | def price(self): 20 | pass 21 | 22 | # @abstractmethod 23 | # def shipping(self): 24 | # pass 25 | 26 | 27 | class DetailBase(ABC): 28 | @abstractmethod 29 | def show(self): 30 | pass 31 | 32 | 33 | class RugsDetail(DetailBase): 34 | def __init__(self, rugs): 35 | self.rugs = rugs 36 | 37 | def show(self): 38 | return f"rugs detail: {self.rugs._name}" 39 | 40 | 41 | class RugsPrice(DetailBase): 42 | def __init__(self, rugs): 43 | self.rugs = rugs 44 | 45 | def show(self): 46 | return f"rugs price: {self.rugs._price}" 47 | 48 | 49 | class GiftCardDetail(DetailBase): 50 | def __init__(self, card): 51 | self.giftCard = card 52 | 53 | def show(self): 54 | return f"company : {self.giftCard.company}," 55 | 56 | 57 | class GiftCardPrice(DetailBase): 58 | def __init__(self, card): 59 | self.giftCard = card 60 | 61 | def show(self): 62 | return f"gift card min price : {self.giftCard.min_price},max price : " \ 63 | f"{self.giftCard.max_price} " 64 | 65 | 66 | class Rugs(ProductBase): 67 | def __init__(self, name, price): 68 | self._name = name 69 | self._price = price 70 | 71 | @property 72 | def detail(self): 73 | return RugsDetail(self) 74 | 75 | @property 76 | def price(self): 77 | return RugsPrice(self) 78 | 79 | 80 | class GiftCard(ProductBase): 81 | 82 | def __init__(self, company, min_price, max_price): 83 | self.company = company 84 | self.min_price = min_price 85 | self.max_price = max_price 86 | 87 | @property 88 | def detail(self): 89 | return GiftCardDetail(self) 90 | 91 | @property 92 | def price(self): 93 | return GiftCardPrice(self) 94 | 95 | 96 | if __name__ == '__main__': 97 | r1 = Rugs('persian', 135) 98 | r2 = Rugs('armani', 155) 99 | 100 | g1 = GiftCard("google", 20, 50) 101 | g2 = GiftCard("Apple", 40, 100) 102 | 103 | products = [r1, r2, g1, g2] 104 | for product in products: 105 | print(product.detail.show()) 106 | print(product.price.show()) 107 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '37 22 * * 3' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | language: [ 'python' ] 32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 33 | # Learn more: 34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 35 | 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v2 39 | 40 | # Initializes the CodeQL tools for scanning. 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@v1 43 | with: 44 | languages: ${{ matrix.language }} 45 | # If you wish to specify custom queries, you can do so here or in a config file. 46 | # By default, queries listed here will override any specified in a config file. 47 | # Prefix the list here with "+" to use these queries and those in the config file. 48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 49 | 50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 51 | # If this step fails, then you should remove it and run the build manually (see below) 52 | - name: Autobuild 53 | uses: github/codeql-action/autobuild@v1 54 | 55 | # ℹ️ Command-line programs to run using the OS shell. 56 | # 📚 https://git.io/JvXDl 57 | 58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 59 | # and modify them (or add more) to build your code if your project 60 | # uses a compiled language 61 | 62 | #- run: | 63 | # make bootstrap 64 | # make release 65 | 66 | - name: Perform CodeQL Analysis 67 | uses: github/codeql-action/analyze@v1 68 | -------------------------------------------------------------------------------- /src/builder.py: -------------------------------------------------------------------------------- 1 | """ 2 | Creational : 3 | Builder => every builders designed patterns has director class to attach 4 | each side of objects 5 | """ 6 | 7 | 8 | class Director: 9 | __builder = None 10 | 11 | def set_builder(self, builder): 12 | self.__builder = builder 13 | 14 | def get_car(self): 15 | car = Car() 16 | 17 | body = self.__builder.get_body() 18 | car.set_body(body) 19 | 20 | wheel = self.__builder.get_wheel() 21 | car.set_wheel(wheel) 22 | 23 | engine = self.__builder.get_engine() 24 | car.set_engine(engine) 25 | return car 26 | 27 | 28 | # ---------------------------------------- 29 | class Car: 30 | def __init__(self): 31 | self.__wheel = None 32 | self.__engine = None 33 | self.__body = None 34 | 35 | def set_wheel(self, wheel): 36 | self.__wheel = wheel 37 | 38 | def set_body(self, body): 39 | self.__body = body 40 | 41 | def set_engine(self, engine): 42 | self.__engine = engine 43 | 44 | def detail(self): 45 | print(f'Body: {self.__body.shape}') 46 | print(f'Engine: {self.__engine.hp}') 47 | print(f'Wheel: {self.__wheel.size}') 48 | 49 | 50 | # ---------------------------------------- 51 | class Builder: 52 | def get_engine(self): 53 | pass 54 | 55 | def get_wheel(self): 56 | pass 57 | 58 | def get_body(self): 59 | pass 60 | 61 | 62 | class Benz(Builder): 63 | def get_body(self): 64 | body = Body() 65 | body.shape = 'Suv' 66 | return body 67 | 68 | def get_engine(self): 69 | engine = Engine() 70 | engine.hp = 500 71 | return engine 72 | 73 | def get_wheel(self): 74 | wheel = Wheel() 75 | wheel.size = 22 76 | return wheel 77 | 78 | 79 | class Bmw(Builder): 80 | def get_body(self): 81 | body = Body() 82 | body.shape = 'Sedan' 83 | return body 84 | 85 | def get_engine(self): 86 | engine = Engine() 87 | engine.hp = 340 88 | return engine 89 | 90 | def get_wheel(self): 91 | wheel = Wheel() 92 | wheel.size = 18 93 | return wheel 94 | 95 | 96 | # ---------------------------------------- 97 | class Wheel: 98 | size = None 99 | 100 | 101 | class Body: 102 | shape = None 103 | 104 | 105 | class Engine: 106 | hp = None 107 | 108 | 109 | # ---------------------------------------- 110 | 111 | 112 | car1 = Bmw() 113 | director = Director() 114 | director.set_builder(car1) 115 | b1 = director.get_car() 116 | b1.detail() 117 | -------------------------------------------------------------------------------- /src/proxy.py: -------------------------------------------------------------------------------- 1 | """ 2 | Structural pattern : 3 | Proxy 4 | """ 5 | 6 | COUNTRIES = ['Iran', 'UAE'] 7 | VAT = {'Iran': 9, 'UAE': 15} 8 | 9 | 10 | def check_permission(func): 11 | def wrapped_func(obj, user): 12 | if obj.user == user: 13 | return func(obj) 14 | return "You are not allowed to checkout" 15 | 16 | return wrapped_func 17 | 18 | 19 | class User: 20 | pass 21 | 22 | 23 | class Address: 24 | def __init__(self, country): 25 | self.country = country 26 | 27 | 28 | class Product: 29 | def __init__(self, name, price): 30 | self.name = name 31 | self.price = price 32 | 33 | 34 | class Purchase: 35 | def __init__(self, user, address): 36 | self.user = user 37 | self.address = address 38 | self.products_list = [] 39 | 40 | def add_product_list(self, product_list): 41 | if not isinstance(product_list, list): 42 | product_list = [product_list] 43 | self.products_list.extend(product_list) 44 | 45 | def total_price(self): 46 | result = 0 47 | for product in self.products_list: 48 | result += product.price 49 | return result 50 | 51 | @check_permission 52 | def checkout(self): 53 | return "checkout done!" 54 | 55 | 56 | def calculate_vat(func): 57 | def wrapped_func(purchase_item): 58 | vat = VAT[purchase_item.address.country] 59 | total_price = purchase_item.total_price() 60 | return total_price + total_price * vat / 100 61 | 62 | return wrapped_func 63 | 64 | 65 | def show_total_price(purchase_item): 66 | return purchase_item.total_price() 67 | 68 | 69 | @calculate_vat 70 | def show_vat_plus_price(purchase_item): 71 | return purchase_item.total_price() 72 | 73 | 74 | if __name__ == '__main__': 75 | user_1 = User() 76 | add_iran = Address(country=COUNTRIES[0]) 77 | add_uae = Address(country=COUNTRIES[1]) 78 | 79 | p1 = Product('persian rug', 145) 80 | p2 = Product('uae rug', 160) 81 | p3 = Product('turk rug', 180) 82 | products = [p1, p2, p3] 83 | 84 | purchase_iran = Purchase(user=user_1, address=add_iran) 85 | purchase_uae = Purchase(user=user_1, address=add_uae) 86 | 87 | purchase_iran.add_product_list(p1) 88 | purchase_uae.add_product_list(p1) 89 | purchase_iran.add_product_list([p2, p3]) 90 | purchase_uae.add_product_list([p2, p3]) 91 | # print(show_total_price(purchase_iran)) 92 | # print(show_total_price(purchase_uae)) 93 | # print(show_vat_plus_price(purchase_iran)) 94 | # print(show_vat_plus_price(purchase_uae)) 95 | 96 | user_2 = User() 97 | print(purchase_iran.checkout(user_1)) 98 | print(purchase_iran.checkout(user_2)) 99 | -------------------------------------------------------------------------------- /src/composit.py: -------------------------------------------------------------------------------- 1 | """ 2 | structural: 3 | Composite 4 | Example: 5 | when want to set tree 6 | """ 7 | 8 | from abc import ABC, abstractmethod 9 | 10 | 11 | class World(ABC): 12 | @abstractmethod 13 | def show(self): pass 14 | 15 | 16 | class Being(World): 17 | def __init__(self, name): 18 | self.name = name 19 | self.children = [] 20 | 21 | def add(self, object): 22 | self.children.append(object) 23 | 24 | def remove(self, object): 25 | self.children.remove(object) 26 | 27 | def show(self): 28 | print(f'Being Composite {self.name}') 29 | for child in self.children: 30 | child.show() 31 | 32 | 33 | class Animal(World): 34 | def __init__(self, name): 35 | self.name = name 36 | self.children = [] 37 | 38 | def add(self, object): 39 | self.children.append(object) 40 | 41 | def remove(self, object): 42 | self.children.remove(object) 43 | 44 | def show(self): 45 | print(f'\tAnimal Composite {self.name}') 46 | for child in self.children: 47 | child.show() 48 | 49 | 50 | class Human(World): 51 | def __init__(self, name): 52 | self.name = name 53 | self.children = [] 54 | 55 | def add(self, object): 56 | self.children.append(object) 57 | 58 | def remove(self, object): 59 | self.children.remove(object) 60 | 61 | def show(self): 62 | print(f'\tHuman Composite {self.name}') 63 | for child in self.children: 64 | child.show() 65 | 66 | 67 | class Cat(World): 68 | def __init__(self, name): 69 | self.name = name 70 | 71 | def show(self): 72 | print(f'\t\tCat Leaf {self.name}') 73 | 74 | 75 | class Dog(World): 76 | def __init__(self, name): 77 | self.name = name 78 | 79 | def show(self): 80 | print(f'\t\tDog Leaf {self.name}') 81 | 82 | 83 | class Male(World): 84 | def __init__(self, name): 85 | self.name = name 86 | 87 | def show(self): 88 | print(f'\t\tMale Leaf {self.name}') 89 | 90 | 91 | class Female(World): 92 | def __init__(self, name): 93 | self.name = name 94 | 95 | def show(self): 96 | print(f'\t\tFemale Leaf {self.name}') 97 | 98 | 99 | if __name__ == '__main__': 100 | 101 | cat = Cat('Missy') 102 | dog = Dog('Jack') 103 | 104 | male = Male('Mark') 105 | female = Female('Jane') 106 | 107 | animal = Animal('animal1') 108 | human = Human('human1') 109 | 110 | animal.add(cat) 111 | animal.add(dog) 112 | 113 | human.add(male) 114 | human.add(female) 115 | 116 | being = Being('all') 117 | being.add(animal) 118 | being.add(human) 119 | 120 | being.show() 121 | 122 | # Being Composite all 123 | # Animal Composite animal 124 | # Animal Leaf Missy 125 | # Animal Leaf Jack 126 | # Human Composite human 127 | # Human Leaf Mark 128 | # Human Leaf Jane 129 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Design Patterns Python 2 | ![alt Design Patterns Python](assets/img/design-patterns-python.png "Design Patterns Python") 3 | 4 | 5 | 6 | ## Table of contents 7 | 8 | * [General info](#General-info) 9 | * [Attention](#Attention) 10 | * [Technologies](#Technologies) 11 | * [Design Pattern Map](#Design-Pattern-Map) 12 | * [Help](#Help) 13 | * [Setup](#Setup) 14 | 15 | 16 | 17 | ## General info 18 | 19 | Hi , this is [Reza Mobaraki](https://www.linkedin.com/in/reza-mobaraki/) 20 | 21 | these are the most important design patterns 22 | 23 | you can read and try design patterns with this code 24 | 25 | 26 | #### Attention 27 | 28 | Some pattern designs are implemented in several ways 29 | 30 | 31 | 32 | ## Technologies 33 | 34 | Project is created with: 35 | 36 | * Python: 3.6^ 37 | 38 | 39 | ## Design Pattern Map 40 | 41 | This is a good cheat sheet to review and remind the design of important patterns 42 | 43 | ![alt Design Patterns Map](assets/img/design-pattern-map.png "Design Patterns Python Map") 44 | 45 | 46 | 47 | ## Help 48 | 49 | In this repository we work on 3 type on **design patterns** 50 | 51 | * Creational => (Singleton , Factory , Abstract Factory , Prototype) 52 | * Structural => (Adapter , Decorator , Proxy , Composite) 53 | * Behavioral => (Observer , State , Strategy , Chain of responsibility , Template method) 54 | 55 | The tree structure of these three pattern designs is as follows 56 | 57 | ```shell 58 | DesignPattern git:(master) tree 59 | ``` 60 | ```markdown 61 | . 62 | ├── README.md 63 | ├── __pycache__ 64 | │   └── abstract_factory.cpython-39.pyc 65 | ├── abstract_factory.py 66 | ├── abstract_factory2.py 67 | ├── abstract_factory3.py 68 | ├── adapter.py 69 | ├── adapter2.py 70 | ├── builder.py 71 | ├── chain_of_responsibility.py 72 | ├── composit.py 73 | ├── decorator.py 74 | ├── decorator2.py 75 | ├── facade.py 76 | ├── factory.py 77 | ├── factory2.py 78 | ├── iterator.py 79 | ├── observer 80 | │   ├── __init__.py 81 | │   ├── __pycache__ 82 | │   │   ├── __init__.cpython-39.pyc 83 | │   │   ├── decorator.cpython-39.pyc 84 | │   │   ├── notification.cpython-39.pyc 85 | │   │   └── shop.cpython-39.pyc 86 | │   ├── decorator.py 87 | │   ├── main.py 88 | │   ├── notification.py 89 | │   └── shop.py 90 | ├── observer2.py 91 | ├── prototype.py 92 | ├── prototype2.py 93 | ├── proxy.py 94 | ├── proxy2.py 95 | ├── proxy_dice.py 96 | ├── proxy_lazy.py 97 | ├── singleton.py 98 | ├── singleton2.py 99 | ├── state.py 100 | ├── state2.py 101 | ├── strategy.py 102 | ├── strategy3.py 103 | ├── strategy_2.py 104 | └── template_method.py 105 | 106 | 3 directories, 40 files 107 | ``` 108 | 109 | ## Setup 110 | 111 | To run this project, install it locally using git-clone: 112 | 113 | ```shell 114 | gh repo clone https://github.com/rezamobaraki/design-patterns-python.git 115 | 116 | #or 117 | 118 | git clone https://github.com/rezamobaraki/design-patterns-python.git 119 | ``` 120 | 121 | 122 | ## License 123 | 124 | Distributed under the MIT License. See [license](LICENSE.txt) for more information. 125 | -------------------------------------------------------------------------------- /src/abstract_factory3.py: -------------------------------------------------------------------------------- 1 | """ 2 | Creational: 3 | Abstract Factory: 4 | Car => 1.benz, 2.bmw => 1.suv, 2.coupe 5 | benz suv => gla, glc 6 | bmw suv => x1, x2 7 | benz coupe => cls, E-class 8 | bmw coupe => m2, m4 9 | """ 10 | from abc import ABC, abstractmethod 11 | 12 | 13 | class Car(ABC): 14 | @abstractmethod 15 | def call_suv(self): 16 | pass 17 | 18 | @abstractmethod 19 | def call_coupe(self): 20 | pass 21 | 22 | 23 | # ---------------------------------------------------------- 24 | class Benz(Car): 25 | @staticmethod 26 | def call_suv(model): 27 | return model 28 | 29 | @staticmethod 30 | def call_coupe(model): 31 | return model 32 | 33 | 34 | class Bmw(Car): 35 | @staticmethod 36 | def call_suv(model): 37 | return model 38 | 39 | @staticmethod 40 | def call_coupe(model): 41 | return model 42 | 43 | 44 | # ---------------------------------------------------------- 45 | class Suv(ABC): 46 | @abstractmethod 47 | def creating_suv(self): 48 | pass 49 | 50 | 51 | class Coupe(ABC): 52 | @abstractmethod 53 | def creating_coupe(self): 54 | pass 55 | 56 | 57 | # ---------------------------------------------------------- 58 | class Gla(Suv, Benz): 59 | def creating_suv(self): 60 | print('This is your suv Benz Gla...') 61 | 62 | 63 | class Glc(Suv, Benz): 64 | def creating_suv(self): 65 | print('This is your suv Benz Glc...') 66 | 67 | 68 | class X1(Suv, Bmw): 69 | def creating_suv(self): 70 | print('This is your suv Bmw X1...') 71 | 72 | 73 | class X2(Suv, Bmw): 74 | def creating_suv(self): 75 | print('This is your suv Bmw X2...') 76 | 77 | 78 | # ---------------------------------------------------------- 79 | class Cls(Coupe, Benz): 80 | def creating_coupe(self): 81 | print('This is your coupe Benz Cls...') 82 | 83 | 84 | class Eclass(Coupe, Benz): 85 | def creating_coupe(self): 86 | print('This is your coupe Benz E-class...') 87 | 88 | 89 | class M2(Coupe, Bmw): 90 | def creating_coupe(self): 91 | print('This is your coupe Bmw M2...') 92 | 93 | 94 | class M4(Coupe, Bmw): 95 | def creating_coupe(self): 96 | print('This is your coupe Bmw M4...') 97 | 98 | 99 | # ---------------------------------------------------------- 100 | def order_suv(corp, 101 | model): # corp is the company, model is the model of the car 102 | if issubclass(model, corp): 103 | # check if the model is related to corp, so you cannot call M2 from Benz 104 | suv = corp().call_suv(model()) 105 | suv.creating_suv() 106 | else: 107 | raise NameError() 108 | 109 | 110 | def order_coupe(corp, model): 111 | if issubclass(model, corp): 112 | coupe = corp().call_coupe(model()) 113 | coupe.creating_coupe() 114 | else: 115 | raise NameError() 116 | 117 | 118 | try: 119 | order_coupe(Benz, Cls) 120 | except (NameError, AttributeError): 121 | # calling Corporation incorrectly will raise NameError, calling Model 122 | # incorrectly will raise AttributeError 123 | print('Sorry, we dont have this model....') 124 | 125 | try: 126 | order_suv(Bmw, Glc) 127 | except (NameError, AttributeError): 128 | print('Sorry, we dont have this model....') 129 | --------------------------------------------------------------------------------