Software Craftsmanship
SOLID Design Principles
These are agile, class level design principles. They allow you to minimize coupling and maximize cohesion. The result is code that is easy to read and maintain.
Single Responsibility Principle (SRP)
Open/Closed Principle (OCP)
Liskov Substitution Principle (LSP)
Interface Segregation Principle (ISP)
Dependency Inversion Principle (DIP)
Coupling is a measure of interdependence between two routines or modules.
Cohesion is a measure of how strongly related each piece of functionality is.
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
Each class or module should have only one responsibility, and thus, one reason to change.
Why this principle? It provides guidance around how large to make classes/modules.
Large classes tend to be incohesive.
The single responsibility should be entirely encapsulated by its class.
Bottom line: make classes small enough to maximize cohesion, but large enough to minimize coupling.
Single Responsibility Principle (SRP)
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 getCurrentPage() {
return "current page content";
}
}The function printCurrentPage changes depending on how you want to output the page's content.
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";
}
}interface Printer {
function printPage($page);
}
class PlainTextPrinter implements Printer {
function printPage($page) {
echo $page;
}
}
class HtmlPrinter implements Printer {
function printPage($page) {
echo '<div style="single-page">' . $page . '</div>';
}
}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 save() {
$filename = '/documents/'. $this->getTitle(). ' - ' . $this->getAuthor();
file_put_contents($filename, serialize($this));
}
}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";
}
}
class SimpleFilePersistence {
function save(Book $book) {
$filename = '/documents/' . $book->getTitle() . ' - ' . $book->getAuthor();
file_put_contents($filename, serialize($book));
}
}Open/Closed Principle (OCP)
Software entities should be open for extension,
but closed for modification.
class GraphicEditor {
public void drawShape(Shape s) {
if (s.m_type==1)
drawRectangle(s);
else if (s.m_type==2)
drawCircle(s);
}
public void drawCircle(Circle r) {....}
public void drawRectangle(Rectangle r) {....}
}class Shape {
int m_type;
}
class Rectangle extends Shape {
Rectangle() {
super.m_type=1;
}
}
class Circle extends Shape {
Circle() {
super.m_type=2;
}
}class GraphicEditor {
public void drawShape(Shape s) {
s.draw();
}
} class Shape {
abstract void draw();
}
class Rectangle extends Shape {
public void draw() {
// draw the rectangle
}
}
class Circle extends Shape {
public void draw() {
// draw the circle
}
} Routines that use objects of parent classes must be able to use objects of subclasses without altering the correctness of the program.
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;
}
}No client should be forced to depend on methods it does not use.
Interface Segregation Principle (ISP)
public interface Toy {
void setPrice(double price);
void setColor(String color);
void move();
void fly();
}
public class ToyHouse implements Toy {
double price;
String color;
@Override
public void setPrice(double price) {
this.price = price;
}
@Override
public void setColor(String color) {
this.color=color;
}
@Override
public void move(){}
@Override
public void fly(){}
}
public class ToyHouse implements Toy {
double price;
String color;
@Override
public void setPrice(double price) {
this.price = price;
}
@Override
public void setColor(String color) {
this.color=color;
}
@Override
public String toString(){
return "ToyHouse: Toy house- Price: "+price+" Color: "+color;
}
}
public interface Toy {
void setPrice(double price);
void setColor(String color);
}
public interface Movable {
void move();
}
public interface Flyable {
void fly();
}
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)
Why this principle? It provides guidance around how to correctly use interfaces.
Bottom line: implementations should depend on interfaces, not the other way around.