Design principles and Design Patterns
Table of Contents
These are my notes while watch this Youtube channel.
Cohesion and coupling
- Separate code that changes from that does not change.
- Program to interfaces (abstract class) instead of implementations.
- Prefer composition over inheritance.
- Think about the bridge pattern
- Delegation
- Call a class member (refer to item 3) to do things
SOLID:
- Single responsibility
- High cohesion
- Break a long class into small classes. But this will introduce coupling.
- Open/close
- Open for extension / close for modification
- Instead of modifying the original class to add some additional type support, use the abstract class and a new inheritance is preferred.
- Open for extension / close for modification
- Liskov substitution
- When you have an abstract class and an abstract method, and you find the inherited methods want to have a different parameter from the abstract method. Pass the parameters in from the constructor, not the method itself.
- Interface segregation
- Say you want two sets of interfaces, some subclasses only have one, while others have both. Instead put them all in one abstract class, you can
- Put one set into one abstract class, inherit it and add the second set
- Create two abstract classes for two sets of interfaces separately, and combine them by composition in the subclass.
- Say you want two sets of interfaces, some subclasses only have one, while others have both. Instead put them all in one abstract class, you can
- Dependency inversion
- Instead of depending/coupling on a concrete class, you create an abstract class and depend on it, and make your original concrete class inherit from the abstract class.
- This will make adding new inheritance to the abstract class easier.
Design patterns
- Creational
- Singleton: only one instance; global access
- For python, defining it as a module is the best way to do it.
- Factory:
- When
- You want to create a group of objects together
- if there is just one object you want to create, just construct it.
- There are only a few valid combinations of objects
- If any combination is allowed, you’d better use bridge pattern
- You want to create a group of objects together
- Factory is not the owner of the created objects
- Abstract factory and then derive it for every valide combination
- When
- Singleton: only one instance; global access
- Behavioral
- Strategy: A function/algorithm can be done in different ways.
- put the different strategies into classes/functions, and pass it in instead of passing a string/enum around and do different stuff based on it.
- If different strategies have different parameter variables. Make each strategy a class, and pass parameters to the constructor (Liskov substitution).
- Template: Base class defines the skeleton of the algorithm. Derived class can override some of the steps in the algorithm.
- It is very similar to the strategy pattern if you implement different strategies with classes. There are two small differences:
- Template has multiple steps, strategy usually has just one step. That is why you can use functions for the strategy pattern.
- For the strategy pattern, the strategy is passed to a function to call. In the template pattern, you implement the skeleton function in the base class and inherit it to all strategies.
- It is very similar to the strategy pattern if you implement different strategies with classes. There are two small differences:
- Observer / Publisher-Subscriber / Callback / Event system: one-to-many dependency at runtime, one object changes its state; notifies many dependents. One trigger causes many listeners to do some stuff.
- Note that when I say one, it does not mean there is really just one publisher/event. There could be a few different publisher/events, and they all have the same subscribers/listeners.
- Instead of writing code that directly manipulates the subscribers/listeners at the publisher/event end, you write the code at the subscribers/listeners’ end and hand the function/callback over (subscribe) to the publisher/event to call.
- Strategy: A function/algorithm can be done in different ways.
- Structural
- Adaptor / Wrapper: convert one interface into another interface
- Composition instead of inheritance
- Delegate so that it works under the new interface.
- Bridge: decouple two things by using composition.
- Shape and color class, Shape contains a color.
- Difference with Strategy:
- Strategy is passed into a function
- Bridge is passed into a class as a composition. This is why strategy is a behavior and bridge is a structural
- Adaptor / Wrapper: convert one interface into another interface
Dependency Injection vs dependency inversion:
- Dependency of two classes (strong to weak)
- Inheritance
- Composition: has as member variable
- Passed in as parameter:
- Dependency injection is a design pattern
- Create outside and pass it in(to the constructor or the method).
- Separate creating and using
- Dependency inversion is a design principle in SOLID:
- In addition to passing in, it also needs an abstract class to reduce the dependency
- This will make adding inheritance easier.
Code smells:
- Use string to represent a type -> enum
- Duplicate code -> extract out as a function or class
- Not using built-in functions (e.g. list comprehension)
- Vague variable / function name (e.g. no unit)
- Isinstance -> inheritance
- Boolean flag to do two different things -> break into two functions
- Catch and ignore all exception
- Not using custom exceptions -> custom exceptions can carry custom fields to provide more info.