Object Oriented Programming

Learning goals

  • Software design approaches and patterns, to identify reusable solutions to commonly occurring problems
  • Apply an appropriate software development approach according to the relevant paradigm (for example object-oriented, event-driven or procedural)

Object Oriented Programming (OOP) is a coding style focused around objects which encapsulate all the data (information) and code (behaviour) in an application. The majority of computer programs written today use OOP as part of their programming style, normally the main part and almost all mainstream languages support object orientation.

This article considers OOP specifically in the context of Java, but the principles apply to any language.

Classes

You are hopefully already familiar with the concept of a class. This is the unit in which you write Java code, normally putting one class in a file. In OOP, you can consider a class to be a blueprint to create objects. The class has two key types of content:

  • Data – information that’s stored in the class
  • Behaviour – methods that you can call on the class

We’ll use as an example a Zoo Management System application – a simple app that helps zookeepers keep tabs on their animals. Here are some classes you might find in the Zoo app:

  • Lion – represents a lion. You might want to store data about the lion’s age, and you might want to implement some program behaviour to deal with the time when the lion is fed.
  • Keeper – represents a zookeeper. A zookeeper probably has a list of animals she’s responsible for.
  • FeedingScheduler – a class that is responsible for managing the animals’ feeding schedule. This one is less recognisable as a “thing” in the real world, but it’s still very much a type of thing in your program.
  • Config – stores configuration information about your program. This is a class storing a useful conceptual object that we will explore later.

Fork a copy of the Zoo Management System repo and follow the instructions laid out in the README.

Open up the project directory in your IDE and take a look at the classes described above – for each one, make sure you can follow its responsibilities, both in terms of data and behaviour.

Instances

An instance of a class is an object that follows the blueprint of the class it belongs to. So, the class Lion represents lions generally; an instance of that class represents a single specific lion. A class can have many objects that belong to it. You create a new instance of a class using the new keyword:

Lion myLion = new Lion();

As mentioned, these instances are referred to as “objects” – hence the term Object Oriented Programming.

Once you have an object, then you call methods on the object itself:

if (myLion.isHungry()) {
    myLion.feed();
}

Static vs nonstatic

By default, data (stored in fields) and behaviour (stored in methods) are associated with the instance. The Rabbit class defines lastGroomed, and each individual Rabbit instance has a value for this field.

You can alternatively associate data and behaviour with the class itself. To do this, use the static keyword. Now this field or method is called on the class itself, not an instance of the class.

Take a look at the FeedingScheduler class. We’ve added a static field and a static method to the class – to access the instance property, you call FeedingScheduler.getInstance(). In contrast, to call the nonstatic method assignFeedingJobs() you’d invoke it on a specific object, as in myScheduler.assignFeedingJobs(etc).

When should you use a static field or method? On the whole, not very often. OOP works best if you create instances of classes because then you unlock the power of inheritance, polymorphism, etc. which we’ll come onto below. It also makes testing your code easier. But occasionally static is useful – here are some of the main examples:

  • Where you have unchanging data which can be shared by all instances of a class. Perhaps the zoo has a policy on lion feeding times that applies to all lions; this could go as a static field on the Lion class because it’s shared.
  • The Singleton pattern. This is where there is strictly only one instance of a particular class allowed; the FeedingScheduler is an example of how this pattern is used. The single instance of the class is stored in a private static field, and anyone who wants “the” FeedingScheduler can get it via a static property. (You might well ask, why not just make all the fields and methods on the FeedingScheduler static, and not bother with instances at all? That’s because of the above point about how the power of OOP will eventually benefit you most if you use instances).
  • The main method. This must be static by definition. Again it’s best practice to keep this short, though, and start using instances as soon as possible.

As a rule, aim to make everything nonstatic, and make a conscious decision to make things static where appropriate – not the other way around.

Inheritance

Inheritance is where a lot of the power of OOP comes from. If a child class “inherits” from a parent class, the child class gets all the data and behaviour definitions from its parent, and can then add additional behaviour on top.

Look at the models folder: There’s a hierarchy of classes. Lion inherits from AbstractAnimal, and hence inherits the dateOfBirth property, age function, and feeding behaviour. This can be most naturally phrased as “A Lion is an Animal”. Rabbit is implemented similarly, but unlike Lion contains some rabbit-specific behaviour – rabbits can be groomed.

Inheritance also allows behaviour to be modified, not just added – take a look at the Rabbit’s feed method. If someone calls the feed method on a rabbit, this method is invoked instead of the one on Animal. Key things to note:

  • The Rabbit has a feed method which is marked @Override. That says this is replacing the base class (parent)’s implementation.
  • The Rabbit’s feed method calls super.feed(). That’s entirely optional, but invokes the behaviour in the base class – i.e. the rabbit makes a munching noise, and then does the normal stuff that happens when you feed any animal.

Note that the rabbit’s implementation of feed will be invoked regardless of the data type of the variable you’re calling it on. So consider the following example:

Rabbit rabbit = new Rabbit();
Animal animal = rabbit;

rabbit.feed();
animal.feed();

