mirror of
https://gitlab.com/djdietrick/docs
synced 2026-05-03 03:50:54 -04:00
Compare commits
2 Commits
9bff0f7371
...
727a0e734b
| Author | SHA1 | Date | |
|---|---|---|---|
| 727a0e734b | |||
| e4b7b75eab |
@@ -1,34 +1,33 @@
|
|||||||
export default {
|
export default {
|
||||||
title: 'Docs.Dietrick.Dev',
|
title: "Docs.Dietrick.Dev",
|
||||||
descript: 'A collection of notes and snippets',
|
descript: "A collection of notes and snippets",
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
socialLinks: [
|
socialLinks: [
|
||||||
{ icon: 'github', link: 'https://gitlab.com/djdietrick/docs'}
|
{ icon: "github", link: "https://gitlab.com/djdietrick/docs" },
|
||||||
],
|
],
|
||||||
sidebar: {
|
sidebar: {
|
||||||
'/python/': require('../python/sidebar.json'),
|
"/python/": require("../python/sidebar.json"),
|
||||||
'/rust/': require('../rust/sidebar.json'),
|
"/rust/": require("../rust/sidebar.json"),
|
||||||
'/nuxt/': require('../nuxt/sidebar.json'),
|
"/nuxt/": require("../nuxt/sidebar.json"),
|
||||||
'/ts/': require('../ts/sidebar.json'),
|
"/ts/": require("../ts/sidebar.json"),
|
||||||
'/terraform/': require('../terraform/sidebar.json'),
|
"/terraform/": require("../terraform/sidebar.json"),
|
||||||
'/interview/': require('../interview/sidebar.json'),
|
"/interview/": require("../interview/sidebar.json"),
|
||||||
'/server/' : require('../server/sidebar.json'),
|
"/server/": require("../server/sidebar.json"),
|
||||||
'/aws/' : require('../aws/sidebar.json'),
|
"/aws/": require("../aws/sidebar.json"),
|
||||||
'/': [
|
"/": [
|
||||||
{
|
{
|
||||||
text: 'Home',
|
text: "Home",
|
||||||
items: [
|
items: [
|
||||||
{text: 'Introduction', link: '/'},
|
{ text: "Introduction", link: "/" },
|
||||||
{text: 'Languages', link: '/languages'},
|
{ text: "Languages", link: "/languages" },
|
||||||
{text: 'Frameworks', link: '/frameworks'},
|
{ text: "Frameworks", link: "/frameworks" },
|
||||||
{text: 'Devops', link: '/devops'},
|
{ text: "Devops", link: "/devops" },
|
||||||
{text: 'Interview Prep', link: '/interview/'},
|
{ text: "Interview Prep", link: "/interview/" },
|
||||||
{text: 'Server', link: '/server/'},
|
{ text: "Server", link: "/server/" },
|
||||||
{text: 'AWS', link: '/aws/cloud-prac/'},
|
{ text: "AWS", link: "/aws/" },
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
}
|
},
|
||||||
}
|
],
|
||||||
}
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
1
docs/aws/index.md
Normal file
1
docs/aws/index.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# AWS
|
||||||
91
docs/interview/patterns/creation/builder.md
Normal file
91
docs/interview/patterns/creation/builder.md
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
# Builder
|
||||||
|
|
||||||
|
This pattern is useful when creating more complicated objects that would take a lot of initializer arguments. Instead, you call a number of methods for creating the object step by step. The idea is that the builder class should expose methods to add properties to the object its creating, with a final method `build` to return the object. These methods can be made **fluent** by returning `self` from the function, allowing you to chain these calls together. Builders can also be broken down into **Facets**, which are inherited builders exposed through the base builder class as properties to handle related elements of the object you are creating. This technically violates the open closed principle because you need to add properties to the base builder, however the alternative is creating a chain of inheritance where you instantiate the most specific builder that has access to all the inherited methods which is somewhat clunky.
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Person:
|
||||||
|
def __init__(self):
|
||||||
|
print('Creating an instance of Person')
|
||||||
|
# address
|
||||||
|
self.street_address = None
|
||||||
|
self.postcode = None
|
||||||
|
self.city = None
|
||||||
|
# employment info
|
||||||
|
self.company_name = None
|
||||||
|
self.position = None
|
||||||
|
self.annual_income = None
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f'Address: {self.street_address}, {self.postcode}, {self.city}\n' +\
|
||||||
|
f'Employed at {self.company_name} as a {self.postcode} earning {self.annual_income}'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def new():
|
||||||
|
return PersonBuilder()
|
||||||
|
|
||||||
|
class PersonBuilder: # facade
|
||||||
|
# Good practice to take person as an argument
|
||||||
|
def __init__(self, person=Person()):
|
||||||
|
self.person = person
|
||||||
|
|
||||||
|
@property
|
||||||
|
def lives(self):
|
||||||
|
return PersonAddressBuilder(self.person)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def works(self):
|
||||||
|
return PersonJobBuilder(self.person)
|
||||||
|
|
||||||
|
def build(self):
|
||||||
|
return self.person
|
||||||
|
|
||||||
|
class PersonJobBuilder(PersonBuilder):
|
||||||
|
def __init__(self, person):
|
||||||
|
super().__init__(person)
|
||||||
|
|
||||||
|
def at(self, company_name):
|
||||||
|
self.person.company_name = company_name
|
||||||
|
return self
|
||||||
|
|
||||||
|
def as_a(self, position):
|
||||||
|
self.person.position = position
|
||||||
|
return self
|
||||||
|
|
||||||
|
def earning(self, annual_income):
|
||||||
|
self.person.annual_income = annual_income
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
class PersonAddressBuilder(PersonBuilder):
|
||||||
|
def __init__(self, person):
|
||||||
|
super().__init__(person)
|
||||||
|
|
||||||
|
def at(self, street_address):
|
||||||
|
self.person.street_address = street_address
|
||||||
|
return self
|
||||||
|
|
||||||
|
def with_postcode(self, postcode):
|
||||||
|
self.person.postcode = postcode
|
||||||
|
return self
|
||||||
|
|
||||||
|
def in_city(self, city):
|
||||||
|
self.person.city = city
|
||||||
|
return self
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
pb = PersonBuilder()
|
||||||
|
p = pb\
|
||||||
|
.lives\
|
||||||
|
.at('123 London Road')\
|
||||||
|
.in_city('London')\
|
||||||
|
.with_postcode('SW12BC')\
|
||||||
|
.works\
|
||||||
|
.at('Fabrikam')\
|
||||||
|
.as_a('Engineer')\
|
||||||
|
.earning(123000)\
|
||||||
|
.build()
|
||||||
|
print(p)
|
||||||
|
person2 = PersonBuilder().build()
|
||||||
|
print(person2)
|
||||||
|
```
|
||||||
|
|
||||||
59
docs/interview/patterns/creation/factory.md
Normal file
59
docs/interview/patterns/creation/factory.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# Factory
|
||||||
|
|
||||||
|
**Factory Methods** are typically static methods in a class that return an instance of that class. This is helpful when it's not obvious how to construct an item, but still want it to be created wholesale and not piecewise like builders. A **Factory** is a separate class with factory methods dedicated to creating another type.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from enum import Enum
|
||||||
|
from math import *
|
||||||
|
|
||||||
|
class CoordinateSystem(Enum):
|
||||||
|
CARTESIAN = 1
|
||||||
|
POLAR = 2
|
||||||
|
|
||||||
|
class Point:
|
||||||
|
def __init__(self, x, y):
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'x: {self.x}, y: {self.y}'
|
||||||
|
|
||||||
|
# Bad way, requires more steps
|
||||||
|
# def __init__(self, a, b, system=CoordinateSystem.CARTESIAN):
|
||||||
|
# if system == CoordinateSystem.CARTESIAN:
|
||||||
|
# self.x = a
|
||||||
|
# self.y = b
|
||||||
|
# elif system == CoordinateSystem.POLAR:
|
||||||
|
# self.x = a * sin(b)
|
||||||
|
# self.y = a * cos(b)
|
||||||
|
# # steps to add a new system
|
||||||
|
# # 1. augment CoordinateSystem
|
||||||
|
# # 2. change init method
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def new_cartesian_point(x, y):
|
||||||
|
return Point(x, y)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def new_polar_point(rho, theta):
|
||||||
|
return Point(rho * sin(theta), rho * cos(theta))
|
||||||
|
|
||||||
|
# Can have factory within or ourside of class
|
||||||
|
class Factory:
|
||||||
|
@staticmethod
|
||||||
|
def new_cartesian_point(x, y):
|
||||||
|
return Point(x, y)
|
||||||
|
|
||||||
|
factory = Factory()
|
||||||
|
|
||||||
|
# take out factory methods to a separate class
|
||||||
|
class PointFactory:
|
||||||
|
@staticmethod
|
||||||
|
def new_cartesian_point(x, y):
|
||||||
|
return Point(x, y)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def new_polar_point(rho, theta):
|
||||||
|
return Point(rho * sin(theta), rho * cos(theta))
|
||||||
|
|
||||||
|
```
|
||||||
49
docs/interview/patterns/creation/prototype.md
Normal file
49
docs/interview/patterns/creation/prototype.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# Prototype
|
||||||
|
|
||||||
|
A prototype is an object or partial object that you clone in order to create a new object. This is especially tricky in python where everything is a reference. This will require the use of `copy.deepcopy(obj)` to clone the object so that any changes to the copied object won't be referenced by the original. A practical use for prototypes is to combine them with factories to create **Prototype Factories**. These store static example objects that can then be returned via static methods after providing only the significant arguments.
|
||||||
|
|
||||||
|
```python
|
||||||
|
import copy
|
||||||
|
|
||||||
|
class Address:
|
||||||
|
def __init__(self, street_address, suite, city):
|
||||||
|
self.suite = suite
|
||||||
|
self.city = city
|
||||||
|
self.street_address = street_address
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.street_address}, Suite #{self.suite}, {self.city}'
|
||||||
|
|
||||||
|
class Employee:
|
||||||
|
def __init__(self, name, address):
|
||||||
|
self.address = address
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.name} works at {self.address}'
|
||||||
|
|
||||||
|
class EmployeeFactory:
|
||||||
|
main_office_employee = Employee("", Address("123 East Dr", 0, "London"))
|
||||||
|
aux_office_employee = Employee("", Address("123B East Dr", 0, "London"))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __new_employee(proto, name, suite):
|
||||||
|
result = copy.deepcopy(proto)
|
||||||
|
result.name = name
|
||||||
|
result.address.suite = suite
|
||||||
|
return result
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def new_main_office_employee(name, suite):
|
||||||
|
return EmployeeFactory.__new_employee(
|
||||||
|
EmployeeFactory.main_office_employee,
|
||||||
|
name, suite
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def new_aux_office_employee(name, suite):
|
||||||
|
return EmployeeFactory.__new_employee(
|
||||||
|
EmployeeFactory.aux_office_employee,
|
||||||
|
name, suite
|
||||||
|
)
|
||||||
|
```
|
||||||
132
docs/interview/patterns/creation/singleton.md
Normal file
132
docs/interview/patterns/creation/singleton.md
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
# Singleton
|
||||||
|
|
||||||
|
A singleton is a object, usually stored statically, that is instantiated on the first request and then returns that instance on all further requests. In python, the cleanest way to do this is with a decorator.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def singleton(class_):
|
||||||
|
instances = {}
|
||||||
|
|
||||||
|
def getinstance(*args, **kwargs):
|
||||||
|
if class_ not in instances:
|
||||||
|
instances[class_] = class_(*args, **kwargs)
|
||||||
|
return instances[class_]
|
||||||
|
|
||||||
|
return getinstance
|
||||||
|
|
||||||
|
@singleton
|
||||||
|
class Database:
|
||||||
|
def __init__(self):
|
||||||
|
print('Loading database')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
d1 = Database()
|
||||||
|
d2 = Database()
|
||||||
|
print(d1 == d2)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Monostate
|
||||||
|
|
||||||
|
A monostate is a type of singleton where all instances of a class share a set of shared state, but are still different objects with other member attributes.
|
||||||
|
|
||||||
|
```python
|
||||||
|
class CEO:
|
||||||
|
__shared_state = {
|
||||||
|
'name': 'Steve',
|
||||||
|
'age': 55
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.__dict__ = self.__shared_state
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.name} is {self.age} years old'
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
ceo1 = CEO()
|
||||||
|
print(ceo1)
|
||||||
|
|
||||||
|
ceo1.age = 66
|
||||||
|
|
||||||
|
ceo2 = CEO()
|
||||||
|
ceo2.age = 77
|
||||||
|
print(ceo1)
|
||||||
|
print(ceo2)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
It can be difficult to test singletons because they are not usually injected into a class by design. Every client that initializes the class will receive the same object so there's no use to pass it around. The correct way to test these then are to instead pass the singleton class into the initializer with the default being instantiating your production class. This allows you to inject a mock singleton class for testing.
|
||||||
|
|
||||||
|
```python
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
class Singleton(type):
|
||||||
|
_instances = {}
|
||||||
|
|
||||||
|
def __call__(cls, *args, **kwargs):
|
||||||
|
if cls not in cls._instances:
|
||||||
|
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
|
||||||
|
return cls._instances[cls]
|
||||||
|
|
||||||
|
class Database(metaclass=Singleton):
|
||||||
|
def __init__(self):
|
||||||
|
self.population = {}
|
||||||
|
f = open('capitals.txt', 'r')
|
||||||
|
lines = f.readlines()
|
||||||
|
for i in range(0, len(lines), 2):
|
||||||
|
self.population[lines[i].strip()] = int(lines[i + 1].strip())
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
# Accesses production class directory
|
||||||
|
class SingletonRecordFinder:
|
||||||
|
def total_population(self, cities):
|
||||||
|
result = 0
|
||||||
|
for c in cities:
|
||||||
|
result += Database().population[c]
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Takes an argument with default of prod class
|
||||||
|
class ConfigurableRecordFinder:
|
||||||
|
def __init__(self, db=Database()):
|
||||||
|
self.db = db
|
||||||
|
|
||||||
|
def total_population(self, cities):
|
||||||
|
result = 0
|
||||||
|
for c in cities:
|
||||||
|
result += self.db.population[c]
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class DummyDatabase:
|
||||||
|
population = {
|
||||||
|
'alpha': 1,
|
||||||
|
'beta': 2,
|
||||||
|
'gamma': 3
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_population(self, name):
|
||||||
|
return self.population[name]
|
||||||
|
|
||||||
|
class SingletonTests(unittest.TestCase):
|
||||||
|
def test_is_singleton(self):
|
||||||
|
db = Database()
|
||||||
|
db2 = Database()
|
||||||
|
self.assertEqual(db, db2)
|
||||||
|
|
||||||
|
def test_singleton_total_population(self):
|
||||||
|
""" This tests on a live database :( """
|
||||||
|
rf = SingletonRecordFinder()
|
||||||
|
names = ['Seoul', 'Mexico City']
|
||||||
|
tp = rf.total_population(names)
|
||||||
|
self.assertEqual(tp, 17500000 + 17400000) # what if these change?
|
||||||
|
|
||||||
|
ddb = DummyDatabase()
|
||||||
|
|
||||||
|
def test_dependent_total_population(self):
|
||||||
|
crf = ConfigurableRecordFinder(self.ddb)
|
||||||
|
self.assertEqual(
|
||||||
|
crf.total_population(['alpha', 'beta']),
|
||||||
|
3
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
177
docs/interview/patterns/index.md
Normal file
177
docs/interview/patterns/index.md
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
# Principles of Design Patterns
|
||||||
|
|
||||||
|
## Single Responsibility Principle
|
||||||
|
|
||||||
|
This principle states that every class should have one responsibility and therefore one reason to change. We want to avoid 'God' classes which perform all the functionality within a single class. This simplifies modification of code because if you do need to make a change, you only need to change it in the one place that has that responsibility.
|
||||||
|
|
||||||
|
## Open Closed Principle
|
||||||
|
|
||||||
|
This principle follows the rule 'open for extension, closed for modification' after you have created a class or function. Take the below example:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Product:
|
||||||
|
def __init__(self, name, color, size):
|
||||||
|
self.name = name
|
||||||
|
self.color = color
|
||||||
|
self.size = size
|
||||||
|
|
||||||
|
class ProductFilter:
|
||||||
|
def filter_by_color(self, products, color):
|
||||||
|
for p in products:
|
||||||
|
if p.color == color: yield p
|
||||||
|
```
|
||||||
|
|
||||||
|
With this design, if you want to create new filters based on product details, you will need to modify your ProductFilter class and this could get out of hand with too many filters. Instead, you would want to design your system so that you can extend classes to add functionality.
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Specification:
|
||||||
|
def is_satisfied(self, item):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Filter:
|
||||||
|
def filter(self, items, spec):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ColorSpecification(Specification):
|
||||||
|
def __init__(self, color):
|
||||||
|
self.color = color
|
||||||
|
|
||||||
|
def is_satisfied(self, item):
|
||||||
|
return item.color == self.color
|
||||||
|
|
||||||
|
# ... Other specifications
|
||||||
|
|
||||||
|
class BetterFilter(Filter):
|
||||||
|
def filter(self, items, spec):
|
||||||
|
for item in items:
|
||||||
|
if spec.is_satisfied(item): yield item
|
||||||
|
|
||||||
|
class AndSpecification(Specification):
|
||||||
|
def __init__(self, *args):
|
||||||
|
self.args = args
|
||||||
|
|
||||||
|
def is_satisfied(self, item):
|
||||||
|
return all(map(
|
||||||
|
lambda spec: spec.is_satisfied(item), self.args
|
||||||
|
))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Liskov Substitution Principle
|
||||||
|
|
||||||
|
This principle states that all implementations using a base class should work correctly for all derived classes. In the below example, the Square class breaks the use_it function because the behavior of Square is different than that off the Rectangle.
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Rectangle:
|
||||||
|
def __init__(self, width, height):
|
||||||
|
self._width = width
|
||||||
|
self._height = height
|
||||||
|
|
||||||
|
@property
|
||||||
|
def width(self): return self._width
|
||||||
|
@width.setter
|
||||||
|
def width(self, value): self._width = value
|
||||||
|
@property
|
||||||
|
def height(self): return self._height
|
||||||
|
@height.setter
|
||||||
|
def height(self, value): self._height = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def area(self): return self._height * self._width
|
||||||
|
|
||||||
|
class Square(Rectangle):
|
||||||
|
def __init__(self, size):
|
||||||
|
Rectangle.__init__(self, size, size)
|
||||||
|
|
||||||
|
@Rectangle.width.setter
|
||||||
|
def width(self, value): self._width = self._height = value
|
||||||
|
@Rectangle.height.setter
|
||||||
|
def height(self, value): self._height = self.width = value
|
||||||
|
|
||||||
|
def use_it(rc):
|
||||||
|
w = rc.width
|
||||||
|
rc.height = 10
|
||||||
|
expected = int(w*10)
|
||||||
|
print(f'Expected {expected}, got {rc.area}')
|
||||||
|
|
||||||
|
use_it(Rectangle(2,3))
|
||||||
|
use_it(Square(2))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Interface Segregation Principle
|
||||||
|
|
||||||
|
This principle states that an interface should not have too many methods and should be broken into multiple interfaces with the minimal amount of functionality. In the below example, subclasses of Machine may not implement all of those functions, like an OldFashionedPrinter can't scan and fax.
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Machine:
|
||||||
|
def print(self, document):
|
||||||
|
raise NotImplementedError
|
||||||
|
def fax(self, document):
|
||||||
|
raise NotImplementedError
|
||||||
|
def scan(self, document):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
class Printer:
|
||||||
|
def print(self, document):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class FaxMachine:
|
||||||
|
def fax(self, document):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Scanner:
|
||||||
|
def scan(self, document):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Photocopier(Printer, Scanner):
|
||||||
|
def print(self, document):
|
||||||
|
pass
|
||||||
|
def scan(self, document):
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dependency Inversion Principle
|
||||||
|
|
||||||
|
This principle states that high level classes should not depend on low level implementations, but instead on interfaces/abstractions. This allows you to swap interfaces for different implementations. In the below example, the Research class depends on Relationships always storing its data in a list. Instead, it should interact with an interface to handle fetching the items it needs.
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Relationship(Enum):
|
||||||
|
PARENT=0
|
||||||
|
CHILD=1
|
||||||
|
SIBLING=2
|
||||||
|
|
||||||
|
class Person:
|
||||||
|
def __init__(self, name):
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
# BAD
|
||||||
|
class Relationships:
|
||||||
|
def __init__(self):
|
||||||
|
self.relations = []
|
||||||
|
def add_parent_and_child(self, parent, child):
|
||||||
|
self.relations.append((parent, Relationship.PARENT, child))
|
||||||
|
self.relations.append((child, Relationship.CHILD, parent))
|
||||||
|
|
||||||
|
class Research:
|
||||||
|
def __init__(self, relationships):
|
||||||
|
relations = relationships.relations
|
||||||
|
for r in relations:
|
||||||
|
if r[0].name == 'John' and r[1] == Relationship.PARENT:
|
||||||
|
print(f'John has a child called {r[2].name}')
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
class RelationshipBrowser:
|
||||||
|
def find_all_children_of(self, name): pass
|
||||||
|
|
||||||
|
class Relationships(RelationshipBrowser):
|
||||||
|
# ... same as before but also....
|
||||||
|
def find_all_children_of(self, name):
|
||||||
|
for r in self.relations:
|
||||||
|
if r[0].name == name and r[1] == Relationship.PARENT:
|
||||||
|
yield r[2]
|
||||||
|
|
||||||
|
class Research:
|
||||||
|
def __init__(self, browser):
|
||||||
|
for p in browser.find_all_children_of('John'):
|
||||||
|
print(f'John has a child called {p}')
|
||||||
|
|
||||||
|
```
|
||||||
34
docs/interview/patterns/structure/adapter.md
Normal file
34
docs/interview/patterns/structure/adapter.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Adapter
|
||||||
|
|
||||||
|
An adapter is a construct whcih adapts an existing interface to conform to another required interface. This could be a factory which takes one object and transforms it into another, or it can be a wrapper around the object that performs the operations on the fly.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
class Square:
|
||||||
|
def __init__(self, side=0):
|
||||||
|
self.side = side
|
||||||
|
|
||||||
|
def calculate_area(rc):
|
||||||
|
return rc.width * rc.height
|
||||||
|
|
||||||
|
class SquareToRectangleAdapter:
|
||||||
|
def __init__(self, square):
|
||||||
|
self.square = square
|
||||||
|
|
||||||
|
@property
|
||||||
|
def width(self):
|
||||||
|
return self.square.side
|
||||||
|
|
||||||
|
@property
|
||||||
|
def height(self):
|
||||||
|
return self.square.side
|
||||||
|
|
||||||
|
class Evaluate(TestCase):
|
||||||
|
def test_exercise(self):
|
||||||
|
sq = Square(11)
|
||||||
|
adapter = SquareToRectangleAdapter(sq)
|
||||||
|
self.assertEqual(121, calculate_area(adapter))
|
||||||
|
sq.side = 10
|
||||||
|
self.assertEqual(100, calculate_area(adapter))
|
||||||
|
```
|
||||||
43
docs/interview/patterns/structure/bridge.md
Normal file
43
docs/interview/patterns/structure/bridge.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# Bridge
|
||||||
|
|
||||||
|
A bridge is a mechanism that decouples an interface from an implementation. This is a fancy way of saying passing an object into another object to have it perform some other logic that is not the responsibility of the containing object. In the below example, a shape can be drawn in either vector or raster. The Circle class should not contain the logic of how to draw that shape in a particular format, so it uses the renderer as a bridge to perform that duty. This means that as you create more combinations of possibilities you limit the number of places you need to change.
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Renderer():
|
||||||
|
def render_circle(self, radius):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class VectorRenderer(Renderer):
|
||||||
|
def render_circle(self, radius):
|
||||||
|
print(f'Drawing a circle of radius {radius}')
|
||||||
|
|
||||||
|
class RasterRenderer(Renderer):
|
||||||
|
def render_circle(self, radius):
|
||||||
|
print(f'Drawing pixels for circle of radius {radius}')
|
||||||
|
|
||||||
|
class Shape:
|
||||||
|
def __init__(self, renderer):
|
||||||
|
self.renderer = renderer
|
||||||
|
|
||||||
|
def draw(self): pass
|
||||||
|
def resize(self, factor): pass
|
||||||
|
|
||||||
|
class Circle(Shape):
|
||||||
|
def __init__(self, renderer, radius):
|
||||||
|
super().__init__(renderer)
|
||||||
|
self.radius = radius
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
self.renderer.render_circle(self.radius)
|
||||||
|
|
||||||
|
def resize(self, factor):
|
||||||
|
self.radius *= factor
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
raster = RasterRenderer()
|
||||||
|
vector = VectorRenderer()
|
||||||
|
circle = Circle(vector, 5)
|
||||||
|
circle.draw()
|
||||||
|
circle.resize(2)
|
||||||
|
circle.draw()
|
||||||
|
```
|
||||||
63
docs/interview/patterns/structure/composite.md
Normal file
63
docs/interview/patterns/structure/composite.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# Composite
|
||||||
|
|
||||||
|
A composite is a mechanism which handles a single object (scalar) or a composite of objects the same. This can be made easier in python by overriding the `__iter__` method.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from abc import ABC
|
||||||
|
from collections.abc import Iterable
|
||||||
|
|
||||||
|
class Connectable(Iterable, ABC):
|
||||||
|
def connect_to(self, other):
|
||||||
|
if self == other:
|
||||||
|
return
|
||||||
|
|
||||||
|
for s in self:
|
||||||
|
for o in other:
|
||||||
|
s.outputs.append(o)
|
||||||
|
o.inputs.append(s)
|
||||||
|
|
||||||
|
|
||||||
|
class Neuron(Connectable):
|
||||||
|
def __init__(self, name):
|
||||||
|
self.name = name
|
||||||
|
self.inputs = []
|
||||||
|
self.outputs = []
|
||||||
|
|
||||||
|
# def connect_to(self, other):
|
||||||
|
# self.outputs.append(other)
|
||||||
|
# other.inputs.append(self)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
yield self
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.name}, {len(self.inputs)} inputs, {len(self.outputs)} outputs'
|
||||||
|
|
||||||
|
|
||||||
|
class NeuronLayer(list, Connectable):
|
||||||
|
def __init__(self, name, count):
|
||||||
|
super().__init__()
|
||||||
|
self.name = name
|
||||||
|
for x in range(0, count):
|
||||||
|
self.append(Neuron(f'{name}-{x}'))
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.name} with {len(self)} neurons'
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
neuron1 = Neuron('n1')
|
||||||
|
neuron2 = Neuron('n2')
|
||||||
|
layer1 = NeuronLayer('L1', 3)
|
||||||
|
layer2 = NeuronLayer('L2', 4)
|
||||||
|
|
||||||
|
neuron1.connect_to(neuron2)
|
||||||
|
neuron1.connect_to(layer1)
|
||||||
|
layer1.connect_to(neuron2)
|
||||||
|
layer1.connect_to(layer2)
|
||||||
|
|
||||||
|
print(neuron1)
|
||||||
|
print(neuron2)
|
||||||
|
print(layer1)
|
||||||
|
print(layer2)
|
||||||
|
|
||||||
|
```
|
||||||
130
docs/interview/patterns/structure/decorator.md
Normal file
130
docs/interview/patterns/structure/decorator.md
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
# Decorator
|
||||||
|
|
||||||
|
A decorator is an object which holds a reference to another object that you want to alter/extend the behavior of another object without inheriting from it.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from abc import ABC
|
||||||
|
|
||||||
|
class Shape(ABC):
|
||||||
|
def __str__(self):
|
||||||
|
return ''
|
||||||
|
|
||||||
|
class Circle(Shape):
|
||||||
|
def __init__(self, radius=0.0):
|
||||||
|
self.radius = radius
|
||||||
|
|
||||||
|
def resize(self, factor):
|
||||||
|
self.radius *= factor
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'A circle of radius {self.radius}'
|
||||||
|
|
||||||
|
class Square(Shape):
|
||||||
|
def __init__(self, side):
|
||||||
|
self.side = side
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'A square with side {self.side}'
|
||||||
|
|
||||||
|
class ColoredShape(Shape):
|
||||||
|
def __init__(self, shape, color):
|
||||||
|
if isinstance(shape, ColoredShape):
|
||||||
|
raise Exception('Cannot apply ColoredDecorator twice')
|
||||||
|
self.shape = shape
|
||||||
|
self.color = color
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.shape} has the color {self.color}'
|
||||||
|
|
||||||
|
class TransparentShape(Shape):
|
||||||
|
def __init__(self, shape, transparency):
|
||||||
|
self.shape = shape
|
||||||
|
self.transparency = transparency
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.shape} has {self.transparency * 100.0}% transparency'
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
circle = Circle(2)
|
||||||
|
print(circle)
|
||||||
|
|
||||||
|
red_circle = ColoredShape(circle, "red")
|
||||||
|
print(red_circle)
|
||||||
|
|
||||||
|
# ColoredShape doesn't have resize()
|
||||||
|
# red_circle.resize(3)
|
||||||
|
|
||||||
|
red_half_transparent_square = TransparentShape(red_circle, 0.5)
|
||||||
|
print(red_half_transparent_square)
|
||||||
|
|
||||||
|
# nothing prevents double application
|
||||||
|
mixed = ColoredShape(ColoredShape(Circle(3), 'red'), 'blue')
|
||||||
|
print(mixed)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dynamic Decorator
|
||||||
|
|
||||||
|
You can also have a **Dynamic Decorator** by passing calls to access the underlying item.
|
||||||
|
|
||||||
|
```python
|
||||||
|
class FileWithLogging:
|
||||||
|
def __init__(self, file):
|
||||||
|
self.file = file
|
||||||
|
|
||||||
|
def writelines(self, strings):
|
||||||
|
self.file.writelines(strings)
|
||||||
|
print(f'wrote {len(strings)} lines')
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return self.file.__iter__()
|
||||||
|
|
||||||
|
def __next__(self):
|
||||||
|
return self.file.__next__()
|
||||||
|
|
||||||
|
def __getattr__(self, item):
|
||||||
|
return getattr(self.__dict__['file'], item)
|
||||||
|
|
||||||
|
def __setattr__(self, key, value):
|
||||||
|
if key == 'file':
|
||||||
|
self.__dict__[key] = value
|
||||||
|
else:
|
||||||
|
setattr(self.__dict__['file'], key)
|
||||||
|
|
||||||
|
def __delattr__(self, item):
|
||||||
|
delattr(self.__dict__['file'], item)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
file = FileWithLogging(open('hello.txt', 'w'))
|
||||||
|
file.writelines(['hello', 'world'])
|
||||||
|
file.write('testing')
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Functional Decorators
|
||||||
|
|
||||||
|
Functional decorators are built in to python to alter the behavior of your functions.
|
||||||
|
|
||||||
|
```python
|
||||||
|
import time
|
||||||
|
|
||||||
|
def time_it(func):
|
||||||
|
def wrapper():
|
||||||
|
start = time.time()
|
||||||
|
result = func()
|
||||||
|
end = time.time()
|
||||||
|
print(f'{func.__name} took {int(end-start) * 1000}ms')
|
||||||
|
return result
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
@time_it
|
||||||
|
def some_op():
|
||||||
|
print('Starting op')
|
||||||
|
time.sleep(1)
|
||||||
|
print('Finished op')
|
||||||
|
return 123
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
some_op()
|
||||||
|
|
||||||
|
```
|
||||||
7
docs/interview/patterns/structure/facade.md
Normal file
7
docs/interview/patterns/structure/facade.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Facade
|
||||||
|
|
||||||
|
A facade provides a simple, easy to use interface for a larger and more suphisticated body of code underneath.
|
||||||
|
|
||||||
|
```python
|
||||||
|
|
||||||
|
```
|
||||||
@@ -29,5 +29,24 @@
|
|||||||
{"text": "Improvements", "link": "/interview/sd/improvements"},
|
{"text": "Improvements", "link": "/interview/sd/improvements"},
|
||||||
{"text": "Implementation", "link": "/interview/sd/impl"}
|
{"text": "Implementation", "link": "/interview/sd/impl"}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Design Patterns",
|
||||||
|
"items": [
|
||||||
|
{"text": "Principles", "link": "/interview/patterns/index"},
|
||||||
|
{"text": "Creational", "items": [
|
||||||
|
{"text": "Builder", "link": "/interview/patterns/creation/builder"},
|
||||||
|
{"text": "Factory", "link": "/interview/patterns/creation/factory"},
|
||||||
|
{"text": "Prototype", "link": "/interview/patterns/creation/prototype"},
|
||||||
|
{"text": "Singleton", "link": "/interview/patterns/creation/singleton"}
|
||||||
|
]},
|
||||||
|
{"text": "Structural", "items": [
|
||||||
|
{"text": "Adapter", "link": "/interview/patterns/structure/adapter"},
|
||||||
|
{"text": "Bridge", "link": "/interview/patterns/structure/bridge"},
|
||||||
|
{"text": "Composite", "link": "/interview/patterns/structure/composite"},
|
||||||
|
{"text": "Decorator", "link": "/interview/patterns/structure/decorator"},
|
||||||
|
{"text": "Facade", "link": "/interview/patterns/structure/facade"}
|
||||||
|
]}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
Reference in New Issue
Block a user