Software Craftsmanship
SOLID Design Principles
April 19, 2016
These are class level design principles. They allow you to minimize coupling and dependencies. The result is code that is easy to read and refactor.
Single Responsibility Principle (SRP)
Open/Closed Principle (OCP)
Liskov Substitution Principle (LSP)
Interface Segregation Principle (ISP)
Dependency Inversion Principle (DIP)
A class should have only one reason to change and when making that change it should only effect the class.
Every class should only have a single responsibility.
The single responsibility should be entirely encapsulated by its class.
Single Responsibility Principle (SRP)
a single reason for change
class Book {
function getTitle() {
return "A Great Book";
}
function getAuthor() {
return "John Doe";
}
function turnPage() {
// pointer to next page
}
function printCurrentPage() {
echo "current page content";
}
}The function printCurrentPage changes depending on how you want to output the page's content.
Console, File, or DataBase. All require altering the Book class.
a single reason for change
class Book {
function getTitle() {
return "A Great Book";
}
function getAuthor() {
return "John Doe";
}
function turnPage() {
// pointer to next page
}
function getCurrentPage() {
return "current page content";
}
function getLocation() {
// returns the position in the library
// ie. shelf number & room number
}
}Location is only meaningful in the context of a library, so the library should handle the location.
Location is only meaningful in the context of a library, so the library should handle the location.
class Book {
function getTitle() {
return "A Great Book";
}
function getAuthor() {
return "John Doe";
}
function turnPage() {
// pointer to next page
}
function printCurrentPage() {
echo "current page content";
}
function save() {
$filename = '/documents/'. $this->getTitle(). ' - ' . $this->getAuthor();
file_put_contents($filename, serialize($this));
}
}Coupling is a measure of how closely connected two routines or modules are.
Cohesion is a measure of how strongly related each piece of functionality is.
Coupling and Cohesion have an inverse relationship.
With tightly coupled systems:
a change in one module forces changes in the others.
modules are harder to reuse.
modules are harder to test.
With low cohesion systems:
modules are complex with more operations.
modules are less maintainable and harder to work with.
Two or more modules are orthogonal if
changes in one do not affect the other.
When components are highly interdependent, there is no such thing as a quick, local fix.
Orthogonal software provides increased productivity and decreased risk because developers never have to worry about side-effects of making changes.
Orthogonal components are easier to swap meaning less dependence on a specific library or vendor.
Software entities should be open for extension,
but closed for modification.
Open/Closed Principle (OCP)
This seems obvious but why is it important?
@implementation C1NetworkOperation
- (NSString *)endpoint
{
NSString *base = @"http:someurl/api";
if (self.command != None)
base = [base stringByAppendingFormat:@"/%@", [self commandEnumToString]];
return base;
}
- (NSDictionary *)parameters
{
return @{
@"amount" : self.amount
};
}
- (C1CSSerializerType)responseSerializerType
{
return C1CSSerializerTypeJSON;
}
@endUI components in iOS are an excellent implementation of this principle.
//###########################
#pragma Behavior Overrides
- (CGRect)textRectForBounds:(CGRect)bounds
{
return [super textRectForBounds:UIEdgeInsetsInsetRect(bounds, self.edgeInsets)];
}
- (CGRect)editingRectForBounds:(CGRect)bounds
{
return [super editingRectForBounds:UIEdgeInsetsInsetRect(bounds, self.edgeInsets)];
}
- (CGRect)leftViewRectForBounds:(CGRect)bounds
{
CGRect textRect = [super leftViewRectForBounds:bounds];
textRect.origin.x = _iconPadding;
int padding = (int)((bounds.size.height - 25) / 2);
textRect.origin.y = bounds.origin.y + padding;
textRect.size.width = 25;
textRect.size.height = 25;
return textRect;
}
- (CGRect)caretRectForPosition:(UITextPosition *)position
{
CGRect originalRect = [super caretRectForPosition:position];
float height = self.frame.size.height - (2 * _cursorPadding);
float offset = _cursorPadding;
originalRect.origin.y = self.bounds.origin.y + offset;
originalRect.size.height = height;
originalRect.size.width = originalRect.size.width / 1.5f;
return originalRect;
}
- (BOOL)becomeFirstResponder
{
[self setBackgroundFromState:eActive];
return [super becomeFirstResponder];
}
- (BOOL)resignFirstResponder
{
[self setBackgroundFromState:eDefault];
return [super resignFirstResponder];
}
@end
Since objects are less dependent on the internal structure of other objects, classes can be changed without reworking their callers.
Functions that use pointers or references to base
classes must be able to use objects of derived
classes without knowing it.
Liskov Substitution Principle (LSP)
Rectangle rect = new Rectangle();
rect.SetHeight(10); rect.SetWidth(2);
assert(rect.CalculateArea() == 20);
Rectangle rect1 = new Square();
rect1.SetHeight(10); rect1.SetWidth(2);
assert(rect1.CalculateArea() == 20);
public class Rectangle
{
private int height, width;
public void SetHeight(int height) { this.height = height; }
public void SetWidth(int width) { this.width = width; }
public int CalculateArea()
{
return this.Height * this.Width;
}
}
public class Square : Rectangle
{
public override void SetHeight(int height) {
base.height = height;
base.width = height;
}
public override void SetWidth(int width) {
base.height = width;
base.width = width;
}
}IList<Card> cards = new ArrayList<Card>();
void setUp() {
cards.add(new DebitCard());
}
void testProcessTransaction() {
for (Card card : cards) {
Card.setAvailableFunds(300);
assert(Card.canDebit(320) == false);
}
}public class Card {
protected int availableFunds;
public void setAvailableFunds(int funds) {
availableFunds = funds;
}
public virtual bool canDebit(int amount) {
return (availableFunds > amount);
}
}public class DebitCard : Card {
private int overdraftProtection;
public DebitCard(int odProtection) {
overdraftProtection = odProtection;
}
public override bool canDebit(int amount) {
return (super.availableFunds + overdraftProtection > amount);
}
}The interface-segregation principle (ISP) states that no consumer should be forced to depend on methods it does not use.
Interface Segregation Principle (ISP)
Interface-based design:
A. High-level modules should not depend on low-level modules. Both should depend on abstractions.
B. Abstractions should not depend upon details. Details should depend upon abstractions.
Dependency Inversion Principle (DIP)
Separate high-level and low-level components into different packages.
High-level interfaces and abstractions belong in packages explicitly designed for that purpose.
This allows interchangeable implementations and promotes code encapsulation with flexibility.