animal = new Lion();
animal.feed();

Both the first two feedings will call the rabbit version because the object (instance) in question is a rabbit – even though animal.feed() doesn’t appear to know that. However, the final feeding will call the general animal version of the method, becuase Lion doesn’t override the feed method.

Interfaces

An interface is a promise by the class to implement certain methods. The interface doesn’t contain any actual behaviour (each class that implements the interface defines its own behaviour), it just contains method names and their parameters.

Take a look at the CanBeGroomed interface definition – it specifies a single groom method. Note that we don’t say public, even though the method will always be public, because all interface methods are public by default.

You cannot create an instance of CanBeGroomed – it’s not a class. Notice that:

  • Zebra and Rabbit implement this interface
  • Keeper.groom accepts anything of type CanBeGroomed

If it wasn’t for the CanBeGroomed interface, it would be impossible to define a safe Keeper.groom method.

  • If Keeper.groom took a Rabbit, you couldn’t pass in a Zebra, because a Zebra is not a Rabbit.
  • If Keeper.groom took an Animal (like feed does), you could pass in anything but then you can’t call groom on that animal afterward. This applies even if the animal is, in fact, a zebra – the Java compiler cannot know that you’ve passed in a zebra on this particular occasion because the variable is of type Animal and hence might be any animal.

Note that there’s no direct link between animals and things-that-can-be-groomed. The keeper can now groom anything implementing that interface – it doesn’t have to be an Animal! This is perfectly reasonable – if all the keeper does is call the groom method, it really doesn’t care whether it’s an animal or not. The only thing that matters is that the groom method does actually exist, and that’s precisely what the interface is for.

Polymorphism

Polymorphism means the ability to provide instances of different classes to a single method. Take a look at the Keeper class. The method feed takes a single parameter with type Animal and the method groom takes a single parameter with type CanBeGroomed. Thanks to polymorphism, we can provide any class extending Animal to feed and any class implementing CanBeGroomed to groom.

In particular, we could pass any of the following to feed:

  • A Rabbit, because it inherits from Animal
  • A Lion, because it inherits from Animal
  • An animal we’ve never heard of, but that someone else has defined and marked as inheriting from Animal (this might be relevant if you were writing an application library, where you don’t know what the user will do but want to give them flexibility when writing their own programs)
  • An Animal itself (i.e. an instance of the base class). Except actually right now you can’t create one of those because that class is marked abstract, which means you’re not allowed to create instances of it and it’s only there for use as a base class to inherit other classes from

Polymorphism is pretty powerful. As an experiment, try adding a GuineaFowl to the zoo, you can decide for yourself if a GuineaFowl can be groomed.

Choosing between interfaces and inheritance

It’s worth pointing out that our use of the interface CanBeGroomed here leads to a slightly unfortunate side-effect, which is that the Rabbit and Zebra classes have a lot of duplicated code. They don’t just share a groom method; they have completely identical implementations of that method. One way of implementing this behaviour to avoid this:

  • Create a new subclass of Animal called AnimalThatCanBeGroomed
  • Change Zebra and Rabbit to inherit from AnimalThatCanBeGroomed, not from Animal
  • Get rid of the interface, and replace all other references to it with AnimalThatCanBeGroomed

Since their common code lives in AnimalThatCanBeGroomed, this would make the individual animals (Rabbit and Zebra) much shorter. However, there’s a potential disadvantage lurking too: You can only inherit from a single parent. Suppose we now add in some scheduling for keepers sweeping muck out of the larger enclosures. That applies to Lion and Zebra, the larger animals, but not Rabbit (no doubt we’ll get a separate hutch-cleaning regime in place soon enough). Now what do we do? We can repeat the above steps to create a CanHaveMuckSweptOut interface or an AnimalThatCanHaveMuckSweptOut class. But if we do the latter, we can’t also have AnimalThatCanBeGroomed because Zebra can’t inherit from both at once. At this stage, it starts looking like we’re better off with the interface approach. We’ll have to find another way to minimise code duplication. What options can you come up with?

As another exercise, take a look at the main method. This currently creates two schedulers, one for feeding and one for grooming, and then calls them both with very similar calls. The methods (assignFeedingJobs and assignGroomingJobs) currently have different names, but we could rename them both to just assignJobs and then try to implement some common code that takes a list of schedulers and calls assignJobs on each in turn. Should we do this by creating a Scheduler interface, or by creating a common Scheduler base class? Think about the pros and cons.

Composition

Composition is an OOP pattern that is an alternative to inheritance as a way of sharing common characteristics and behaviour. We’ll look at how it could be used as a different approach to the issue of grooming and sweeping out muck.

  • Inheritance expresses an “is a” relationship, as in “a Zebra is an AnimalThatCanBeGroomed, which is an Animal
  • Composition expresses a “has a” relationship, as in “an Animal has a Groomability characteristic”

In the latter case, Groomability would be an interface that has at least two implementations, GroomableFeature and UngroomableFeature:

public interface Groomability {
    boolean isGroomable();
    void groom();
}

public class GroomableFeature implements Groomability {
    @Override
    public boolean isGroomable() {
        return true;
    }

