Solid Principles of Object Oriented and Agile Design
Solid Principles of Object Oriented and Agile Design
• Learn:
• Clean Code Basics
• SOLID Principles of Software Design
• Dependency Management
• Professional Practices
SOFTWARE ROT
Messy Code
• Have you been significantly impeded
by messy code?
public Pair() { }
public Pair( Object first, Object second, Object third )
{
this.first = first;
this.second = second;
this.third = third;
}
http://www.pragmaticprogrammer.com/ppbook/extracts/no_broken_windows.html
What Happens To a
Design Over Time?
• Reveals Intent
• Provides insights into requirements
analysis.
• Adaptable
• Tolerates evolution of business need.
• Accepts new technologies without
undue cost.
Clean Code
Clean Code Separates
Levels of Detail
public int deduct(Money amount) {
if (amount == null ||
(balance.getAmount() <
amount.getAmount()))
return -1;
else {
int bal2 = balance.getAmount()*100+.5;
int amt2 = amount.getAmount()*100+.5;
balance.setAmount((bal2-amt2)/100.0);
if (balance.getAmount() == 0.0)
return 0; // is zero.
}
return 1;
}
Clean Code Tells a
Story
• It should be:
if (argumentValid(arg)) {
setArgument(arg);
validArgs.add(arg);
}
And Much More…
Professionalism
Understandable Code
Doesn't Just Happen
• You have to craft it.
• You have to clean it.
• You have to make a professional
commitment.
tep
s
g
bi
u s Test
ro
g e Rename
an
d OR Test
ne
O Move Method
Test
Replace Conditional with Polymorphism
Test
Extract Method
SOLID
History
• comp.object debates
• James O. Coplien.
Barbara Liskov.
Bertrand Meyer.
• NCARB Project.
• Designing Object Oriented C++
Applications using the Booch Method
• Agile Software Development: Principles
Patterns and Practices.
(SRP)
The Single
Responsibility
Principle
Single Responsibility
Principle
• A class should have one and
only one reason to change.
• One actor that is the
source of that change.
Single Responsibility
Principle
•A class should have one and
only one reason to change.
Employee
+ calculatePay() • CFO
+ save() • CTO
+ printReport() • COO
• How many
reasons does
Employee have
to change?
CM Collision
1. Tim checks out Employee in order to add a new
business rule to calculatePay()
2. Dean checks out Employee in order to change
the format of the EmployeeReport.
3. They check in at the same time and are forced to
do a merge.
+ calculatePay() EmployeeReport
+ calculateTaxes()
+ writeToDatabase()
PayrollReport
+ loadFromDatabase()
+ displayOnEmployeeReport()
+ displayOnPayrollReport() TaxReport
+ displayOnTaxReport()
Database
Deployment Thrashing
Employee
+ calculatePay()
+ getters
Employee Report
Generator
+ printReport(Employee)
Façade Pattern: A
Partial Solution
EmployeeFacade Employee
Bank
+ calculatePay() + calculatePay()
+ calculateTaxes() + calculateTaxes()
+ writeToDatabase()
+ loadFromDatabase()
+ displayOnEmployeeReport()
+ displayOnPayrollReport() EmployeeReportHelper
+ displayOnTaxReport()
+ displayOnEmployeeReport()
+ displayOnPayrollReport()
+ displayOnTaxReport()
EmployeeGateway
+ writeEmployee()
+ loadEmployee() Employee Payroll Tax
Report Report Report
Employee
• The Pure Truth
+ calculatePay(date)
+ isPayday(date)
• The intent is exposed.
The Code Tells the
Story
• This code tells it’s own story.
• It doesn’t need comments.
• There are few obscuring details.
• The Employee object has hidden most
details behind an abstract interface.
• This is what Object Oriented Design is
all about.
Symptom: Switching on
Type
switch (employee.getEmployeeType()) {
case COMMISSIONED:
payToday = DateUtils.isOddFriday(payDate);
break;
case SALARIED:
payToday = DateUtils.isLastDayOfMonth(payDate);
break;
case HOURLY:
payToday = DateUtils.isFriday(payDate);
break;
}
• Not Fragile
• Payroll can be deployed
with as many or as few
Pay-Classifications as
desired.
Salaried Hourly Commissioned
• Mobile
Misuse: Endless
Abstraction
Notes on Endless
Abstraction
Inheritance == BEHAVES-AS-A
Example
Run Payroll «interface»
Employee
Transaction Pay
Classification
+ execute() + calculatePay()
+ calculatePay()
Add Timecard
Transaction
Run Payroll
Salaried Hourly Commissioned
Transaction
Classification Classification Classification
* *
Timecard Sales Receipt
Violates
Substitutability
Change Salary «interface»
Employee
Transaction Pay Classification
+ calculatePay() + calculatePay()
Add Sales Receipt + add(:Timecard) + add(:Timecard)
Transaction + add(:SalesReceipt) + add(:SalesReceipt)
+ setSalary(:Money) + setSalary(:Money)
Add Timecard
Transaction
Run Payroll
Transaction
Salaried Hourly Commissioned
Classification Classification Classification
+ calculatePay() + calculatePay() + calculatePay()
+ add(:Timecard) + add(:Timecard) + add(:Timecard)
+ add(:SalesReceipt) + add(:SalesReceipt) + add(:SalesReceipt)
+ setSalary(:Money) + setSalary(:Money) + setSalary(:Money)
Hourly
Classification
+ calculatePay()
+ add(:Timecard)
*
Timecard
try {
((HourlyPayClassification)payClass).addTimecard(timecard);
} catch (ClassCastException e) {
throw new NotHourlyEmployeeException(employee);
}
}
} • Is this an LSP violation?
• How about OCP?
Wrap Up
• LSP is a prime enabler of OCP.
• Substitutability supports
IS-A extensibility without
modification.
• Polymorphism demands
substitutability.
BEHAVES-
• Derived classes must uphold the
AS-A
contract between their base class
and its clients!
(ISP)
The Interface
Segregation
Principle
The Interface
Segregation Principle
• Clients should depend only on methods they call.
Fat Interfaces
Hourly Salaried Commissioned
Employee Employee Employee
Controller Controller Controller
EmployeeGateway
+ addTimeCard(Employee, Timecard)
+ addSalesReceipt(Employee, SalesReceipt)
+ findEmployee(id)
+ save(Employee)
Y Immo
Controller Controller Controller
GID I T bility
R I
EmployeeGateway
+ addTimeCard(Employee, Timecard)
+ addSalesReceipt(Employee, SalesReceipt)
+ findEmployee(id)
+ save(Employee)
Employee
Gateway
+ addTimeCard(Employee, Timecard)
+ addSalesReceipt(Employee, SalesReceipt)
+ findEmployee(id)
+ save(Employee)
Notes on ISP
• EmployeeGateway still has a fat
interface.
• But clients don’t know this because all
dependencies point AWAY from it.
• Phantom dependency has been
eliminated.
• Changing existing interfaces or
adding new interfaces do not impact
other interfaces, and therefore do not
impact other clients.
Client Groupings
Salaried Commissioned
Hourly Salaried Commissioned
Employee Employee
Employee Employee Employee
Controller Controller
Controller Controller Controller
Employee
Gateway
+ addTimeCard(Employee, Timecard)
+ addSalesReceipt(Employee, SalesReceipt)
+ findEmployee(id)
+ save(Employee)
When You Can't Change
the Fat interface
Hourly
Employee
Controller
Hourly
Employee Gateway Employee
Adapter Gateway
Interfaces
Fat Interfaces Match Client
Needs
(DIP)
The Dependency
Inversion Principle
The Dependency
Inversion Principle
Payroll
JDBC252
+ payDay()
Problems with
Procedural Designs
• How do we test Payroll?
• How do we test the clients of Payroll?
• How do we prevent Payroll from
being redeployed when the SQL
changes?
• How do we get this code to tell its
story?
Payroll
JDBC252
+ payDay()
OO Design
public class Payroll {
private EmployeeGateway employeeGateway;
findAll «interface»
Payroll EmployeeGateway
+ payDay() + findAll()
Control Compile-time
Flow Dependencies
JDBC
Employee
• Control flow opposes source Gateway
code dependencies.
Inverting a Problematic
Dependency
• The procedural case has
a problematic Uninverted Inverted
Approach Approach
dependency. interface
Payroll Payroll
• Payroll depends on
JDBC
implementation
• Making Payroll
independent of JDBC. JDBC
Solutions Provided by
DIP
• We can test Payroll and its clients by Payroll
FedTaxCalculator
Withholding 2007
Statement Tax Tables 2007
2007 + calculateTax()
Solution: Factor out an
Interface
FedTaxCalculator
Withholding 2006
Statement Tax Tables 2006
2006 + calculateTax()
«interface»
FedTaxCalculator
+ calculateTax()
FedTaxCalculator
Withholding 2007
Statement Tax Tables 2007
2007 + calculateTax()
Depending On Concrete
Classes
• Sometimes there is no problem.
• String, HashMap, etc.
• They are non-volatile.
• They are dependency dead-ends.
• Sometimes there is no choice.
• Third party packages
• Concrete classes that are heavily used.
• You can’t change them to add
interfaces.
• Keyword: new.
Solution: Adapter [GOF]
• Limits the dependency on classes that
are hard to change.
• Lets you define the ideal interface for
your needs.
«interface»
Third-Party
Payroll Tax Table
Tax Table
Adapter
«creates»
«interface»
Employee
Employee
Factory
+ makeSalaried
+ makeCommissioned
+ makeHourly
Employee
Salaried Hourly Commissioned
Factory
Employee Employee Employee
Implementation
«creates»
Solution: Dynamic
Factory
• Solves the problem.
• But only by removing compile-time
type safety.
HR
«lnterface»
Employee
Factory Employee
+ make(string)
If statements Employee
comparing Salaried Hourly Commissioned
Factory
strings Employee Employee Employee
Implementation
Review: Dependency
Inversion
• In procedural designs source code
dependencies flow with control.
• This makes it easy to add functions to
existing data structures without
changing the data structures.
• In OO designs dependencies can be
inverted so that source code
dependencies flow against control.
Wrap Up
• High-Level policy should not depend
on details.
• Details should implement abstractions.
Depend Depend
on on
Concrete Abstractions
Classes
Practices
Practices that Fight Rot
• Simple Design
• Automated Testing
• Test-Driven Development
• Refactoring
• Teamwork
Rules of Simple Design
• In order of importance:
• Keep all tests passing.
• Eliminate duplication (DRY, once and
only once).
• Reveal developer’s intent.
• Minimize the number of classes and
methods.
Automated Testing
tep
s
g
bi
u s Test
ro
g e Rename
an
d OR Test
ne
O Move Method
Test
Replace Conditional with Polymorphism
Test
Extract Method
Pair Programming
• Two programmers, one workstation
• Share knowledge and skill
• Continuous code/design review
• Opportunistic
• Optional
• Encouraged
Collective Ownership:
email: [email protected]
Website: cleancoder.com
Videos: cleancoders.com
Thank You!