O is for Open/Closed Principle

This is part 2 of the SOLID Principles for Android Developers series. If you missed part 1 or aren’t familiar with what the SOLID principles are, check out Part 1, where we intro SOLID and discuss the Single Responsibility Principle.

The Open/Closed Principle

The ‘O’ in the SOLID mnemonic acronym is the Open/Closed principle. The Open/Closed principle states:

software entities (classes, modules, functions, etc) should be open for extension, but closed for modification

While this sounds simple, it’s also one of those phrases that if you say it enough times in your head you eventually start confusing yourself. The basic synopsis is that you should strive to write code that doesn’t have to be changed every time the requirements change. In Android, we’re using Java, so this principle can be implemented with inheritance and polymorphism. 1

An Example of the Open/Closed Principle

The example below is a common industry sample of what the Open/Closed principle is and how to implement it. The example below is very simple and makes visualizing the Open/Closed principle very easy.

Let’s assume that you have an application that has a requirement to calculate area for any given shape. Whilst simple, I actually ran into this exact problem while working at a crop insurance company in Minnesota years ago. The app had to be able to calculate the total given area of all crops for an insurance quote. As you may or may not know - crops come in all shapes and sizes, yes, even in circles and triangles and various other polygons.

Ok, back to the example …

Being the good programmers that we are, we abstract the area calculation into a class named AreaManager. The AreaManager class has a single responsibility - to calculate the total area of the shapes.

Let’s also assume that we’re only working with rectangle crops right now so we have a Rectangle class that represents this. Here’s what these classes might look like:

Rectangle.java

public class Rectangle {
    private double length;
    private double height; 
    // getters/setters ... 
}

AreaManager.java

public class AreaManager {
    public double calculateArea(ArrayList<Rectangle>... shapes) {
        double area = 0;
        for (Rectangle rect : shapes) {
            area += (rect.getLength() * rect.getHeight()); 
        }
        return area;
    }
}

The AreaManager class does its job well until next week when we have a new type of crop show up - a cirle:

Circle.java

public class Circle {
    private double radius; 
    // getters/setters ...
}

Since there is a new shape we have to account for, we have to change the area manager:

AreaManager

public class AreaManager {
    public double calculateArea(ArrayList<Object>... shapes) {
        double area = 0;
        for (Object shape : shapes) {
            if (shape instanceof Rectangle) {
                Rectangle rect = (Rectangle)shape;
                area += (rect.getLength() * rect.getHeight());                
            } else if (shape instanceof Circle) {
                Circle circle = (Circle)shape;
                area += (circle.getRadius() * cirlce.getRadius() * Math.PI;
            } else {
                throw new RuntimeException("Shape not supported");
            }            
        }
        return area;
    }
}

This code is starting to smell already.

If we have a triangle show up, or any other polygon for that matter, we’re going to be changing this class over and over.

This class violates the Open/Closed principle. It is not closed for modification and it is not open to extension. Every time a new shape comes along we have to modify the AreaManager. We want to avoid this.

How do we make the AreaManager class Open/Closed friendly?

Implementing the Open/Closed Principle with Inheritance

Since the AreaManager is responsible for calculating the total area of all the shapes, and because the shape calculation is unique to each individual shape, it seems only logical to move the area calculation for each shape into its respective class.

Hmmm, but that makes the AreaManager still have to know about all the shapes, right? Because how does it know that the object it’s iterating over has an area method? Sure, this could be solved with reflection (cough gross cough) or we could have each of the shape classes inherit from an interface: the Shape interface (this could also be an abstract class too):

Shape.java

public interface Shape {
    double getArea(); 
}

Each class would implement this interface (or extend from the abstract class if that’s what you needed - for whatever reason) like this:

Rectangle.java

public class Rectangle implements Shape {
    private double length;
    private double height; 
    // getters/setters ... 

    @Override
    public double getArea() {
        return (length * height);
    }
}

Circle.java

public class Circle implements Shape {
    private double radius; 
    // getters/setters ...

    @Override
    public double getArea() {
        return (radius * radius * Math.PI);
    }
}

We can now make the AreaManager follow the Open/Closed principle by relying on this abstraction:

public class AreaManager {
    public double calculateArea(ArrayList<Shape> shapes) {
        double area = 0;
        for (Shape shape : shapes) {
            area += shape.getArea();
        }
        return area;
    }
}

We’ve made changes to the AreaManager that allow it to be closed for modification but open for extension. If we need to add a new shape, such as an octogon, the AreaManager will not need to be changed because it is open for extension through the Shape interface.

The Open/Closed Principle In Android

Shapes are good for learning and they’re very useful when you’re working in crop insurance, but how does this apply to Android? Well, it not only applies to Android, but any language for that matter. Android does have some nice Open/Closed principle implementations that are useful to explain. Let’s check them out -

One thing that many new Android developers are not aware of is that built-in Android views like the Button, Switch and Checkbox are all TextView objects. Take a look at this screenshot and you’ll see there are various other views that inherit from TextView.

Android TextView

This means that the Android view system is closed for modification but open for extension. If you want to change the way the text looks by creating your own CurrencyTextView, you simply extend (inherit) from TextView and implment your view logic there. The Android view system does not care that you’re using a new CurrencyTextView, it just cares that your class follows a particular contract for a TextView. Android will rely on particular methods to be present and your view will be drawn to the screen.

The same thing can be said of the ViewGroup class as well:

Android ViewGroup

There are many different ViewGroups (RelativeLayout, LinearLayout, etc) and the Android view system knows how to work with them. You can create your own ViewGroup fairly easily by extending ViewGroup. Finally, you can write code that relys on ViewGroup, TextView, or even the View class to do something special.

Relying on abstractions like View, TextView, ViewGroup and more allows you to write code that is closed for modification but open to extension.

Conclusion

The Open/Closed principle is not limited to the Android view system, but the Android view system is a simple way to see this principle applied in a real world operating system used by thousands of developers everyday. You too can write code that is Open/Closed friendly. With a little bit of planning and abstraction you can create code that is more easily maintainted and extensible, and doesn’t need modification every time a new feature is sent down the line.

As with everything, you may not see the need to create abstractions out of the gate with a new project. Furthermore, doing so may create overly complicated code for no reason other than for the sake of implementing a pattern. In my experience, I have found that I usually implement the Open/Closed principle after I find myself changing a class a couple of times. At that point I’ll ensure that code is covered in a test and then I’ll refactor it to be closed for modification and open for extension. This allows me to have a test coverage safety net and it also allows me to write more maintainable code while remaining lean and minimally viable in my day to day development.

Stay tuned for the next item in this series, the Liskov Substitution Principle, it’s by far one of my favorites.



1. Technically speaking, there are two variations of the Open/Closed principle. The Open/Closed principle was created by Bertrand Meyer and his varition is the Meyer's Open/Closed principle. The other is the Polymorphic Open/Closed principle. Both of the principles use inheritance as the solution. I will be utilizing the polymorphic interpretation as expressed by Robert C. Martin

Check out part three of the series, the Liskov Substitution Principle next!


Donn Felker

Donn Felker

Donn Felker is the co-host of the Fragmented Podcast and the founder of Caster.IO, a bite-sized Android developer video training site. He's the curator of the #AndroidDev Digest, a best selling mobile author, international speaker, and consultant on all things Android. He also loves Chipotle.