    @Override
    public void groom() {
        lastGroomed = LocalDateTime.now();
    }
}

public class UngroomableFeature implements Groomability {
    @Override
    public boolean isGroomable() {
        return false;
    }

    @Override
    public void groom() {
        throw new Exception("I told you I can't be groomed");
    }
}

So then the classes might be composed in the following way.

public abstract class Animal {
    private Groomability groomability;
    private Muckiness muckiness;

    protected Animal(Groomability groomability, Muckiness muckiness) {
        this.groomability = groomability;
        this.muckiness = muckiness;
    }
}

public class Zebra extends Animal {
    public Zebra() {
        super(new GroomableFeature(), new NeedsMuckSweeping());
    }
}

public class Rabbit extends Animal {
    public Rabbit() {
        super(new GroomableFeature(), new NonMucky());
    }
}

public class Lion extends Animal {
    public Lion() {
        super(new UngroomableFeature(), new NeedsMuckSweeping());
    }
}

As you can see, composition enables much more code reuse than inheritance when the commonality between classes doesn’t fit a simple single-ancestor inheritance tree. Indeed, when you are designing an OOP solution, there is a general principle of preferring composition over inheritance – but both are valuable tools.

Default methods (Advanced)

In modern versions of Java, interfaces may include implementations of methods marked with default. These are mainly useful when you need to extend an interface while retaining backward compatibility. For example, the Iterable interface has a default implementation of forEach which accepts a functional interface (covered later in the course) – this is usable on all existing implementations without changing any code.

However, if you find yourself with lots of large default methods, an abstract class might be more suitable!

Object oriented design patterns

Design patterns are common ways of solving problems in software development. Each pattern is a structure that can be followed in your own code, that you implement for your specific situation. Following established design patterns is advantageous because:

  • in general they have evolved as best practice, and so avoid the need for you to solve every problem anew – although “best practice” changes over time so some established patterns fall out of favour; and
  • other developers will probably be familiar with the patterns so it will be easier for them to understand and maintain your code.

Although design patterns aren’t specifically tied to object oriented programming, most of the common design patterns that have arisen fit the OOP paradigm. The following are some common OOP design patterns.

  • Singleton: This pattern ensures that there is only ever one instance of a class. This could be important if your application needs a single shared configuration object or an object manages access to an external resource. This would be implemented by:

    • Having a static property in the class that can hold the singleton
    • Making the class constructor private so that new objects of that class cannot be created
    • Having a static public method that is used to access the singleton; if the shared object doesn’t exist yet then it is created
  • Factory: This is a pattern for creating objects that share an interface, without the caller needing to know about the various classes that implement that interface. For example, a factory pattern for creating objects that have the interface Product could look like:

    • Two classes that implement Product are Pen and Book
    • The interface ProductFactory has the method createProduct, which creates and returns Product objects
    • The class PenFactory implements ProductFactory, and its createProduct method creates and returns a new Pen
    • The class BookFactory implements ProductFactory, and its createProduct method creates and returns a new Book
    • If code elsewhere in the application is given a ProductFactory for creating products, it doesn’t need to know what the products are or be told when new products are created
  • Model-View-Controller: You’ll be familiar with the MVC pattern from the Bootcamp Bookish exercise; it’s a very common way to structure user interfaces.

    • Model objects contains the data that is to be displayed (e.g., an entity that has been fetched from a database)
    • The View is a representation of the Model to the user (such as a table or graphical representation)
    • The Controller processes commands from the user and tells the Model objects to change appropriately
  • Adapter: This pattern would be suitable for the Bootcamp SupportBank exercise, in which you had to process data from files in different formats (CSV, JSON and XML). An adapter is a class that allows incompatible interfaces to interact. In the case of SupportBank, you might decide that:

    • TransactionReader is an interface that has the method loadTransactions(), which returns an array of Transaction objects
    • There are three implementations of TransactionReader, each of which knows how to parse its particular file type, convert values where necessary and produce Transaction objects
    • Note that you might use a Factory pattern to create the TransactionReaders, so it’s easy to add support for new file types in future

Strengths of OOP

The following are strengths of the object oriented programmming paradigm.

Abstraction refers to the fact that object oriented code hides information that you don’t need to know, so if you’re using an object all you know about it is its publicly visible characteristics. Your code doesn’t know about how the object’s data and methods are implemented. This is especially clear when dealing with interfaces – code that interacts with an interface knows only the contract that the interface publishes, and that interface might be implemented by lots of different classes that have completely different structures and behaviour.

Encapsulation refers to the fact that the data and behaviour of an object are tied together into a single entity. If you think about programming without encapsulation, if you have a simple data structure with a set of fields in it and then want to run a function on it, you need to make sure that you find the function that correctly handles that specific data structure. With encapsulation, you just call the object’s own method.

Polymorphism refers to the fact that an object can be interacted with as if it were an instance of an ancestor class, while its behaviour will come from its actual class. When a new descendent class is defined with its own implementation of ancestor class methods, code that interacts with the base class will automatically call the new implementations without having to be updated.