patterns
#define patterns: \
I-------------------------------------------------------------------------------------\
I-------------------------------------------------------------------------------------\
I-------------------------------------------------------------------------------------\
I \
I /$$$$$$$ /$$ /$$ \
I | $$__ $$ | $$ | $$ \
I | $$ \ $$ /$$$$$$ /$$$$$$ /$$$$$$ /$$$$$$ /$$$$$$ /$$$$$$$ /$$$$$$$ \
I | $$$$$$$/|____ $$|_ $$_/|_ $$_/ /$$__ $$ /$$__ $$| $$__ $$ /$$_____/ \
I | $$____/ /$$$$$$$ | $$ | $$ | $$$$$$$$| $$ \__/| $$ \ $$| $$$$$$ \
I | $$ /$$__ $$ | $$ /$$| $$ /$$| $$_____/| $$ | $$ | $$ \____ $$ \
I | $$ | $$$$$$$ | $$$$/| $$$$/| $$$$$$$| $$ | $$ | $$ /$$$$$$$/ \
I |__/ \_______/ \___/ \___/ \_______/|__/ |__/ |__/|_______/ \
I-------------------------------------------------------------------------------------\
I-------------------------------------------------------------------------------------\
I-------------------------------------------------------------------------------------I
# closely related subject
"Data structures"
# good first introduction to OOP patterns
Head First Design Patterns by Eric Freeman and Elisabeth Robson
GIGO: GIGO:
• "Garbage In Garbage Out"/"RIRO"/"Rubish In Rubish Out"
• derived from LIFO/FIFO
• the concept that incorrect input produces incorrect output
• in practice used to describe behaviour that accepts faulty input
and produces some output without raising an error
Strings:
Escaping:
{
"I don't know what \"irony\" is."
}
• it is common that some characters hold special meaning inside strings
• string literals are usually delimited from code using some characters
• escaping is the mechanism to remove special significance from
a character inside a string
• in any sane system, escaping is either done by prepending a backslash
or doubling the character
• the escape character being a prefix has a practical significance
for parser implementations
Format_strings:
• the forefather of string interpolation
• a string contains placeholders,
which will be filled using variables passed to a format function
• C's "printf" is the most well-known example
Interpolation:
# basic interpolation in Python
title = "Example"
print(f"--- {title} ---") # Out: --- Example ---
• when a string literal is allowed to contain expressions,
which will be expended at runtime
— it is possible to interpolate:
• variables
• function calls
• child process outputs
• most interpreted language support it to some degree
• true interpolation with symbol support requires atleast some
weak mechanism of reflection
• in languages without any reflections, its often emulated using maps
{ https://github.com/agvxov/ministach }
Templating:
• interpolation with logic
• a library providing templating is commonly referred to as a "templating engine"
• a good templating engine should not be aware of the global context
(only allowing parameterization) and should only support simple logic;
a good example to what happens otherwise is PHP
Lorem_ipsum:
• "Ipsum"
• many applications require dummy text to test on
• "Lorem ipsum..." is a well known nonsensical, Latin sounding text,
originally developed to test in the printing industry
• most environments will have some method of fetching ipsum text
• it is generally a good idea to test on ipsum text
For_loop: For_loop:
• introduced in B, but no one cares about B, so its attributed to C
• older laguages only had logical subsets for integers, not arbitrary statements
• common even outside of the C language family
• a loop with initialization code, a condition and post loop code
○ flow chart
for(A; B; C){ D } [...]:
A
B
C
D
+-------------+
| A |
+-------------+
|
|
|
|
V
A
/ \
/ \
/ \ False
+------------> B ----------+
| \ / |
| \ / |
| \ / |
| V |
| | |
| | |
| | True |
| | |
| V |
| +-------------+ |
| | D | |
| +-------------+ |
| | |
| | |
| | |
| | |
| V |
| +-------------+ |
+---------- C | |
+-------------+ |
|
|
|
|
|
+-------------+ |
| [...] |<------+
+-------------+
Zahns_construct: Zahns_construct:
• orignates from a paper of Zahn
• mainstream languages do not implement it as of 2024
• abstraction over quiting blocks to avoid goto-s and extra state variables
• blocks can be exited with in situations, each situation is added as a case at the end of the block
{
loop
until found or missing;
do
for i in 1..100 do
for h in 1..100 do
if myTable[i, h] = target then
found;
missing;
endloop;
then
found: print "Item was found.";
missing: print "Item was not found.";
end;
}
Lambdas: Lambdas:
• anonymous function
• can be passed around, usually by reference
• the lambda expression "<...-1> -> <...-2>" is pronounced
"<...-1> becomes <...-2>" or
"<...-1> for which <...-2>"
Error_handling: Error_handling:
Return_value_based:
• if a function encountered an error, it gives back a special value
Null:
NULL nullptr None Nill
• a concrete value reserved for signalling "no value at all"
• naming originates from the common practice of returning
a pointer to the address 0x0 where a heap address is
expected, but the operation failed
• the null value can be easily tested and ideally passed
on to functions that except a real value { free(NULL); }
Object:
• a null object is a real object instance with its
inner state set to some default and all methods
performing a no-operations
• can be passed to any function or otherwise
operated on safely without having to check
for errors
• helps readability
• redundant functions calls will be issued left and right
• makes sense if error handing would be messy and
errors are expected to occur very rarely
Side_effects:
• on error, some value is set; usually a global or an extra parameter
• common in C with errno or <mylib>errno
Exceptions:
• an exception is an event unwinding the stack until a handler is found
• the trigerring of an exception is commonly called "throwing" or "raising"
• the handling of an exception is commonly called "catching"
• cannot be ignored; while a NULL value returned could go unnoticed until a cryptic crash,
an unhandled exeption will crash right at the spot
• slow
• best used when errors are unlikely, but important
{
try {
int i = f();
if (i == 0) {
throw Exception();
print("message");
}
print("this only executes if f() suceeded")
} catch (...) {
;
} finally {
;
}
;
}
Monad: Monad:
• "A monad is a monoid in the category of endofunctors." - easy
• "programmable semicolons"
• "compile-time variation of the decorator pattern"
— a monad is:
• a null value
• logic wrapping the null value
○ examples
• builders are monads
• decorators can be monads if they have error checking logic
• C's NULL and "if (myvalue != NULL) { return; }" form a monad
(truth be told, a very crude version)
• (modern) haskell is built on the idea of monads and has pure nomadic types
• the ideal monad is lazily evaluated with no redundant function calls
Regex: Regex:
"Grammar/Regular Expressions"
Compiling:
• regex strings are compiled into an intermediate form
(e.g. non-deterministic finate state machines)
which is a computationally expensive operation,
however it greatly reduces searching/matching time,
paying back after repeated usage
• compiling is not only a performance decision,
it allows the programmer to arbitrary abstract the problem,
allowing for much more comprehensible debugging and
promoting extendableness
Macthing:
• match objects are not bloat
• even the most minimalistic implementation will require signalling both
the position of the match and its width, even if groups and whatnot are to be ignored
| Intermediate | Transformation | Practical
| State | | Result
I`````````````````````I
┌────────┐ I Compile I
│"1[01]+"│ -------------> I~~~~~~~~~~~~~~~~~~~~~I
└────────┘ I regcomp() I
I std::regex::regex() I
I re.compile() I
I.....................I
|
+--------------------------------+
|
V
┌────────────────┐ I````````````````````I
│ Regex Type │ ---------> I Matching I
│────────────────│ I~~~~~~~~~~~~~~~~~~~~I
│ regex_t │ I regexec() I
│ std::regex │ I std::regex_match() I
│ re.Pattern │ I <regex>.match() I
└────────────────┘ I....................I
| N^^^^^^^^^^^^^^^^N
+-------------------> N Match Type N
N================N
N regmatch_t N
N std::smatch N
N re.Match N
N::::::::::::::::N
|
+-----------------+-- - - - - - --+
| | |
V V V
.^^^^^^^^^. .^^^^^^^^^. .^^^^^^^^^.
| Group 0 | | Group 1 | | Group N |
'.........' '.........' '.........'
Singleton: Singleton:
Image:
"The Earth in the emptiness of space."
• "egyke"^HU
• an object that can have only 1 instance
• usually accomplished by making the constructor private and
adding a special function wrapping construction,
checking for other instances
• unlike a set of globals, it could practice polymorphism
• unlike a set of globals, it could be lazily eval-ed,
freeing a potentially considerable amount of memory when not in use
• commonly overused / abused in radically OO languages
• if your singleton keeps returning the same instance, genious job,
you manged to recreate a procedural global in OOP
• if your reasoning for making a singleton is "ugh my application will only need one",
consider choosing a new career
• as a comperasion to how idiotic it is to create a "MainApplication" singleton or
a "MyMainComponentThatINeedFromBeginingToEndAndCouplingCloselyAnyways",
if you read the source code of AlbertLauncher you will see that they set a static
flag whenever main is called so they can conditionally crash if main() were to be
called recursively; the logic is the same, so might as well go ahead and do that
from now to forever on; in fact, lets apply that to every function that we assume
should not be called recursively
{
class A{
static obj_count = 0;
int i;
A() {
this->i = 42;
}
~A() {
--obj_count;
}
public:
A* init(){
if(obj_count == 0){
++obj_count;
return new A;
}else{
return nullptr;
}
}
}
}
Access: Access:
• when code is protected from being executed or data is protected from being read/written
from some code segments
• usually implemented at compiletime, but runtime checks qualify too
○ common access levels
public - accessible from anywhere {C++, C#}
private - accessible only from inside the current class {C++, C#}
protected - accessible only from inside the current class and its children {C++, C#}
internal - accessible from only the current compilation unit / assembly {static in C/C++, C#}
Opaque:
• an object whichs every field is private
• working with opaque types is only possible through member functions
Getter_setter_situation:
— getter:
• a getter is a method which takes only the object as a parameter and
returns some value which is representitive of the objects inner state
• may or may not directly correspond to an internal variable
— setter:
• a setter is a method whichs only purpose is to modifies the inner state of an object
based on the passed values
— language support:
• the real problem why everyone complains about them is that they are bloody annoying;
from quick prototyping you are thrown into a den where you have to implement
5 getters and setters or else!
C#-like:
• auto generate trivial setters/getters with quick keywords
Java-like:
• auto generate trivial getters for special record class fields
Kotlin-like:
• allow getter setter functions to pretend to be raw fields;
meaning you can change the implementation anytime;
most of the time, the api client really doesnt care whether its a function or a value,
however theoretically it could lead to footgun situations where values
end up all fucked up and hunting down the error is a nightmare
• best
{
class Rect {
public:
int w, h;
int area() {
return w * h;
}
};
"Dear (You),"
"We, at UltraSonicBearTrap Inc., have been using your open source project\
rect.hpp. Much of our critical infrastructure is dependent on it in fact.\
However, our new product UltraSonicBearTrap-4670™ runs on our in house\
architecture: Arch YIKES. On Arch YIKES calculating a product takes\
approximetly 20 mins. For this reason, we are left no choice, but to\
store the area and only update it when the sides are updated.\
Which is something that our code dependent uppon your library is unable to allow.\
So, with a heavy heart we must let rect.hpp go."
"P.S. FUCK YOU WE WILL HAVE TO EDIT OVER 1'000'000'000 LINES"
class Rect {
int _w, _h, _area;
public:
void set_w(int w) {
_w = w;
area = _w * _h;
}
void set_h(int h) {
_h = h;
area = _w * _h;
}
int area() {
return _area;
}
};
---
---
unique_ptr<map<int, vector<time_t>>> a;
int x;
---
}
AGGREGATION: AGGREGATION:
• when an object contains another object
Composition: Composition:
• when the containing object manages the lifetime of the contained object
• the two lifetimes being tied by any mechanism {stack popping, GC}
qualifies as "management"
• in the trenches "composition" could be used to refer to aggregation;
its considered a minor unclarity outside of documentation
Inheritance: Inheritance:
• a special case of composition when the parent can access
non-public members of the child
• unless polymorphism is explicitly required, composition is
preferred for lighter encapsulation
Dependency_injection: Dependency_injection:
• "DI"
• when the containing object receives the contained object from the outside {constructor}
• often preffered as it decouples the containing and contained classes
• inherently requires pointers (even if implicit)
• some retards refer to functions in general receiving objects as DI,
usually in radically OO languages, which, is quite dumb,
ie. its just "explicit member functions" for them,
except they will never recognize its a member function
because there is no syntax support
Strategy: Strategy:
Image:
"The tactitian promoting his pawn to a queen."
• behaviour is defined as a variable
• allows for runtime chaning of behaviour
• can prevent messy and seemingly endless branching
• can prevent messy inheritance
• "The Strategy Pattern defines a family of algorithms, encapsulates each one,\
and makes them interchangeable.\
Strategy lets the algorithm vary independently from clients that use it."
— usually acheved by:
• function variables { C/C++: function pointer, Python: class 'function'}
• composing a polymorphic behaviour class {C++, C#, Java}
{ # Function strategy diagramm in pseudo-python
def fn(...): '' fn = ─ ┬ ─ ─ > def fn_a(...):
if a: '' ...
... '' │ return
return ''
if b: '' ├ ─ ─ > def fn_b(...):
... '' ...
return '' │ return
if c: ''
... '' └ ─ ─ > def fn_c(...):
return '' ...
'' return
}
{
#include <stdio.h>
#include <ncurses.h>
signed main() {
initscr();
cbreak();
curs_set(0);
noecho();
nodelay(stdscr, true);
scrollok(stdscr, true);
void recieve() {
addstr("Recieved a message.\n");
}
void block() {
addstr("A message was blocked.\n");
}
void (*strategizing_function)(void) = recieve;
while (1) {
strategizing_function();
char c = wgetch(stdscr);
if (c != EOF) {
strategizing_function = (strategizing_function == recieve ? block : recieve);
}
napms(500);
}
}
}
{
using System;
using System.Collections.Generic;
public interface Operation {
string description { get; set; }
ConsoleColor color { get; set; }
void action();
}
public class ToDown : Operation {
public string description { get; set; }
public ConsoleColor color { get; set; }
public void action() {
int n = Menu.items.FindIndex(o => o.operation == this);
if (n != Menu.items.Count-1) {
Menu.swap(n, n+1);
} else {
Menu.swap(0, n);
}
}
}
public class ToUp : Operation {
public string description { get; set; }
public ConsoleColor color { get; set; }
public void action() {
int n = Menu.items.FindIndex(o => o.operation == this);
if (n != 0) {
Menu.swap(n-1, n);
} else {
Menu.swap(n, Menu.items.Count-1);
}
}
}
public class ToTop : Operation {
public string description { get; set; }
public ConsoleColor color { get; set; }
public void action() {
int n = Menu.items.FindIndex(o => o.operation == this);
Menu.swap(0, n);
}
}
public class Item {
public string name { get; set; }
public ConsoleColor color { get; set; }
public Operation operation { get; set; }
}
public class Menu {
public static List<Item> items = new List<Item>();
public static void swap(int a, int b) {
Operation swap = items[a].operation;
Menu.items[a].operation = Menu.items[b].operation;
Menu.items[b].operation = swap;
}
public static void print() {
foreach (var item in items) {
Console.ForegroundColor = item.color;
Console.WriteLine($"{item.name}:");
Console.ForegroundColor = item.operation.color;
Console.WriteLine($" { item.operation.description}\n");
Console.ResetColor();
}
}
public static void Main(string[] args) {
items.Add(new Item { name = "Item 1", color = ConsoleColor.Yellow,
operation = new ToUp { description = "Move up", color = ConsoleColor.Yellow }
});
items.Add(new Item { name = "Item 2", color = ConsoleColor.Green,
operation = new ToDown { description = "Move down", color = ConsoleColor.Green }
});
items.Add(new Item { name = "Item 3", color = ConsoleColor.Blue,
operation = new ToTop { description = "Move to top", color = ConsoleColor.Blue }
});
while (true) {
print();
Console.Write("$ ");
string input = Console.ReadLine();
if (string.IsNullOrEmpty(input)) continue;
if (int.TryParse(input, out int number)) {
if (number > 0 && number <= items.Count) {
items[number - 1].operation.action();
}
}
}
}
}
}
. ### The strategy pattern is really bad ###
For clarity, I would first like to state that I'm specifically referring to what OOP
depicts as THE strategy pattern. If we restrict our selves to the definition of
"behaviour as data", procedural function pointers and much of functional programming
becomes examples to the strategy pattern. Those work pretty well, I take no offense
to their existence.
On the other hand, if we take a look at the OOP side, its not all so rosy suddenly.
There seems to be multiple reasons to apply the strategy pattern:
.=======================.
I Reasons to strategize I
`=======================`
│
│ .------------------.
├──| Trivially needed |
│ | a new class |
│ `------------------`
│
│ .------------------.
└──| Cope for lack of |
| free functions |
`------------------`
So, as I was attempting to come up with a simpler example than HFDP's duck hierarchy,
I kept coming up with concepts that scream "I AM MY OWN CLASS". For example Magicwands
with engraved behaviour, but then there was no way to gaslight the reader that the
concept we are arriving to is not a Spell. Then as I applied complexity, I realized
the whole dilemma of this design arose from being unable to pinpoint the problem
and not it being inherently harder than say prototyping a function.
For the second part I wish you to refer to the previously mentioned duck example.
However, assuming you are unable to reference the original or dont care enough,
here is the quick rundown:
• Your current design contains the following hierarchy
┏━━━━━━━━━━━━━━━━━┓
┃ class Duck ┃
┣━━━━━━━━━━━━━━━━━┫
│ var duckyness; │
│ void quack(); │
└─────────────────┘
/ \
/ \
/ \
/ \
/ \
/ \
┏━━━━━━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━━━━━━┓
┃ class MallardDuck ┃ ┃ class ReadHeadDuck ┃
┣━━━━━━━━━━━━━━━━━━━━┫ ┣━━━━━━━━━━━━━━━━━━━━┫
│ var duckyness; │ │ var duckyness; │
│ void quack(); │ │ void quack(); │
└────────────────────┘ └────────────────────┘
• You are asked to add a RubberDuck
• Oh no! rubber ducks cannot quack! they squeak
• After juggling terrible ideas such as a quackable interface implemented by the children,
Strategies save the day
┏━━━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━━━┓
┃ class Duck ┃ ┃ interface Quack ┃
┣━━━━━━━━━━━━━━━━━┫ ┣━━━━━━━━━━━━━━━━━┫
│ var duckyness; │ │ void quack(); │
│ var quack; │ └─────────────────┘
└─────────────────┘ | |
/ | \ | |
/ | \ | |
/ | \ | |
/ | \ | |
/ | \ | |
/ | \ | |
┏━━━━━━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━━━━━━┓ | |
┃ class MallardDuck ┃ ┃ class ReadHeadDuck ┃ ┃ class RubberDuck ┃ .-----------. .-----------.
┣━━━━━━━━━━━━━━━━━━━━┫ ┣━━━━━━━━━━━━━━━━━━━━┫ ┣━━━━━━━━━━━━━━━━━━━━┫ | class Quack | | class Squek |
│ var duckyness; │ │ var duckyness; │ │ var duckyness; │ `---,-------` `-----------`
│ var quack; │ │ var quack; │ │ var quack; │- - - - - - - - - - - -'
└────────────────────┘ └────────────────────┘ └────────────────────┘ :
: :
'- - - - - - - - -'- - - - - - - - - - - - - - - - - '
Ok, now lets think about this critically. The quack interface describes a single behaviour
that does not require internal state.
We used to have those things actually, they were called functions.
Seriously, 2 new classes and 1 additional interface is introduced to describe 2 behaviours.
Our source got more complex, bloated, dependant -as there are new couplings, never mind how
close they are-, and more verbose to extend. If thats not a work around I dont know what is.
Digging a little further, does Quack look encapsulated to you? Because to me she seems like a
free use whore pretending to be better than her ancestors under the banner of self-expression,
but I digress.
As a minor note, I would like to poke fun, how without optimizations the ABOVE will be
oh-so-slightly slower than the procedural equivalent because now we have to instantiate
quacks amongst other things.
"But Anon, you ignored rockets being strapped to ducks during the simulation,\
which modifies their fly behaviour" - I hear you say. You are correct.
This does mean that we will have to change behaviour at runtime.
Which, again, leaves us with scenario 1. Procedurally speaking, having a strategy
would be a pattern avoiding strangely placed and convoluted logical blocks, but
to replicate the problem in OOP, you would need a hierarchy of strangely placed
and convoluted logical blocks, at which point it should be obvious to anyone
something very wrong is going on.
#
Mock: Mock:
Image:
"The broom might not be the best dance partner,\
but is always available for practice."
• implementation of an interface
• simulates desired behaviour
• used for testing and prototyping
• can be used to allow for compilation and therefor continue-d work
while waiting for a proper implementation
• can be used to simulate unlikely and hard to invoke errors
Facade: Facade:
Image:
"The funnel that concentrates the input,\
for higher throughput."
• a higher level interface combining one or more lower level interfaces
• allows for less verbose expression of complex, but common problems
while not polluting the original low-level code
{
• curl Easy interface (https://curl.se/libcurl/c/curl_easy_init.html)
}
Proxy: Proxy:
Image:
"The commentators talking over the race."
• is a block that takes in an input block and returns a compatible block
with an existing functionality extended
• the interface is not modified
• for it to make sense, behaviour must be added
• code relying on the input remains unchanged
○ useful examples
• stopper
• caching
• restricting access (like a firewall)
Decorator: Decorator:
• a proxy which aggregates, but does not compose the proxied class
• commonly used when multiple layers of proxying is desirable
— consider the following demented statement from "Head First Design Patterns, 2nd Edition":
"Q: Can decorators know about the other decorations in the chain? Say I wanted\
my getDescription() method to print “Whip, Double Mocha” instead of “Mocha,\
Whip, Mocha.” That would require that my outermost decorator know all the\
decorators it is wrapping.\
A: Decorators are meant to add behavior to the object they wrap. When you need to\
peek at multiple layers into the decorator chain, you are starting to push the decorator\
beyond its true intent. Nevertheless, such things are possible. Imagine a\
CondimentPrettyPrint decorator that parses the final decription and can print “Mocha," "\
Whip, Mocha” as “Whip, Double Mocha.” Note that getDescription() could return an\
ArrayList of descriptions to make this easier."
• while that technically works, parsers are not very nice to maintain,
the parser has to assume a lot about the output and
performace just got executed with a butter knife
{ #!/bin/python3
# From scratch example in python for decorators
# Do NOTE that python also has builtin syntax sugar
def print_canary():
print(''' .-. \n'''
+ ''' /'v'\ \n'''
+ ''' (/ \) \n'''
+ '''='="="===<\n'''
+ '''mrf|_| \n''',
end=''
)
def print_with_yellow(func):
def wrapper(*args, **kwargs):
print("\033[33m")
r = func()
print("\033[0m")
return r
return wrapper
myBirdFunction = print_canary
myBirdFunction()
myBirdFunction = print_with_yellow(print_canary)
myBirdFunction()
}
{
class Decorator {
static
class Canary {
private
String ascii = " .-. \n"
+ " /'v'\\ \n"
+ " (/ \\) \n"
+ "='=\"=\"===<\n"
+ "mrf|_| \n"
;
public
void print() {
System.out.print(ascii);
}
}
static
class YellowCanary extends Canary {
public
void print () {
System.out.println("\033[33m");
super.print();
System.out.println("\033[0m");
}
}
public static
void main(String[] args) {
Canary sunny = null;
sunny = new Canary();
sunny.print();
sunny = new YellowCanary();
sunny.print();
}
}
}
Template: Template:
Image:
"The empty frame hanging on the wall;\
the nail is already there,\
you just have to find something that fits."
• an algorithm is written without knowing implementation details
• the meaning of the operation is know, but the side-effects are not
{
Disaster dead_end(Entity &e) {
Lung l;
do {
l = e.scream();
} while (not l);
return e.next();
}
}
Generics:
• compile time template-s
{
template <typename C>
auto min(const C &iterable) {
auto r = std::begin(iterable);
for (auto it = std::begin(iterable); it != std::end(iterable); it++) {
if (*it < *r) {
r = it;
}
}
return *r;
}
}
Adapter: Adapter:
Image:
"The magic box that sits between two incompatible interfaces."
• when an interface is wrapped to be compatible with another
• the adapter pattern is could be a sign of technical dept;
ask this: are we using an adapter because it makes sense
or because we are making cuts?
○ use case examples
— good
• programming to the abstractions of a library and not the implementation
• you have a RATIONAL fear of not touching the internal cryptography class
of your organization
• 3th party libraries are being wired together
— bad
• Rajeshwar copy pasted his implementation from another project
• "oh wow, so many LOC, i better not touch anything"
• writting an adapter-adapter, because the more patterns it has,
the better the project gets, right?
abstract:
• not adapting to new change or preparing for future change, but embracing on demand change
• allowing multiple backends
.--------.
┌─── | C |
│ `--------`
│ .--------.
├─── | C++ |
│ `--------`
│ .--------.
+============+ ┏━━━━━━━━━━━┓ ├─── | D |
I Programmer I ─── ┃ DSL Bison ┃ │ `--------`
*============* ┣━ ━━ ━━ ━━ ┫ │ .--------.
│ internals ├─┴─── | Java |
└───────────┘ `--------`
Observer: Observer:
Image:
"The meddling crazy old woman accross the street\
who keeps calling the authorities."
┏━━━━━━━┓
┏┛ ┗┓ ┌──────────────┐
┏┛ ┗.------------->│ Subscriber 1 │
┃ Publisher/┃ └──────────────┘
┃ *` ┃ ┌──────────────┐
┃ *---------------->│ Subscriber 2 │
┃ *. ┃ └──────────────┘
┗┓ \┛ ┌──────────────┐
┗┓ ┏┛'------------->│ Subscriber 3 │
┗━━━━━━━┛ └──────────────┘
• hierarchical
• the publisher is an object that has knowledge of some event,
a list of subscribers and the responsibility to notify said subscribers
if said event occurs
• the subscriber is an arbitrary object or function that should
gain execution control if some event has occurred;
they obviously must share an interface
• a way to subscribe and unsubscribe must exist; classically using methods
• subscribers can be added and removed at runtime
{
Publisher Subscriber 1 Subscriber 2
______ ______
__ /O .' '. .' '.
b b / / | | | |
'b ^/ / | .--. | | .--. |
' I/ / | { } | | { } |
' C_/I '. `..` .' '. `..` .'
' I I |_ii_| |_ii_|
'P P ;====; ;====;
P__P ;====; ;====;
│ │ │
└───────────────────────────┴─────────────────────────────┘
interface ElectronicInput {
void input(bool state);
}
class Bulb : ElectronicInput {
bool state;
void input(bool state) { this.state = state; }
}
class Switch {
list<ElectronicInput> subscribers;
void on(void) {
for (i : subscribers) {
i.input(true);
}
}
void off(void) {
for (i : subscribers) {
i.input(false);
}
}
}
}
Signals_and_slots:
• specific implementation of the observer pattern
• the naming and concept originates from QT
Command: Command:
• the idea is encapsulating arbitrary code and state to be executed
• behaviour can be changed at run time
• somewhat analogous to function pointers
{
interface Command {
void execute(void);
}
class UniversalRemote {
Command button_one;
Command button_two;
}
class PartyLights {
void turn(bool state);
}
class TurnPartyLightsOn : Command {
PartyLights light;
void execute { light.turn(true); }
}
class TurnPartyLightsOff : Command {
PartyLights light;
void execute { light.turn(false); }
}
}
Active_object:
• multitasking technique
• a command queue is maintained
• commands are added to the queue and executed whenever its their turn
• protects from hanging
Event_sourcing: Event_sourcing:
Image:
"The line of wagons keeping eachother safe\
as they move across the desert."
• the act of implicitly storing state as a list of events
• the concrete state may or may not be represented directly
• the term itself is associated with high importance systems,
where any fuck-up would be very costly
• highly dependent on the command pattern (see ABOVE)
• can act as the poor-mans RPC
• can be used to rapidly implement scripting or undo functionality,
since objects are easy to record and an Command::execute_reverse()
could accompany any Command::reverse()
Migration: Migration:
• a special type of event sourcing, in the context of relational databases
• agile technique
• basically "how to fix a fucked database"
• a migration is code, which describes how an existing database should be modified
○ goals
• painless schema changes
• preserved data integrity
• reproducibility on other deployments
• one may "bring up", "apply" or "perform" a migration (to the same effect)
-- -- Typical minimalistic migration, slightly modifying the status quo
-- new requirements specify that each user can be warned once being banned for good
ALTER TABLE user ADD COLUMN is_warned BOOLEAN DEFAULT FALSE;
• the migration could be in any language {raw SQL, PHP with factory syntax sugar, Python class hierarchy}
• since migrations only specify change, they must be run sequentially to get an up-to-date version
-- -- Migration demonstrating dependence, this migration would fail without the previous example also being run
-- on new years eve reset all warn statuses
ALTER TABLE user SET is_warned FALSE;
• they often contain the definition for the opposite action { CREATE table -> DROP table}
— migrations tend to be stored as individual files with clear sequencing
{ migrations/
├── 0001_init
├── 0002_add_is_warned_column
└── 0003_reset_is_warned_column
}
• the current state of migrations is always recorded
{
Migration name ............................................................. Batch / Status
2014_10_12_000000_create_users_table .............................................. [1] Ran
2014_10_12_100000_create_password_reset_tokens_table .............................. [1] Ran
2019_08_19_000000_create_failed_jobs_table ........................................ [1] Ran
2019_12_14_000001_create_personal_access_tokens_table ............................. [1] Ran
2023_12_09_145206_init ............................................................ Pending
}
• (2023) they tend to be abstracted in awfully framework specific ways and be buggy as fuck,
with broken drops, corrupted fields and state stalemates; my advice is to only ever touch them
lightly and never try looking under the hood; with errors encountered, you are better of
restarting with a clean state
seeder:
• a special migration which only deals with adding data
Factory: Factory:
Image:
"The factory, parts go in on a conveyor belt and items come out,\
but their internals and the assembly is hidden."
• a central point of logic controlling polymorphic construction
• the appropriate subclass is deducted from the parameters passed
• encapsulates subclasses, decoupling it from the code using them
• a back-swing of the encapsulation inheritence has violated
Factory_class:
• dedicated class that is used as a factory
• in the context OOP this is what "just" factory referes to
• usually the class name will contain the word Factory
• the concepts of factory function && factory method
follow exactly the same logic
Abstract_factory:
• "kit"
• polymorphism applied to factories
{
┏━━━━━━━━━━━━━━━━━━━━┓
┃ interface ┃
┃ GameTileFactory ┃
┣━━━━━━━━━━━━━━━━━━━━┫
│ tile CreateCube(); │
└────────────────────┘
/ \
/ \
/ \
┏━━━━━━━━━━━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ class ┃ ┃ class ┃
┃ ForestLevelTileFactory ┃ ┃ DesertLevelTileFactory ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━┫ ┣━━━━━━━━━━━━━━━━━━━━━━━━┫
│ tile CreateCube(); │ │ tile CreateCube(); │
└────────────────────────┘ └────────────────────────┘
}
Visitor: Visitor:
Image:
TODO ?!
{
class AA : public A {
void accept(Visitor v) {
v.AA();
}
};
}
" \
The Visitor Pattern is very important in general, but \
it’s beyond ubiquitous for us compiler developers. It \
helps avoid bloating AST node classes with methods and \
state required for the various operations we perform on \
them. It also often saves us from writing AST traversal \
code. \
"
• polimorphism/overloading, but weirder
• we wish to use polymorphism, with methods that can only access public fields;
this problem has no language support what so ever
• a Visintor class/interface is created, which has the desired outsider methods
for our subclasses and a function which calls an accept method which allows
instances to "chose" which is the correct method
{
foreach e : el {
x v.visit(e
│ }
│
│ <-- "Double dispatch"
│ | |
│ V V
│
│
│
└────▶| visit(e){ |
| e.accept(this) |x ─ ─ ─ ┐
| } |
| | ├ ─ ─ ▶| accept(v){ |
| on_a(e){ |◀─────────────x| v.on_a() |
| (a)e; | │ | } |
| } | +------------+
| | │
| on_b(e){ |◀────┐
| (b)e; | │ │
| } | │
+------------------+ │ └ ─ ─ ▶| accept(v){ |
└────────x| v.on_b() |
| } |
+------------+
}
• we add 2 redundant function calls to each 1 we actually wanted,
we add a new interface on which our hierarchy depends on,
on the process the original hierarchy is modified and
the new class closely couples with our hierarchy
• i get the problem, i really do, but this is insanity;
one of those "self proposed problems"
— other ways this could be solved:
• have language support for methods which can only access
private fields
• use reflections in some (any) way to figure out the overloading
• do not employ morons who fuck up everything they touch
• the cool thing about this is encapsulation, right? we may have
an {Exporter} visitor which is clearly cut from the base,
preventing it from getting helplessly bloated,
however the double dispatch model is the ugliest thing i've seen;
its clearly a work around over a new sort of problem
Builder: Builder:
Image:
"The production line, you set up your machines then\
whatever you place onto the belt, it comes out on the other side.\
Processing is guaranteed, you only need to judge the quality of the end product."
• a class whichs purpose is to aid contruct object of another class
• construction is broken up into multiple steps represented as methods
• the function which finally returns the actual instance is usually called .build()
• every member which is used for construction returns the builder instance,
to allow method call chaining
• construction values not provided by the developer are usually (hopefully)
set to sensible defaults
• can be regarded as very primitive DSLs
• a monad concerned with construction
• useful when not all construction options are available immidiately,
as the builder can be used for storing partial options temporaliy
• may or may not perform extra operations other than calling the constructor
{
Human enemy_wizard = HumanBuilder
.random_level_between(3, 10)
.add_random_n_items(10)
.add_random_armour(ARMOUR_RARE)
.add_random_potion_effect()
.build()
;
}
Inversion_of_control: Inversion_of_control:
Image:
"The guide dog. He is under your command,\
yet he is the one leading the way"
• "IoC"
• giving 3th party code {framework, DSL, library} control over our application
— hollywood princaple:
• "Don't call us, we will call you."
• catchy reminder to "IoC exists, and if you are using it,\
dont fuck it up by closely coupling your handlers"
traditional_sense:
• the control flow is inverted
• event or state driven
{
%%
a { ECHO; }
.|\n { ; }
%%
signed main(void) {
yylex();
}
}
java_sense:
• alternative meaning that was conceived by OOP and Java
• the responsibility of creating ancestors is delegated away
• leverages dependency injection
• saves you from creating a bunch of objects by hand
• only comes up when a framework is a cluster-fuck anyways,
so its safe to call it shit
Honourable_mentions: Honourable_mentions:
• the following are too obvious and or stupid to have their own sections
Bridge: Bridge:
+---+ +---+
| a |-. .-| а |
+---+ '. .' +---+
'. .'
+---+ '.+-----------+ +-----------+.' +---+
| b |--------| interface |---| interface |--------| б |
+---+ .'+-----------+ +-----------+'. +---+
.' '.
+---+ .' '. +---+
| c |-' '-| в |
+---+ +---+
• abstraction depends on abstraction
Monostate: Monostate:
• two code segments sharing state using references
— so generic that its meaningless:
• virtual tables are monostate
• class static is monostate
• shared pointer is monostate
• variable reference is monostate
• when combined with singleton,
it is the text-book example of reinventing global state
State: State:
Automaton
Flex
• state automaton applies practically
• easy to debug
• easy to visualize
• easy to understand
• great for parsing (might be only a component)
• great for drawing conclusions from many conditionals
Composite: Composite:
• not to be confused compounds
• a container that redefines operator-s to apply to all elements
{ # Composite in Python/numpy
import numpy as np
array = np.array([1.5, 2.7, 3.9, 4.4, 5.8])
array = array / 2
print(array) # [0.75 1.35 1.95 2.2 2.9]
# NOTE: dividing a matrix by a number is formally defined
# as dividing each element
}
Prototype: Prototype:
• a so called "prototype" object is used to initialize further objects,
using memory copying
• useful when the computations of the initialization would be costly;
but can be done ahead of time
Caching: Caching:
Memoization:
• argument based caching for pure functions
Multi_processing: Multi_processing:
• strictly outsourced to the kernel
Thread:
• abstraction over a physical core
• can be software emulated by time sharing
• the number of threads do not depend on the number of cores
• every thread can access the resources of the main thread
• each thread has its own stack
Spinlock:
{ |
while(i) { ; } | i = false;
}
• a pattern of patterns
MVC: MVC:
• "Model-View-Controller"
• used for UIs
{
Controller (UserInput) {
switch (UserInput.Type) {
case A: return ModelA(UserInput.Data);
case b: return ModelA(UserInput.Data);
}
}
ModelA (UserData) {
ProcessedData = process(UserData);
return ViewX(ProcessedData);
}
ViewX (ProcessedData) {
return f"I: {ProcessedData.i}, "
f"H: {ProcessedData.h};"
}
}
┌───────┐
.-│ Model │-
.' └───────┘ '.
| |
| |
┌──────┐ ┌────────────┐
│ View │ │ Controller │
└──────┘ └────────────┘
\ /
\ /
\ /
\ /
+------+
| user |
+------+
— MVP:
• "Model-View-Presenter"
• an intelectual blackhole
• you can find the MVC graph online with all possible permutations of arrows;
mostly because the view gets associated with the literal interface
the user is presented with, and not what procudes that using data
• MVP is a "clarification" regarding the controversy that webdevs caused;
because they cannot abstract and had breakfast yesterday