How I Learned
to Stop Worrying and Love OOP

Based on a true story...

  • We are building an Intranet Site
  • At the top of every page there is a Mega Nav
  • Mega Nav has links to important section of the site
  • While hovering a link, a menu appears with
    more options; a featured page for the section
    and list of columns with sub pages and
    some of their sub sub pages
  • There are 3 types of user, each one has special rules for the headers and how sub sub pages are fetched

How would you code it?

Are you thrilled
with your solution?

Here is how I would do it

public abstract class HeaderMenu {

  private final PageFetcher fetcher;
  private final User user;

  public HeaderMenu(final PageFetcher fetcher, final User user) {
    this.fetcher = fetcher;
    this.user = user;
  }

  public final MenuItem build() {
    Optional<Item> featured = getFeaturedItem(user);
    List<Link> headers = getHeaders(user);
    List<HeaderSubItem> columns = 
        header.stream()
              .map(link -> new HeaderSubItem(link, fetcher.getPages(link.getUrl()))
              .collect(Collectors.toList());
    return new MenuItem(featured, columns);
  }

  protected abstract Optional<Item> getFeaturedItem(User user);	
  protected abstract List<Link> getHeaders(User user);
}

HeaderMenu

  • It's an abstract class
  • Has two private attributes
  • Has one public method which is final
  • In it we create an algorithm to
    construct our Mega Nav, in part
    using 2 methods: getFeaturedItem()
    and getHeaders() which are abstract

Now we need
some subclasses
of HeaderMenu

public final class StoreUserHeaderMenu extends HeaderMenu {

  public StoreUserHeaderMenu(User user) {
    super(PageFetcher.RELATED_PAGES_FETCHER, user);
  }

  @Override
  proteced Optional<Item> getFeaturedItem(final User user) {
    // get latest article
  }

  @Override
  protected List<Link> getHeaders(final User user) {
    // Get About Us, Community, Department, 
    // Store and Human Resources links
  }
}
public class AdministrativeHeaderMenu extends HeaderMenu {

  public CorporateHeaderMenu(User user) {
    super(PageFetcher.RELATED_PAGES_FETCHER, user);
  }

  @Override
  proteced Optional<Item> getFeaturedItem(final User user) {
    // get upcoming staff meeting
  }

  @Override
  protected List<Link> getHeaders(final User user) {
    // Get About Us, Community, Department, 
    // Human Resources links -- No Store Header!
  }
}
public final class CorporateHeaderMenu extends AdministrativeHeaderMenu {

  public CorporateHeaderMenu(User user) {
    super(user);
  }

  @Override
  proteced Optional<Item> getFeaturedItem(final User user) {
    // get upcoming shareholder meeting
  }
}

A MegaNav to each user

  • Store users has one exclusive header, the sub pages belong to the page, and the sub sub pages are direct children of them, and their featured item is the latest article
  • Administrative users don't have a Store header, the sub pages are related to their area, and sub sub pages are pulled across the site, and their featured item is the next staff meeting
  • Corporate users are the same as Administrative but their featured item pulls sensitive information

What about PageFetcher?

public enum PageFetcher {
  CHILD_FETCHER {
    @Override
    public List<Link> getPages(URI path) {
      // pull direct pages under given path
    }
  }, 
  RELATED_PAGES_FETCHER {
    @Override
    public List<Link> getPages(URI path) {
      // pull pages that are related to page on given path
    }
  };
  public abstract List<Link> getPages(final URI path);
}

Two Strategies

  • There is an interface that defines the algorithm we need to fetch pages
  • There are 2 implementations
  • Since each implementation doesn't have an state, we are OK with having just one object for each one of them, so we use enums for that

How do we create
a HeaderMenu?

public final class HeaderMenuFactory {

  private HeaderMenuFactory() {
    throw new AssertionError("This class shouldn't be initialized");
  }

  public static HeaderMenu newInstance(final User user) {
    switch (user.getType()) {
    case STORE:
      return new StoreUserHeaderMenu(user);
    case ADMINISTRATIVE:
      return new AdministrativeHeaderMenu(user);
    case CORPORATE:
      return new CorporateHeaderMenu(user);
    default:
      throw new AssertionError();
    }
  }
}

How easy is to
understand this code?

How easy is to
modify this code?
Add a new HeaderMenu? Add a new Strategy?

I was certainly thrilled when I thought about
this solution!

Actually this was my face

To me, great code
is like great poetry!
and I can fall in love
with poetry!

Is OOP dead?

That article makes
the argument of the
death of OOP based, in
part, on this example...

We have this base class 

import java.util.ArrayList;
 
public class Array
{
  private ArrayList<Object> a = new ArrayList<Object>();
 
