The call to Adventure:
SOLID principles

The Ordinary World

Types

Hero's Armor

OOP should be
all about types!

OOP design is awesome when is well done,
and that has usually
to do with types!

Encapsulation

Hero's Shield

Encapsulation

  • Access modifiers
  • Types has behavior

You can hide implementation details under access modifiers

and expose, what you need, with methods.

Fraction oneHalf = new Fraction(1, 2);
Fraction twoThirds = new Fraction(2, 3);
Fraction sum = oneHalf.sum(twoThirds);

What do you think are the attributes of Fraction?
Two int's? A two-sized
array of int? A map?

You don't need to care!
You only care about Fraction's API

You are free to change
the internals of your
class without
breaking client code.

Also classes
have invariants.
Having encapsulation makes them easier to maintain because you
have greater control
of an object's state.

Regarding access
control the golden rule is: make everything as restricted as possible.

Inheritence

Hero's Sword

Inheritance is a
mechanism to express
an IS-A relationship ...

... which peripherally let you re use code, but that's just a nice coincidence.

We would return to Inheritance later.

Polymorphism

Hero's Magic

Polymorphism is
the feature that let's
you have a reference
of a type T refer to any object of a subtype of T

List<String> data = new ArrayList<>();
Ship s = new Battleship();
Link cta = new EmailLink();
DateFormat formatter = new SimpleDateFormat();

The type of the reference, left, doesn't need to be the same of the object, right.

As long as the type of the object
is in the inheritance tree of the
type of the reference

Polymorphism is
the key to the Kingdom!

public void foo(List<String> foo) {
  // this method will work with any
  // implementation of List, even
  // if it hasn't been created
}
public List<String> foo() {
  // this method will return a List
  // but you shouldn't worry about which. 
  // In fact it can change in the future!
  // But it'll always be a List
}
public class Foo {
   // This attribute is a List
   // but it's implementation can change
   private List<String> data;
}
public class PersonService {

   private PersonDAO dao;

   public PersonService(final PersonDAO dao) {
      // I can inject the implementation I need!
      this.dao = dao; 
   }
}

How do you define a
Type regardless of its implementation?

Use Interfaces for types so you have the flexibility that brings Polymorphism.

If you have an interface
 and more than one implementation, move common code to an Abstract Class.

Tools of the Hero

  • Types
  • Encapsulation
  • Inheritance
  • Interfaces,
    Abstract Classes
  • Polymorphism

Are you ready for
the adventure?

Single Responsibility Principle - SRP

A class should only
have one responsibility
to change

A class should have
high cohesion

Every method related to one functionality should be in the same class. 

Methods related
to different
functionalities should
be on their own class.

public class ContactServlet extends HttpServlet {

   public void doGet(HttpServletRequest req, HttpServletResponse res) {
      String id = req.getParameter("id");
      Contact contact = this.fetchFromDatabase(id);
      String message = String.format("%s - %s", id, contact);
      res.getWriter().println(message);
   }
}

Reasons to change

  • Changes in parameters
  • Changes in the DB
  • Changes in the output format
public class ContactServlet extends HttpServlet {

   public void doGet(HttpServletRequest req, HttpServletResponse res) {
      String id = re.getParameter("id");
      Contact contact = service.findById(id);
      res.setAttribute("contact", contact);
      RequestDispatcher dispatcher = getServletContext()
         .getRequestDispatcher(CONTACT_VIEW);
      dispatcher.forward(request, response);
   }
}

Reason to change

  • Changes in the parameters
public class Fraction {
   private int numerator;
   private int denominator;
   public Fraction(int numerator, int denominator) {
      final int GCF = gcf(numerator, denominator);
      this.numerator = numerator / GCF;
      this.denominator = denominator / GCF;
   }
   private static int gcf(int a, int b) {
      return b == 0 ? a : gcf(b, a % b);
   }
   public int getNumerator() {
      return numerator;
   }
   public int getDenominator() {
      return denominator;
   }
}

Does it break SRP?

Yes!

Reason to Change

  • Changes in Fraction implementation
  • Changes in gcf() implementation
public class Fraction {
   private int numerator;
   private int denominator;
   public Fraction(int numerator, int denominator) {
      final int GCF = 
         ArithmeticOperations.gcf(numerator, denominator);
      this.numerator = numerator / GCF;
      this.denominator = denominator / GCF;
   }
   public int getNumerator() {
      return numerator;
   }
   public int getDenominator() {
      return denominator;
   }
}
public class final ArithmeticOperations {
   private ArithmeticOperations() {
      throw new AssertionError("This class shouldn't be instantiated");
   }

