Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The SOLID principles of OO

The SOLID principles of OO

or the rant* of post-OO programmer
A simple summary of SOLID principles w/ very simple examples in Java

-- http://peel.github.io

Piotr Limanowski

May 26, 2010
Tweet

More Decks by Piotr Limanowski

Other Decks in Programming

Transcript

  1. The SOLID principles of OO
    or the rant* of post-OO developer
    Piotr Limanowski
    KAOS / Burncity
    not an angry, bemused or dissapointed rant - just a rant

    View Slide

  2. Let’s roll... why even bother?
    Software rots like bad meat:
    Rigidity - cascading changes
    Fragility - problems in new areas with no direct relations
    Immobility - no possibility of reuse of a „reusable” code
    Complexity - overdesign; complex to understand
    Repetition - duplication, oh gawd
    Opacity - not human redable
    Broken Window Theory
    Broken windows drives a building into a smahsed and abandoned derelict

    View Slide

  3. Let’s roll... the SOLID Principles
    Single Responsibility
    Open Closed
    Liskov Substitution
    Interface Segregation
    Dependency Inversion

    View Slide

  4. SINGLE RESPONSIBILITY
    JUST BECAUSE YOU CAN, DOESN’T MEAN YOU SHOULD

    View Slide

  5. There is one and only one reason to change a class
    President / Gov’t / Local
    Divide ’n Conquer: logical mergesort
    Divide problem to subproblems
    Divide subproblems to subsubproblems
    Divide until the solution is a single class

    View Slide

  6. INVALID
    public abstract class LibraryUser{
    double balance;
    ArrayList rent;
    void payDue(double amount);
    void checkIn(Book b){}
    void checkOut(Book b){}
    }
    VALID
    public class LibraryUser{
    private LibraryAccount account;
    public void LibraryUser(LibraryAccount account){
    this.account=account;
    }
    public void checkOut(Book book){
    account.prolong();
    }
    public void checkIn(Book book){}
    public void upgradeAccount(LibraryAccount account){
    this.account=account;
    }
    }
    interface LibraryAccount{
    public void checkOut(Book book);
    public void checkIn(Book book);
    public void upgradeAccount(LibraryAccount account);
    }
    public class PaidAccount implements LibraryAccount{
    public void checkOut(Book book){payDue()(...)}
    public void checkIn(Book book){}
    public void upgradeAccount(LibraryAccount account){}
    public void payDue(double due);
    }
    public class FreeAccount implements LibraryAccount{
    public void checkOut(Book book){}
    public void checkIn(Book book){}
    public void upgradeAccount(LibraryAccount account){}
    }
    STRATEGY
    <>
    DETAILED STRATEGY
    <>
    DETAILED STRATEGY
    <>
    CONTEXT
    <>

    View Slide

  7. Divide and conquer design
    Strategy Pattern
    Template Pattern
    If a single class (method) answers too many „Zachman” questions
    (how, where, what, why etc.), it’s a good indication that this class
    has too many responsibilities.
    THERE IS ONE AND
    ONLY ONE
    REASON TO CHANGE A CLASSFOR [email protected]#$
    SAKE!

    View Slide

  8. OPEN CLOSED
    BRAIN SURGERY IS NOT NEEDED WHEN PUTTING ON A
    HAT.

    View Slide

  9. Software entities (classes, modules, functions)
    should be OPEN for EXTENSION, CLOSED for
    MODIFICATION
    Divide ’n Conquer: logical mergesort
    Divide problem to subproblems
    Divide subproblems to subsubproblems
    Divide until the solution is a single class

    View Slide

  10. INVALID
    public class LoanApprovalHandler{
    public void approveLoan(PersonalValidator v){
    if(validator.isValid()){}
    }
    }
    public class PersonalLoanValidator{
    public boolean isValid(){}
    }
    public class LoanApprovalHandler{
    public void approvePersonalLoan(PersonalLoanValidator v){
    if(validator.isValid()){}
    }
    public void approveVehicleLoan (VehicleLoanValidator v){
    if(validator.isValid()){}
    }
    //keep them comming
    }
    public class PersonalLoanValidator{
    public boolean isValid(){}
    }
    public class VehicleLoanValidator{
    public boolean isValid(){}
    }
    VALID
    public abstract class Validator{
    public boolean isValid();
    }
    public class PersonalLoanValidator extends Validator{
    public boolean isValid(){}
    }
    public class VehicleLoanValidator extends Validator{
    public boolean isValid(){}
    }
    public class LoanApprovalHandler{
    public void approveLoan(Validator validator){
    if ( validator.isValid()){}
    }
    }

    View Slide

  11. Making all member variables private so that the other
    parts of the code access them via the getters not
    directly.
    Avoiding typecasts at runtime - makes the code fragile
    and dependent on the classes under consideration,
    which means any new class might require editing the
    method to accommodate the cast for the new class.
    YOU SHOULD BE ABLE TO
    EXTEND
    A CLASSES’ BEHAVIOUR, WITHOUT
    MODIFYING IT
    HOLY RUNAWAY CODE, BATMAN!

    View Slide

  12. LISKOV SUBSTITUTION
    IF IT LOOKS LIKE A DUCK AND QUACKS LIKE A DUCK
    BUT NEEDS BATTERIES, YOU PROBABLY HAVE THE
    WRONG ABSTRACTION.

    View Slide

  13. Let q(x) be a property provable about objects x of
    type T. Then q(y) should be true for objects y of
    type S where S is a subtype of T.
    Changing the implementation doesn’t affect client’s
    code

    View Slide

  14. INVALID
    public class App {
    ! public static void main(String[] args) {
    ! ! // kod użytkownika
    ! ! String abc = "abc";
    ! ! TextFilter filter = new AFilter();
    ! ! System.out.println(filter.procced(abc));
    ! ! filter = new BFilter();
    ! ! System.out.println(filter.procced(abc));
    ! }
    }
    // nasz kod
    interface TextFilter {
    ! public String procced(String text);
    }
    class AFilter implements TextFilter {
    ! public String procced(String text) {
    ! ! return text.replaceAll("a", "");
    ! }
    }
    class BFilter implements TextFilter {
    ! public String procced(String text) {
    ! ! return text.replaceAll("b", "");
    ! }
    }
    VALID
    public class App {
    ! public static void main(String[] args) {
    ! ! // kod użytkownika
    ! ! String abc = "abc";
    ! ! TextFilter filter = new AFilter();
    ! ! System.out.println(filter.procced(abc));
    ! ! filter = new BFilter();
    ! ! System.out.println(filter.procced(abc));
    ! }
    }
    interface TextFilter {
    ! @Deprecated
    ! public String procced(String text);
    ! public String proccedAndThrow(String text) throws Exception;
    }
    class AFilter implements TextFilter {
    ! @Deprecated
    ! public String procced(String text) {
    ! ! return text.replaceAll("a", "");
    ! }
    ! public String proccedAndThrow(String text) throws Exception {
    ! ! return text.replaceAll("a", "");
    ! }
    }
    class BFilter implements TextFilter {
    ! @Deprecated
    ! public String procced(String text) {
    ! ! return text.replaceAll("b", "");
    ! }
    ! public String proccedAndThrow(String text) throws Exception {
    ! ! return procced(text);
    ! }
    }

    View Slide

  15. Factoring
    DERIVED CLASSES
    MUST BE
    SUBSTITUTABLE
    FOR THEIR BASE CLASSES

    View Slide

  16. INTERFACE SEGREGATION
    YOU WANT ME TO PLUG THAT IN WHERE?

    View Slide

  17. Let q(x) be a property provable about objects x of
    type T. Then q(y) should be true for objects y of
    type S where S is a subtype of T.
    Changing the implementation doesn’t affect client’s
    code
    Number of interfaces (implementation is slower than
    inheritance)
    Factory pattern
    Unit testing
    Documentation

    View Slide

  18. INVALID
    public interface Worker {
    ! public void work();
    public void receivePaycheck();
    }
    public interface Manager {
    ! public void addWorker(Worker w);
    ! public void manage();
    }
    public interface Accountant {
    ! public void enlist(Worker w);
    ! public void submitPaycheck();
    }
    VALID
    public interface Worker {
    public void worker();
    }
    public interface PaidWorker {
    public void receivePaycheck();
    }
    public interface Manager {
    ! public void addWorker(Worker w);
    ! public void manage();
    }
    public interface Accountant {
    ! public void enlist(Worker w);
    ! public void submitPaycheck();
    }
    public interface WorkingMan extends PaidWorker, Worker {
    }

    View Slide

  19. Think in terms of the client not number of lines
    Factoring
    MAKE
    FINE GRAINED
    INTERFACES THAT ARE
    NT SPECIFIC
    SIMPLE AS THAT:

    View Slide

  20. DEPENDENCY INVERSION
    SO... YOU’RE IT SHOULD BE CONNECTED DIRECTLY?

    View Slide

  21. High level modules should not depend upon low level modules. Both
    should depend upon abstractions.
    Abstractions should not depend upon details. Details should depend
    upon abstractions.
    modules? abstractions? in Java? C++? or... is it AOP modules? DDD?
    No more spiderwebs = bad design
    Hard to write unit tests = bad design
    SRP
    lots of dependencies
    class is like another class that does the same but produces different output
    Examples:
    Battery chargers
    LogFiles gathering

    View Slide

  22. INVALID
    public class OrderProcessor{
    public double CalculateTotal(Order order){
    double itemTotal = order.getItemTotal();
    double discount =
    DiscountCalc.calculateDiscount(order);
    double tax = 0.0;
    if (order.country == "US"){
    tax = findTaxAmount(order);
    } else if (order.country == "UK"){
    tax = findVatAmount(order);
    }
    double total=itemTotal-discount+tax;
    return total;
    }
    private double findVatAmount(Order order){
    return 10.0;
    }
    private decimal FindTaxAmount(Order order){
    return 10.0;
    }
    VALID
    public interface DiscountCalculator{
    double calculateDiscount(Order order);
    }
    public interface TaxStrategy{
    double findTaxAmount(Order order);
    }
    public class OrderProcessor{
    private static final DiscountCalculator discountCalculator;
    private static final TaxStrategy taxStrategy;
    public OrderProcessor(DiscountCalculator discountCalculator,
    TaxStrategy taxStrategy){
    this.taxStrategy = taxStrategy;
    this.discountCalculator = discountCalculator;
    }
    public double CalculateTotal(Order order){
    double itemTotal=order.getItemTotal();
    double discountAmount=DiscountCalc.CalculateDiscount(order);
    double tax=taxStrategy.findTaxAmount(order);
    double total=itemTotal-discountAmount+taxAmount;
    return total;
    }
    }
    public class DiscountCalculatorAdapter implements DiscountCalculator{
    public double CalculateDiscount(Order order){
    return DiscountCalculator.CalculateDiscount(order);
    }
    }
    public class USTaxStrategy implements TaxStrategy{
    public decimal FindTaxAmount(Order order){
    }
    }
    public class UKTaxStrategy implements TaxStrategy{
    public decimal FindTaxAmount(Order order){
    }
    }
    ■ How to calculate the item total
    ■ Finding the discount calculator and finding the
    discount
    ■ Knowing what country codes mean
    ■ Finding the correct taxing method for each
    country code
    ■ Knowing how to calculate tax for each country
    ■ Knowing how to combine all of the results into
    the correct final total
    ADAPTER
    <>

    View Slide

  23. Always isolate the ugly stuff
    IoC containers:
    Google Guice
    Spring
    etc.
    One way to think of this is that lower level modules provide a service to
    higher level modules. The higher level modules specifies the interfaces for
    that service and the lower level module provides that service.
    DEPEND ON
    ABSTRACTIONS
    NOT NEVER NAY
    ONCRETIONS
    THE RULE IS:

    View Slide

  24. FIN
    FORGET THAT!
    FUNCTIONAL
    PROGRAMMING
    IST KRIEG(COMING UP
    NEXT)

    View Slide