  public void add(Object element)
  {
    a.add(element);
  }
 
  public void addAll(Object elements[])
  {
    for (int i = 0; i < elements.length; ++i)
      a.add(elements[i]); // this line is going to be changed
  }
}

And we subclass it like this:

public class ArrayCount extends Array
{
  private int count = 0;
 
  @Override
  public void add(Object element)
  {
    super.add(element);
    ++count;
  }
 
  @Override
  public void addAll(Object elements[])
  {
    super.addAll(elements);
    count += elements.length;
  }
}

That class does
not behaves
as expected; added
elements are counted twice since the base class addAll method calls the add method in its implementation

The article argues that
since the subclass invariants
can be broken via an
implementation detail
of the superclass, then
Inheritence as a
concept is broken

Since Inheritance is a
pillar of OOP but is broken,
then OOP must be broken!
Article trashes other pillars
with "similar" arguments

The problem with those arguments is that
they all show examples
of bad OOP code

And it's code that
has been proved bad
for at least 15 years!

Effective Java has a whole recipe for that example, and it was published in 2001!

Item 16:
Favor composition
over inheritance

So we all agree
with something:
bad OOP is well... bad!

Hasta la vista, bad OOP!

Good OOP takes time!
And it's well beyond what you learned in a University

And you need more than one source to become good at Good OOP, and then you need practice!

Road Path to
Competency in OOP

  • Read Effective Java 2e
  • Read Clean Code
  • Read Head First Design Patterns
  • Learn about SOLID principles
  • Do quick programs to experiment
  • Learn your language well!

OOP is not dead!

OOP is alive and kickin'

  • C++: 30 years in the industry. Every 3 years they release a new version. Commitee members: Microsoft, Intel, Red Hat, Google, Qualcomm, etc.
  • Java: 20 years. Every 3 years release a new version. Companies involved in their evolution. Java Community Process Members: Oracle, IBM, Adobe, Apache, Red Hat, etc.
  • C#: 16 years. New version every 3 years.
  • Every new language that hit mainstream in the past 20 years has had an OOP flavor: JS, Ruby, Python, Scala, Grooovy

The Churn

Robert C. Martin

OO isn't dead. OO was never alive. OO is a technique; and a good one. Claiming it's dead is like claiming that a perfectly good screwdriver is dead. Saying goodbye to OO is like saying goodbye to a perfectly good screwdriver. It's waste!

[Saying that FP is better than OOP is] like saying that a hammer is better than a screwdriver. Functional programming is not "better" than Object Oriented programming. Functional Programming is a technique, and a good one, that can be used alongside Object Oriented programming.

We need to choose a language, or two, or three. A small set of simple frameworks. Build up our tools. Solidify our processes. And become a goddam professional!

Master OOP

  • Learn: SOLID, Design Patterns
  • Pick one industry-ready language and learn it well
  • See what tools are available and learn them well
  • Pick 2 or 3 other languages with big communities and learn them well, they will teach you things about OOP

How do we fix our example code?

Favor Composition over Inheritance

Dude, Composition and Inheritance are unrelated!

That's the point: Inheritance defines
an IS-A relationship,
it shouldn't be
used carelessly

public class Point {
   private double x;
   private double y;
   // more code
   public double getX() { return x; }
   public double getY() { return y; }
}

We need a class
Circle which will have
getX() and getY() methods

public class Circle extends Point {
  private double radius;
  public Circle(public double radius, double x, double y) {
    super(x, y);
    this.radius = radius;
  }
  // no need to write getters for x and y
  public double getRadius() {
    return radius;
  }
}

That's wrong!
a Circle
is not a Point!

You think is a silly example: it comes from
C++ How to Program 4e from Deitel & Deitel

Correct way to reuse Point's code?

public class Circle {
   private Point point;
   private double radius;
   public Circle(Point point, double radius) {
      this.point = point;
      this.radius = radius;
   }
   public double getX() {
     return point.getX(); // re use
   }
   public double getY() {
     return point.getX(); // re use
   }
}

So why bother to
use Inheritance?!

Because when it's correctly it produces thrilling code!

Remember our Header Menu example, the
code used Inheritance
to great effect!

Elegant
Inheritence