   public static int gcf(int a, int b) {
      return b == 0 ? a : gcf(b, a % b);
   }
}

Beware of static
methods because they usually break OOP

If you can think of
a Type where a static method can be, it usually means it should be!

Cohesion also has to do with having all functionality in the appropriate Type.

public class final FractionOperations {
   private FractionOperations() {
      throw new AssertionError("This class shouldn't be instantiated");
   }

   public static Fraction sum(Fraction this, Fraction that) {
      final int LCM = ArithmeticOperations.lcm(this.getDenominator(), that.getDenominator());
      final int numerator = LCM / this.getDenominator() * that.getNumerator()
            + LCM / that.getNumerator() * this.getNumerator();
      return new Fraction(numerator, LCM);
   }
}

Adding 2 fractions has
a lot to do with the implementation, isn't?

If I'm a fraction I
should know how
to add me another one.

public class Fraction {
   // code

   public Fraction sum(Fraction that) {
      final int LCM = ArithmeticOperations.lcm(this.denominator, that.denominator);
      final int numerator = LCM / this.denominator * that.numerator
            + LCM / that.denominator * this.numerator;
      return new Fraction(numerator, LCM);
   }
}

Beware of God Classes, see how methods related to attributes of a class.

If a method doesn't use much of the attributes of a class then maybe it should be in a new Type.

Open/Closed Principle - OCP

A class should be open
for extension but closed
for modification

If you have existing
code that's working,
how can you extend it without breaking
existing functionality?

Unit Tests help, but you can design in a way that you don't depend on them!

How can you reuse code?

public class Point {

   private double x;
   private double y;

   public Point(double x, double y) {
      this.x = x;
      this.y = y;
   }

   public double distance(Point p) {
      return Math.sqrt(Math.pow(x - p.x, 2)
             + Math.pow(y - p.y, 2)) 
   }
}

If I have a simulation
traffic I could need a
Type vehicle that occupies a place in a grid system.

public class Vehicle {
   // more code
   private Point location;

   // more code

   public double distance(Vehicle v) {
      // reuse of distance code from Point
      return location.distance(v.location);
   }
}

Delegation: implementing a method by just calling a method on another object

Delegation can be expanded and in fact a lot of Design Patterns use it!

The easiest way to apply OCP is with Design Patterns: Decorator, State / Strategy, Template, etc.

Another small thing is to be wary of using instanceof

public Square implements Shape {
   // code
   public double getLength() {
      return this.length;
   }
}
public Circle implements Shape {
   // code
   public double getRadius() {
      return this.radius;
   }
}
public double sumOfAreas(List<Shape> shapes) {
   double total = 0.0;
   for (Shape s : shapes) {
      if (s instanceof Square) {
         Square sq = (Square) s;
         total += sq.getLength() * sq.getLength();
      } else if (s instanceof Circle) {
         Circle c = (Circle) s;
         total += c.getRadius() * c.getRadius() * Math.PI;
      }
   }
   return total;
}

If I add a new shape,
then the sumOfAreas
will need to add a
new instanceof test

I should use Types!

public interface Shape {
   // code
   double getArea();
}
public class Square implements Shape {
   // code
   public double getArea() {
      return this.length * this.length;
   }
}
public class Circle implements Shape {
   // code
   public double getArea() {
      return this.radius * this.radius * Math.PI;
   }
}
public double sumOfAreas(List<Shape> shapes) {
   double total = 0.0;
   for (Shape s : shapes) {
      total += s.getArea();
   }
   return total;
}

Now new Shapes can
be created and sumOfAreas() will still work

instanceof has its uses
but try to use polymorphism first

Liskov Substitution Principle - LSP

A method that expects a type T should work with a type S if its a subtype of T

In the end what it means
is that inheritance is powerful but you
need to not abuse it.

Inheritance always define an IS-A relationship, don't use it for code reuse.

public class Point {

   private double x;
   private double y;

   public Point(double x, double y) {
      this.x = x;
      this.y = y;
   }

   public double distance(Point p) {
      return Math.sqrt(Math.pow(x - p.x, 2)
             + Math.pow(y - p.y, 2)) 
   }
}
public class Vehicle extends Point {
   // Vehicle now has a distance() method
}

A Vehicle IS NOT a Point

What could go wrong? There's a strong coupling between classes.

If you are considering Inheritance, try to use Delegation first, if it
doesn't work then use it.

At school we learn that
a Square is a special
case of a Rectangle.

public class Rectangle {
   private double a;
   private double b;

   public double getA() {
      return a;
   }

