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,904