  • Template Method
  • Strategy
  • State
  • Decorator
  • Command
  • etc.

Here's what Effective Java says about how to have a collection that counts how many elements are added

public class ForwardingSet<E> implements Set<E> {
  private final Set<E> s;
  public ForwardingSet(Set<E> s) { this.s = s; }
  public void clear()               { s.clear();            }
  public boolean contains(Object o) { return s.contains(o); }
  public boolean isEmpty()          { return s.isEmpty(); }
  public int size()                 { return s.size(); }
  public Iterator<E> iterator()     { return s.iterator(); }
  public boolean add(E e)           { return s.add(e); }
  public boolean remove(Object o)   { return s.remove(o); }
  public boolean containsAll(Collection<?> c) { return s.containsAll(c); }
  public boolean addAll(Collection<? extends E> c) { return s.addAll(c); }
  public boolean removeAll(Collection<?> c) { return s.removeAll(c); }
  public boolean retainAll(Collection<?> c) { return s.retainAll(c); }
  public Object[] toArray() { return s.toArray();  }
  public <T> T[] toArray(T[] a) { return s.toArray(a); }
  @Override public boolean equals(Object o) { return s.equals(o); }
  @Override public int hashCode()    { return s.hashCode(); }
  @Override public String toString() { return s.toString(); }
}
public class InstrumentedSet<E> extends ForwardingSet<E> {
  private int addCount = 0;
  public InstrumentedSet(Set<E> s) {
    super(s);
  }
  @Override 
  public boolean add(E e) {
    addCount++;
    return super.add(e);
  }
  @Override 
  public boolean addAll(Collection<? extends E> c) { 
    addCount += c.size();
    return super.addAll(c);
  }
  public int getAddCount() {
    return addCount;
  }
}

OOP should
make the wrong thing, hard to do! (right?)

An API should be easy to use and hard to misuse. It should be easy to do simple things; possible to do complex things; and impossible, or at least difficult, to do wrong things.

-- Joshua Bloch

You can say the same about languages and paradigms!

But...

I could "argue"
that FP is broken

Because FP relies
on functions and composability; recursion
is one its key concepts

But recursion is "broken"

public int factorial(int n) {
   return n * factorial(n - 1);
}

You can "argue" that for such a simple function that code creates a lot of stack frames which makes the code expensive

public int factorial(final int n) {
   int f = 1;
   while(n-- > 0) {
      f *= n;
   }
   return f;
}

Then you can argue
that the non-FP code doesn't have those problems, so it's "superior"

But that's an stupid argument, because
I'm using recursion,
the wrong way

If I'm using FP I need
to learn the idioms and the culture that comes with it!

Same with OOP, learn the idioms, the principles and the Design Patterns.

private int loop(final int n, final int accumulator) {
   return n == 0 ? accumulator : loop(n - 1, accumulator * n);
}

public int factorial(final int n) {
   return loop(n, 1);
}

Recursion, the right way!

When you learn FP, you learn about tail-recursion. That's the proper way to do a factorial function

And all FP-heavy languages have tail-recursion optimizations that allow that code to run as fast as the other one

And no, Java doesn't have tail-recursion optimization

The point is:
good OOP trumps
over bad OOP,
good FP trumps
over bad FP.
Good OOP and good FP
are great and they can
be complementary!

Learn both and learn them well, without trying to shoehorn one paradigm
into the other

JVM is a rich ecosystem of awesome languages!
So you are already in "the right place"

Where Does the Hate Come From? 

"Object oriented programs are offered as alternatives to correct ones"
"Object-oriented programming is an exceptionally bad idea which could only have originated in California."

--Edsger W. Dijkstra

It's true that imperative programming (including OOP) goes against Mathematical correctness

But that doesn't mean
that is "superior"; however for
decades FP purist have frown
upon OOP programmers

And it seems some OOP programmers began to believe the FP purists

"I invented the term object-oriented, and
I can tell you I did
not have C++ in mind."

-- Alan Kay

"There are only two kinds of languages: the ones people complain about and the ones nobody uses."

-- Bjarne Stroustrup

You can argue that C++
is a better programming language than Lisp

It has a much, much bigger community and much, much lines of code has been written with it, specially in the industry

Big languages tend to
have newer versions frequently so the can become better. Attacking
a language because it
has bad parts is silly.

And doing a great language is hard. How many do you know that have been successful? If those language are so bad,
where are the ones
that are better?

Most of the quotes

are related to bad use

of a language, which

can be attributed to programmers that are not so good in the first place.

Don't be dragged into flame wars

Learn to Love Programming
and
Love to Learn

Programming

All kinds of it!

Q&A

Thanks!

@gaijinco

How I Stop worrying and learn to love OOP

By Carlos Obregón

How I Stop worrying and learn to love OOP

  • 1,828