   public void setA(double a) {
      this.a = a;
   }

   public double getB() {
      return b;
   }

   public void setB(double b) {
      this.b = b;
   }
}
public void foo(Rectangle r) {
   r.setA(5.0);
   r.setB(4.0);
   r.getA(); // it's 5.0
}
public class Square extends Rectangle {
   // Square has two attributes, but
   // it should have only one right?
   @Override
   public void setA(double a) {
      this.a = a;
      this.b = a; // b and a should be equal
   }
   @Override
   public void setB(double b) {
      this.b = b;
      this.a = b; // b and a should be equal
   }
}
Square s = new Square();
foo(s);

* * *

public void foo(Rectangle r) {
   r.setA(5.0);
   r.setB(4.0); // a also set a to 4.0
   r.getA(); // it's not longer 5.0!
}

If you use inheritance and you need to do weird things in your code, you are doing it wrong!

Inheritance, when done correctly is cool!

Servlet, GenericServlet, HttpServlet, MyServlet

Don't satanize Inheritance, learn when and how
to use it.

Interface Segregation Principle - ISP

No client should
be forced to depend
on methods it
does not use

In Java there's a TreeSet class which is: a collection, a set, sorted, which let's you do range searches.

There are 4 interfaces for each of those features: Collection, Set,
SortedSet, NavigableSet

Collection has 13 methods.
Set doesn't adds any.
SortedSet adds 6.
NavigableSet adds 14.

Don't have 1 big fat interface, because implementers
will probably have
empty methods.

Dependency Inversion
Principle - DIP

  1. High-level modules shouldn't depend on low level modules. Both should depend on abstraction.

  2. Abstractions should not depend on details. Details should depend on abstractions.

In OOP, abstractions
usually means Interface.

Return values of
public methods
should be Interfaces.

Parameters of public methods/constructors
should be Interfaces.

Attributes of classes
should be interfaces (*)

When I say "should" I
don't mean "they always have to", but if you don't follow that advice learn what you are missing

Other useful Non-SOLID principles

Don't Repeat Yourself - DRY

Every piece of knowledge must have a single, unambiguous, authoritative representation within
a system

If I want to make a
fix / change for some functionality, there should be only one place to do it.

Duplication can range from obvious to non-trivial

public GridList(SlingHttpServletRequest request) {
    ValueMap properties = request.getResource().adaptTo(ValueMap.class);

    String leftLink = properties.get("leftlink", "");
    String leftTitle = properties.get("lefttitle", "");            
    this.leftGridBlock = new GridBlockDialog(leftLink, leftTitle);

    String rightLink = properties.get("rightlink", "");
    String rightTitle = properties.get("righttitle", "");
    this.rightGridBlock = new GridBlockDialog(rightLink, rightTitle);
}
public GridList(SlingHttpServletRequest request) {
  ValueMap properties = request.getResource().adaptTo(ValueMap.class);

  GridBlockDialog[] blocks = new GridBlockDialog[2];
  String[] values = {"left", "right"}; 

  for (int i = 0; i < blocks.length; ++i) {
    String link = properties.get(values[i] + "link", "");
    String title = properties.get(values[i] + "title", "");
    String desc = properties.get(values[i] + "description", "");                    
    blocks[i] = new GridBlockDialog(link, title, description);
  }

  this.leftGridBlock = blocks[0];
  this.rightGridBlock = blocks[1];
}

Try to use arrays to
your advantage

public GridList(SlingHttpServletRequest request) {
  ValueMap properties = request.getResource().adaptTo(ValueMap.class);

  this.leftGridBlock = createDialog(properties, "left");
  this.rightGridBlock = createDialog(properties, "right");
}

Or use functions

You Aren't Gonna Need It - YAGNI

Only code what you need! Don't code something because you think
you'll need it.

You can waste a lot of
time doing something
that nobody will ever use.

Be very careful about setters! There's some frameworks / libraries that need them, and that's it.

Adventurer's
Tricks of the Trade

  • Create new Types

  • Use Interfaces to define Types

  • Don't overuse static

  • Don't overuse instanceof

  • Learn Design Patterns

  • Use Inheritance only
    for IS-A relationships

  • Prefer Delegation over Inheritance

  • Use Interfaces for Return Values and Parameters of public method. Also for attributes

  • Avoid duplication

  • Don't code something unless you need it now!

  • Don't be afraid
    to ask questions!

Q&A

Thanks!

@gaijinco

The call to Adventure: SOLID principles

By Carlos Obregón

The call to Adventure: SOLID principles

  • 1,469