Notes about Design Patterns & Clean Coding
Last Updated: June 06, 2021 by Pepe Sandoval
If you find the information in this page useful and want to show your support, you can make a donation
Use PayPal
This will help me create more stuff and fix the existent content...
A design pattern is named a solution to a problem in a context
A pattern is a solution used when facing certain problem in software
Types of patterns:
All patterns are used for management of dependencies, their purpose is to figure out how to separate the elements of your system, they assist with the design of the system
Most times this pattern is composed of an interface named Command
and a single method named execute
It is way to decouple what is done from who does it (Separate actors from actions)
We can use it to separate what a task does from who does it and also when it gets done
A command is represented with an object so it could save state/information about what it did to undo or redo, so the command pattern is a good way to implemented undo functionality
The actor model/Run-to-Completion model for threads The way a thread waits is that it puts a command on the list that checks an event and if the event hasn't occurred the command put itself on the list otherwise puts a command with an actual action
The purpose of the factory pattern is to provide a mechanism for the independent deployable applications to create the objects they use without depending on those objects
The factories are trying to keep the people who use the objects and the people who create the objects from knowing about each other so they are not interfering over each other
If you have a problem you don't look up a pattern that solves it, usually you know the patterns so when you start coding or growing your application you can see how the patterns fit into the application
Both Strategy & Template Method Patterns solve the same problem but have different implementation costs and benefits
These patterns allows us to separate high level policy from a set of low level details/implementation, this is done by having the high level policy delegate through an interface and derivatives that implement that interface (polymorphism)
Costs and benefits
Don't just use a pattern because you can, use them when they solve a problem
Finites state machine:
Subway Turnstile FSM (Given=State, When=Event, Then=Invoke Function)
GUI as a FSM
We should be able to put a FSM in its own module, isolated from the rest of the system, no need to add all the control logic into the program itself.
A FSM benefits from an implementation that separate the FSM from it's actions and events
State Machine representations
The separation of how (to do something) and what (is being done) is an essential aspect of good SW design so we can separate operation from policy
set_state
), holds reference to a state interfaceset_state
# python3 -m unittest test_fsm.py from unittest import TestCase from abc import ABC class TurnstileState_Interface: def __init__(self): pass def person_pass_event(self, fsm): pass def coin_event(self, fsm): pass class OneCoinTurnstileState_Locked(TurnstileState_Interface): def __init__(self): pass def person_pass_event(self, fsm): #print("person_pass_event in OneCoinTurnstileState_Locked state") fsm.alarm() def coin_event(self, fsm): #print("coin_event in OneCoinTurnstileState_Locked state") fsm.set_state(TursntileFSMOneCoin.unlocked_state) fsm.unlock() class OneCoinTurnstileState_Unlocked(TurnstileState_Interface): def __init__(self): pass def person_pass_event(self, fsm): #print("person_pass_event in OneCoinTurnstileState_Unlocked state") fsm.set_state(TursntileFSMOneCoin.locked_state) fsm.lock() def coin_event(self, fsm): #print("coin_event in OneCoinTurnstileState_Unlocked state") fsm.thankyou() class TursntileFSM(ABC): def __init__(self): self.state = None def person_pass_event(self): self.state.person_pass_event(self) def coin_event(self): self.state.coin_event(self) def set_state(self, state): self.state = state def alarm(self): pass def lock(self): pass def unlock(self): pass def thankyou(self): pass class TursntileFSMOneCoin(TursntileFSM): unlocked_state = OneCoinTurnstileState_Unlocked() locked_state = OneCoinTurnstileState_Locked() def __init__(self): super(TursntileFSMOneCoin, self).__init__() self.set_state(TursntileFSMOneCoin.locked_state) self.actions = [] def alarm(self): #print("alarm") self.actions.append("alarm") def lock(self): #print("lock") self.actions.append("lock") def unlock(self): #print("unlock") self.actions.append("unlock") def thankyou(self): #print("thankyou") self.actions.append("thankyou") class FSMTest(TestCase): def test_fsm_normal_operation(self): fsm_obj = TursntileFSMOneCoin() fsm_obj.coin_event() self.assertEqual(fsm_obj.actions, ["unlock"]) fsm_obj.person_pass_event() self.assertEqual(fsm_obj.actions, ["unlock", "lock"]) def test_fsm_forced_entry(self): fsm_obj = TursntileFSMOneCoin() fsm_obj.person_pass_event() self.assertEqual(fsm_obj.actions, ["alarm"]) def test_fsm_double_pay(self): fsm_obj = TursntileFSMOneCoin() fsm_obj.coin_event() fsm_obj.coin_event() self.assertEqual(fsm_obj.actions, ["unlock", "thankyou"]) def test_fsm_n_pay(self): fsm_obj = TursntileFSMOneCoin() for i in range(5): fsm_obj.coin_event() self.assertEqual(fsm_obj.actions, ["unlock", "thankyou", "thankyou", "thankyou", "thankyou"]) def test_fsm_n_pay_then_pass(self): fsm_obj = TursntileFSMOneCoin() for i in range(5): fsm_obj.coin_event() fsm_obj.person_pass_event() self.assertEqual(fsm_obj.actions, ["unlock", "thankyou", "thankyou", "thankyou", "thankyou", "lock"]) def test_fsm_normal_operation_two_user_pass(self): fsm_obj = TursntileFSMOneCoin() fsm_obj.coin_event() self.assertEqual(fsm_obj.actions, ["unlock"]) fsm_obj.person_pass_event() self.assertEqual(fsm_obj.actions, ["unlock", "lock"]) fsm_obj.coin_event() self.assertEqual(fsm_obj.actions, ["unlock", "lock", "unlock"]) fsm_obj.person_pass_event() self.assertEqual(fsm_obj.actions, ["unlock", "lock", "unlock", "lock"])
Backus Naur Form BNF is a notation used to describe computer languages
::=
composed of|
Or*
Many:
<header>
Name followed by a colon (:
) followed by another nameLexical Analyzer (Lexer) takes the stream of characters and transforms it into a stream of tokens
Test fisrt when you can, test after when you must but test
Behavior which is polymorphic to a hierarchy but that does not belong in that hierarchy
You have a hierarchy of classes and you want to put a new behavior on that hierarchy, each time you call the behavior on a particular instance you want a particular behavior (polymorphic) but you don't want the code that implements that behavior as part of the hierarchy
You add to the base class a method called accept
which takes as argument an instance of a visitor (interface) that has a visti
method that takes as argument an instance of the derivatives classes (you can have one visit
method for each of the derivatives of the hierarchy or a single visit
method that handles the different derivatives)
accept
is abstract in the base class and in the derivatives it is implemented identically by calling the visit
method and passing as argument the current instance
The visitor patterns allows to add polymorphic functionality/behavior to existing hierarchies without changing the hierarchies (in other words by just adding one method accept
to base class and one visit
to each derivative)
class Modem: def __init__(self): pass def send(self): pass def recv(self): pass def dial(self): pass def hangup(self): pass def accept(self, modem_visitor): pass class HayesModem(Modem): def __init__(self): pass def send(self): print(f"{self.__class__.__name__}", "send") def recv(self): print(f"{self.__class__.__name__}", "recv") def dial(self): print(f"{self.__class__.__name__}", "dial") def hangup(self): print(f"{self.__class__.__name__}", "hangup") def accept(self, visitor): print(f"{self.__class__.__name__}", "accept", f"{visitor.__class__.__name__}") visitor.visit_hayes(self) class USRModem(Modem): def __init__(self): pass def send(self): print(f"{self.__class__.__name__}", "send") def recv(self): print(f"{self.__class__.__name__}", "recv") def dial(self): print(f"{self.__class__.__name__}", "dial") def hangup(self): print(f"{self.__class__.__name__}", "hangup") def accept(self, visitor): print(f"{self.__class__.__name__}", "accept", f"{visitor.__class__.__name__}") visitor.visit_usr(self) class ModemVisitor(): def __init__(self): pass def visit_hayes(self, visitor): pass def visit_usr(self, visitor): pass class UnixModemConfiguration(ModemVisitor): def __init__(self): pass def visit_hayes(self, hayes_visitor): print(f"{self.__class__.__name__}", "visit_hayes called with access to", f"{hayes_visitor.__class__.__name__}") hayes_visitor.dial() hayes_visitor.hangup() def visit_usr(self, usr_visitor): print(f"{self.__class__.__name__}", "visit_usr called with access to", f"{usr_visitor.__class__.__name__}") usr_visitor.send() usr_visitor.recv() class WindowsModemConfiguration(ModemVisitor): def __init__(self): pass def visit_hayes(self, hayes_visitor): print(f"{self.__class__.__name__}", "visit_hayes called with access to", f"{hayes_visitor.__class__.__name__}") hayes_visitor.send() hayes_visitor.recv() def visit_usr(self, usr_visitor): print(f"{self.__class__.__name__}", "visit_usr called with access to", f"{usr_visitor.__class__.__name__}") usr_visitor.dial() usr_visitor.hangup() myusr_modem = USRModem() myhayes_modem = HayesModem() print("\nLinux") os_modem_visitor = UnixModemConfiguration() myusr_modem.accept(os_modem_visitor) myhayes_modem.accept(os_modem_visitor) print("\nWindows") os_modem_visitor = WindowsModemConfiguration() myusr_modem.accept(os_modem_visitor) myhayes_modem.accept(os_modem_visitor)
The visitor pattern creates a dependency cycle: The base class depends on the visitors and the visitor depends on the derivatives, so any change to any class in the cycle requires you to recompile and redeploy all the classes in the cycle and also any other class outside the cycle that uses the classes in the cycle
Acyclic Visitor is an implementation of the visitor patter used with type checks (instanceof
) needed for statically typed languages, used when we suspect more derivatives willl be added to the hierarchy
There are Two hierarchies one hierarchy is being visited and one hierarchy is doing the visiting, these form a matrix of functions
Used when you have an object that can change for various reason and another object(s) wants to be notified of those changes, it is a formal way of creating callbacks
We need to call the register
method and pass as instance the class that implements the observer interface when the object that detects event notices a change it calls the notify
that must loopd through all the observers registered calling the update on each one
pull observer: when the callback is called the observer it pulls data from the object it's observing
push observer: when the call notify
we pass data which is passed to the update
method so the observed object pushes data to the observer
We have two objects: one that calculates a value the cause and other which uses the value the effect
The names of good things loose their meaning because people steal those names in order to steal the good reputation. When structured, objects, service-oriented, agile, functional, modular were considered good many frameworks and other things added structured, object-oriented, service-oriented, agile, functional, modular to their names
Pattern used in the small
Model Object that has data that is changing due to interactions
View understands how to represent the data in the model
Controller understands how to take inputs from user and convert them to commands the model can understand
The controller sends commands to model and if data changes the model updates the view
Presenter object that gathers, organizes all of the model data, format it if necessary so the only job of the view is just get data out of the presenter into the GUI or other display method
Singleton is used when you only want to create one object on your system
Monostate is a single but transparent to the users, you use it when you don't want them to know there will only be one object
null
/None
checks in your systemclass Employee: def __init__(self, name): self.name = name self.salary = 0.0 self.pay = 0.0 self.deductions = 0.0 def is_pay_day(self): # Here we would have logic to determine if it is really a payday return True def calculate_pay(self): # Here we would have logic to calculate pay self.pay = self.salary*30.0 print(f"Calculated salary {self.salary}") def calculate_deductions(self): # Here we would have logic to calculate deductions self.deductions = self.salary*0.30 print(f"Calculated deductions {self.deductions}") def get_gross_pay(self): return self.pay - self.deductions def send_pay(self): # Here we would have logic to send pay print(f"Sending gross pay {self.get_gross_pay()} to {self.name}") class NullEmployee(Employee): def __init__(self): super(NullEmployee, self).__init__("") def is_pay_day(self): return False def calculate_pay(self): pass def calculate_deductions(self): pass def get_gross_pay(self): return 0.0 def send_pay(self): pass class PayRollDB: def __init__(self): pass def get_all_employees_ids(self): print(f"Here the code would query the DB to get a list of all the employee IDs") return [None] def find_employee_by_id(self, eid): print(f"Here the code would fetch data from the DB using ID={eid} and create Employee object with that data, if employee not found would create NullEmployee") #data_from_db = {"name": "Pepe"} data_from_db = None if data_from_db: e = Employee(data_from_db.get("name")) else: e = NullEmployee() return e class PayRoll: def __init__(self, db): self.employee_db = db self.employee_ids = self.employee_db.get_all_employees_ids() def calculate_payroll_for_employees(self): for eid in self.employee_ids: e = self.employee_db.find_employee_by_id(eid) if e.is_pay_day(): print(f"It is payday for employee {e}") e.calculate_pay() e.calculate_deductions() e.send_pay() my_db = PayRollDB() my_payroll_app = PayRoll(my_db) my_payroll_app.calculate_payroll_for_employees()
The proxy belongs to the decorators family, which are a family of patterns that have the intention to add functionality to classes or hierarchies without significantly affecting the hierarchy
The decorators have the following structure:
Base
interface with all the methods that need to be implementedimp
) of the Base
interfaceBase
interface that holds an instance of one of the imp
with the purpose of adding extra functionality to this specific implementationIt has the intention to imposing a policy upon another set of objects (constraint the way the objects communicate with each other and/or the outside world)
If you have a group of objects that collaborate to provide a function and client that wants to use this function, the Facade will interact with the group of objects and provide an interface to the client that allows the communication with the group of objects but also constrains the kind of communication (the facade is imposing a policy)
The facade imposes policy upon a group of low level implementation classes, so the facade has control over what clients are allowed to ask for and how low level classes are allowed to respond
Imposes policy on the communication between objects (Imposes policy from behind the scenes)
It works behind the scenes to impose a policy on a group of objects, neither the objects nor the clients of those objects know the mediator exists
A common implementation includes an object that holds a reference to two objects, catches events from one object and uses the other object to perform an action
It is used when an objects wants to share a record of its internal state to some other part of the system, the system can store this record and then had it back to the originating object, this object can reconstitute itself from this recorded state or the system can also create a new objects from the recorded state but remains private to the originating client
The Memento object saves/contains state of another object with the intention to go back to that state or create a new object using that state (E.g. if we want to save state of a chess board, we create a memento with this info then we can use this memento to create a new board or make the original board have this state)
A good implementation of the memento makes the memento an object that can only be used by the class that creates it but any other object can store it or hold on to it
When you have a group of objects that can be represented by different possible inheritance hierarchies
Degree of freedom a hierarchy of classes gives freedom to the programmers to implement new functionality, we have a base class and we can implements one or more derivatives, this is a degree of freedom
Used to avoid m*n
type of problems and convert them into m+n
problem so instead of having derivatives of derivatives we join two hierarchies (Two strategy patterns)
Binds multiple strategies into a chain of stages/ stations and each stage performs a task as pass to the next one but it doesn't know anything about the next station
It help us deal with multiple degrees of freedom, each degree of freedom has its own inheritance hierarchy
Used when you have a client that can be served by many different services and want to hide to the client what service(s) is doing the job
We pass an object through a chain of instances that look at an object and determine if it should apply itself or not, then forwards to next instance
Checks an object to see if a stage of processing should be invoked, if it shouldn't be invoked it just passes to next stage, if it should be invoked does calculations and decides if it should pass the object to next stage
All problems in software can be solved by adding another layer of indirection
It is a way of hiding containers of objects and representing them instead as a flow or stream of objects
Lazy list is a list whose elements are only evaluated when they are used, they don't exist before that
The iterator pattern is equivalent to a generator in python and is used to create lists that appear to be infinite but that are evaluated lazy
The iterators is an object that returns the next
element in a lazy list
Provides a way of interaction, allowing two objects that don't know about each other to effectively be used together to perform a combined task
We should try to name our interfaces for the services that they offer to the classes that use the interface
Used when you don't have access to a class but want to add functionality to that class, e.x. the Light
class is part of a library but you want to use it alongside a Button
class you coded so you use an adapter that implements the interface used by the Button
and also delegates to the Light
Object
The process to setup an adapter usually needs to go several steps since you need to pass or set the object that will be used to delegates tasks
The object form of the adapter is the one that uses a new separate object that binds other objects together
If you find the information in this page useful and want to show your support, you can make a donation
Use PayPal
This will help me create more stuff and fix the existent content...