599 lines
9.2 KiB
Plaintext
599 lines
9.2 KiB
Plaintext
Application
|
||
Programming
|
||
Hend Alkittawi
|
||
|
||
SOLID Principles
|
||
Applying SOLID Principles In Object
|
||
Oriented Design
|
||
|
||
WHY SOLID PRINCIPLES?
|
||
-
|
||
|
||
Watch this video from the Coursera course Software Development
|
||
Processes and Methodologies
|
||
|
||
-
|
||
|
||
The content for this lecture is based on a series of
|
||
papers/book chapters by Robert Martin
|
||
|
||
-
|
||
|
||
You might find these references useful
|
||
-
|
||
|
||
Uncle Bob SOLID principles
|
||
|
||
-
|
||
|
||
SOLID Design Principles with Java Examples | Clean Code and
|
||
Best Practices | Geekific
|
||
|
||
SYMPTOMS OF ROTTING DESIGNS
|
||
-
|
||
|
||
-
|
||
|
||
Rigidity
|
||
-
|
||
|
||
Difficult to change in even simple ways
|
||
|
||
-
|
||
|
||
Changes cause a cascade of issues in dependent modules
|
||
|
||
Immobility
|
||
-
|
||
|
||
-
|
||
|
||
Inability to reuse modules
|
||
|
||
Fragility
|
||
-
|
||
|
||
Tendency for changes to cause problems in many areas
|
||
|
||
-
|
||
|
||
New problems often in areas with no conceptual relationship to the original
|
||
change
|
||
|
||
-
|
||
|
||
Viscosity
|
||
-
|
||
|
||
Viscosity of design → It is harder to make changes that preserve the original
|
||
design
|
||
|
||
-
|
||
|
||
Viscosity of environment → Development environment is slow and inefficient
|
||
|
||
CHANGING REQUIREMENTS
|
||
-
|
||
|
||
Requirements change in a way that was not anticipated by the
|
||
original design
|
||
|
||
-
|
||
|
||
The requirements document is the most volatile document in a
|
||
project
|
||
|
||
-
|
||
|
||
We need to make our designs resilient to changes and protect
|
||
them from rotting
|
||
|
||
-
|
||
|
||
Tree Comic
|
||
|
||
DEPENDENCY MANAGEMENT
|
||
-
|
||
|
||
Rot is caused by changes that introduce new and unplanned for
|
||
dependencies
|
||
|
||
-
|
||
|
||
Rigidity, Fragility, Immobility, and Viscosity are directly or
|
||
indirectly caused by improper dependencies between software
|
||
modules
|
||
|
||
-
|
||
|
||
To avoid rot, dependencies between modules in an application
|
||
must be managed
|
||
|
||
-
|
||
|
||
This management consists of dependency firewalls across which
|
||
dependencies do not propagate
|
||
|
||
SOLID PRINCIPLES
|
||
-
|
||
|
||
SOLID is a way to manage dependencies
|
||
-
|
||
|
||
The Single Responsibility Principle (SRP)
|
||
|
||
-
|
||
|
||
The Open Closed Principle (OCP)
|
||
|
||
-
|
||
|
||
The Liskov Substitution Principle (LSP)
|
||
|
||
-
|
||
|
||
The Interface Segregation Principle (ISP)
|
||
|
||
-
|
||
|
||
The Dependency Inversion Principle (DIP)
|
||
|
||
SOLID PRINCIPLES
|
||
-
|
||
|
||
The Single Responsibility Principle (SRP)
|
||
-
|
||
|
||
There should never be more than one reason for a class to
|
||
change
|
||
|
||
-
|
||
|
||
If a class has more than one responsibility, then there will
|
||
be more than one reason for it to change
|
||
-
|
||
|
||
-
|
||
|
||
A class should have one responsibility
|
||
|
||
Multiple responsibilities can become coupled
|
||
-
|
||
|
||
Changes to one responsibility can impair the class' ability to
|
||
meet its other responsibilities
|
||
|
||
SOLID PRINCIPLES
|
||
-
|
||
|
||
The Single Responsibility Principle (SRP)
|
||
-
|
||
|
||
An example of how a class that has too many responsibilities
|
||
can be split into multiple classes
|
||
|
||
-
|
||
|
||
If needed, a class can have objects from the other classes
|
||
|
||
Employee
|
||
|
||
Employee
|
||
|
||
EmployeeDB
|
||
|
||
EmployeeReport
|
||
|
||
name
|
||
salary
|
||
|
||
name
|
||
salary
|
||
|
||
name
|
||
salary
|
||
|
||
name
|
||
salary
|
||
|
||
// payroll methods
|
||
// hr methods
|
||
// DB methods
|
||
|
||
// pay methods
|
||
|
||
// DB methods
|
||
|
||
// hr methods
|
||
|
||
SOLID PRINCIPLES
|
||
-
|
||
|
||
The Open Closed Principle (OCP)
|
||
-
|
||
|
||
Software entities should be open for extension but closed for
|
||
modification
|
||
|
||
-
|
||
|
||
Design modules that never change
|
||
-
|
||
|
||
When requirements change, extend the behavior of a module by
|
||
adding new code not by changing code that already works
|
||
|
||
-
|
||
|
||
Modules that conform to OCP
|
||
-
|
||
|
||
Have behaviors that can be extended
|
||
|
||
-
|
||
|
||
Have source code that does not change
|
||
|
||
SOLID PRINCIPLES
|
||
-
|
||
|
||
The Open Closed Principle (OCP)
|
||
-
|
||
|
||
Example for a design that is not closed for modification
|
||
|
||
public abstract class Shape {
|
||
public abstract String getShapeType();
|
||
}
|
||
public class Circle extends Shape {
|
||
private String shapeType = “Circle”;
|
||
private double radius;
|
||
private Point center;
|
||
|
||
}
|
||
|
||
public String getShapeType(){
|
||
return shapeType;
|
||
}
|
||
|
||
public class Square extends Shape {
|
||
private String shapeType = “Square”;
|
||
private double side;
|
||
private Point topLeftCorner;
|
||
|
||
}
|
||
|
||
public String getShapeType(){
|
||
return shapeType;
|
||
}
|
||
|
||
public void drawAllShapes(Shape[] shapes){
|
||
for(int i = 0; i < shapes.length; i++){
|
||
Shape shape = shape[i];
|
||
switch (shape.getShapeType()){
|
||
case "Circle":
|
||
drawCircle(shape);
|
||
break;
|
||
case "Square":
|
||
drawSquare(shape);
|
||
break;
|
||
default:
|
||
System.out.println("Unknown shape: "
|
||
+ shape.getShapeType());
|
||
}
|
||
}
|
||
}
|
||
|
||
What if we
|
||
add more
|
||
shapes?
|
||
|
||
SOLID PRINCIPLES
|
||
-
|
||
|
||
The Open Closed Principle (OCP)
|
||
-
|
||
|
||
Example how the previous design can be modified to be closed
|
||
for modifications
|
||
public abstract class Shape {
|
||
public abstract void draw();
|
||
}
|
||
public class Circle extends Shape {
|
||
private double radius;
|
||
private Point center;
|
||
|
||
}
|
||
|
||
public void draw(){
|
||
// code to draw here;
|
||
}
|
||
|
||
public class Square extends Shape {
|
||
private double side;
|
||
private Point topLeftCorner;
|
||
|
||
}
|
||
|
||
public void drawAllShapes(Shape[] shapes){
|
||
for(int i = 0; i < shapes.length; i++){
|
||
Shape shape = shape[i];
|
||
shape.draw();
|
||
}
|
||
}
|
||
|
||
public void draw(){
|
||
// code to draw here;
|
||
}
|
||
|
||
What if we
|
||
add more
|
||
shapes?
|
||
|
||
SOLID PRINCIPLES
|
||
-
|
||
|
||
The Liskov Substitution Principle (LSP)
|
||
-
|
||
|
||
Subclasses should be substitutable for their base classes
|
||
|
||
-
|
||
|
||
Example for a subclass that is not substitutable for its base
|
||
class
|
||
|
||
public class Rectangle {
|
||
private double width;
|
||
private double height;
|
||
|
||
}
|
||
|
||
public void setWidth(double width) { this.width = width; }
|
||
public void setHeight(double height) { this.height = height; }
|
||
public double getWidth() { return width; }
|
||
public double getHeight() { return height; }
|
||
|
||
public class Square extends Rectangle {
|
||
public void setWidth(double width){
|
||
super.setWidth(width);
|
||
super.setHeight(width);
|
||
}
|
||
public void setHeight(double height){
|
||
super.setWidth(height);
|
||
super.setHeight(height);
|
||
}
|
||
}
|
||
|
||
public void calArea(Rectangle r) {
|
||
r.setWidth(5);
|
||
r.setHeight(4);
|
||
double area = r.getWidth() * r.getHeight();
|
||
if (area != 20)
|
||
System.out.println("Unexpected area: " + area);
|
||
}
|
||
|
||
What if
|
||
calArea()
|
||
is passed a
|
||
square?
|
||
|
||
SOLID PRINCIPLES
|
||
-
|
||
|
||
The Liskov Substitution Principle (LSP)
|
||
-
|
||
|
||
LSP is an important feature of programs that conform to the Open Closed
|
||
Principle
|
||
|
||
-
|
||
|
||
When subclasses are completely substitutable for their base class, then
|
||
methods that use the base class can be substituted with impunity and the
|
||
subclasses can be changed with impunity
|
||
|
||
-
|
||
|
||
In our example
|
||
-
|
||
|
||
Geometrically, a square is a rectangle, but a square object is not a rectangle
|
||
object.
|
||
|
||
-
|
||
|
||
Behaviorally, a square is not a rectangle. The behavior of a square object is
|
||
not consistent with the behavior of a rectangle object.
|
||
|
||
-
|
||
|
||
For LSP to hold, subclasses must conform to the behavior that clients expect of
|
||
the base classes that they use.
|
||
|
||
SOLID PRINCIPLES
|
||
-
|
||
|
||
The Interface Segregation Principle (ISP)
|
||
|
||
-
|
||
|
||
Using many client specific interfaces is better than using one general
|
||
purpose interface.
|
||
|
||
-
|
||
|
||
If you have a class that has several clients, rather than loading the
|
||
class with all the methods that the clients need, create specific
|
||
interfaces for each client.
|
||
|
||
-
|
||
|
||
It is okay for a method to appear in more than one interface so that
|
||
separate clients can use the same method.
|
||
|
||
-
|
||
|
||
When interfaces between classes and existing clients change, consider
|
||
adding new interfaces for existing objects which can reduce
|
||
recompilation and redeployment.
|
||
|
||
SOLID PRINCIPLES
|
||
-
|
||
|
||
The Interface Segregation Principle
|
||
(ISP)
|
||
-
|
||
|
||
Example for a design that does not
|
||
follow ISP
|
||
|
||
-
|
||
|
||
Bloated interfaces can lead to
|
||
inadvertent couplings between clients
|
||
|
||
Client A
|
||
|
||
Service
|
||
Client A methods
|
||
|
||
Client B
|
||
…
|
||
|
||
Client B methods
|
||
…
|
||
|
||
Client N
|
||
|
||
Client N methods
|
||
|
||
Video
|
||
|
||
<<interface>>
|
||
VideoActions
|
||
|
||
that ought to be isolated otherwise
|
||
-
|
||
|
||
Bloated interfaces can be segregated
|
||
to prevent this coupling
|
||
|
||
Prime Video
|
||
|
||
playVideo()
|
||
getVideoPlayTime()
|
||
playRandomAd()
|
||
|
||
SOLID PRINCIPLES
|
||
-
|
||
|
||
The Dependency Inversion Principle (DIP)
|
||
-
|
||
|
||
Depend on abstractions. Do not depend on concretions
|
||
(implementations)
|
||
|
||
-
|
||
|
||
High level modules should not depend on low level modules.
|
||
Both should depend upon abstractions.
|
||
|
||
-
|
||
|
||
Abstractions should not depend upon details. Details should
|
||
depend upon abstractions
|
||
|
||
-
|
||
|
||
Every dependency in a design should target an interface or an
|
||
abstract class. No dependency should target a concrete class
|
||
|
||
SOLID PRINCIPLES
|
||
-
|
||
|
||
The Dependency Inversion Principle (DIP)
|
||
-
|
||
|
||
the idea …
|
||
|
||
SOLID PRINCIPLES
|
||
-
|
||
|
||
The Dependency Inversion Principle (DIP)
|
||
-
|
||
|
||
Example for how a copy program can work with any reader and
|
||
writer that implement the Reader and Writer interfaces. It is
|
||
no longer dependent on particular lower level modules!
|
||
|
||
public void copy() throws Exception {
|
||
Scanner scnr = new Scanner(System.in);
|
||
PrintStream ps = new PrintStream(new File("myFile"));
|
||
while (scnr.hasNext()) {
|
||
String line = scnr.nextLine();
|
||
ps.println(line);
|
||
}
|
||
}
|
||
|
||
public interface Reader {
|
||
public boolean hasLine();
|
||
public String getLine() throws Exception;
|
||
}
|
||
public interface Writer {
|
||
public void putLine (String s) throws Exception;
|
||
}
|
||
public void copy( Reader input, Writer output) throws Exception{
|
||
while (input.hasLine()) {
|
||
output.putLine(input.getLine()));
|
||
}
|
||
}
|
||
|
||
SOLID PRINCIPLES
|
||
-
|
||
|
||
The Dependency Inversion Principle (DIP)
|
||
-
|
||
|
||
Proper application of the Dependency Inversion Principle is
|
||
necessary for the creation of reusable frameworks.
|
||
|
||
-
|
||
|
||
It is important for the construction of code that is resilient
|
||
to change.
|
||
|
||
-
|
||
|
||
When abstraction is isolated from details, code is easier to
|
||
maintain.
|
||
|
||
IMPORTANT
|
||
|
||
In the industry, problem
|
||
solving often requires
|
||
interaction among many
|
||
colleagues. Rarely will you
|
||
be able to get everyone on a
|
||
project to agree on the
|
||
right approach to a
|
||
solution. Also, rarely will
|
||
any particular approach be
|
||
perfect. You'll often
|
||
compare the relative merits
|
||
of different approaches.
|
||
|
||
DO YOU HAVE ANY
|
||
QUESTIONS?
|
||
|
||
THANK
|
||
YOU!
|
||
|
||
@
|
||
|
||
hend.alkittawi@utsa.edu
|
||
|
||
By Appointment
|
||
Online
|
||
|
||
|