Object-Oriented Software Engineering

This is an archived version of https://www.jhu-oose.com that I (Leandro Facchinetti) developed when teaching the course in the Fall of 2019. Some of the links may be broken.

Lecture 3: Design Patterns

Video 🔒

Introduction

References

Find the Design Pattern

Find the design pattern in one of the applications we’ve worked with thus far: either TODOOSE or something from the assignments (for example, Minesweeper). The pattern may be used by the application, or it could have been used by the application, or it was used by one of the libraries on which the application depends, or it could have been used by one of these libraries, or you may propose an extended feature for the application and use the pattern on the design of that extension, and so forth.

Model–View–Controller (MVC)

This doesn’t appear in the original Design Patterns book.

There are a million different interpretations to MVC: from the original Xerox paper, from Ruby on Rails and other web frameworks, from JavaScript frameworks, and so forth. But in general:

Model: The source of truth for data; and the business logic, for example, the rules of a game.

View: The representation of the data as seen by the client. The client may be the user, or if we’re talking about the server, the client is the browser, and the view is the mapping to JSON.

Controller: Handles the interactions from the client, and coordinates the work of the other parts of the system, including the Model and the View. Again, the client may be the user, or if we’re talking about the server, the client is the browser which initiated the HTTP request.

Where are the M, the V, and the C in TODOOSE’s server? (Hint: We’ve covered this in lectures and assignments.)

My Answer

The models are in com.jhuoose.todoose.models, the controllers are in com.jhuoose.todoose.controllers.ItemsController, and the view are the JSON mapping in the controller (though that’s a stretch).

TODOOSE is a distributed application composed of two components communicating over the network, the server and the client, and each component is an application of its own, which could have its own MVC stack.

The TODOOSE client (where is it?) doesn’t use the MVC pattern directly. If it were to use it, which parts of the code would end up in the M, the V, and the C? Why would you want to move to this architecture in the client?

My Answer

Models: Things like { items: await (await fetch("/items")).json() }.

Views: The renderable parts of the React components (React only helps with the V in MVC).

Controllers: The methods called handle<Event>() (which handle user interaction, like clicks of a button), and methods like componentDidMount().

You’d want to move to a MVC architecture on the client when there’s more logic on the client, besides just display data coming from the server and issuing simple HTTP requests. Or maybe if you have a design team who’s only writing the V, and a software development team who’s writing the rest.

What principles are you following or breaking when you use this design pattern?

My Answer

Following Single Responsibility, but breaking YAGNI (unless you aren’t).

Singleton

In Java, the only way to create an object is by creating a new instance of a class. But in JavaScript we may create objects directly, for example:

const configuration = {
  name: "TODOOSE",
  url: "https://todoose.herokuapp.com"
};

This is useful when the object must be unique throughout the system, for example, some configuration, or an object representing some piece of hardware (like a printer in a system in which there’s only one printer), and so forth.

To reproduce this effect in Java, we may have a class that is instantiated only once, a Singleton.

What classes are singletons in TODOOSE? (Hint: You’ll only see new TheClassThatIsASingleton() once in the code base.)

My Answer

connection, itemsRepository, and itemsController.

How can we enforce that a singleton class is instantiated only once? (That is, how can we prevent other developers from inattentively creating a second instance of that class?)

My Answer

Make the constructor private and provide a static method which returns the only instance, for example:

public class ItemsRepository {
    private static ItemsRepository instance = new ItemsRepository();

    private ItemsRepository() {
        // ...
    }

    public static ItemsRepository getInstance() {
        return instance;
    }

    // ...
}

What about the connection, on which ItemsRepository depend? Make it available through a static method on the Server:

public class Server {
    private static Connection connection;

    static {
        try {
            connection = DriverManager.getConnection("jdbc:sqlite:todoose.db");
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    // ...

    public static Connection getConnection() {
        return connection;
    }
}

and when connection is necessary:

var connection = Server.getConnection();

But static things, and therefore singletons, are just another name for global variables. Beware.

Also, what if you buy another printer?

What principles are you following or breaking when you use this design pattern?

My Answer

If you enforce that the class is a singleton with the technique above, you’re applying loose coupling, because the clients need to know less about the class: the system is enforcing the singleton nature of the class for you. At the same time, you may be tightening the coupling between the objects in the system and their real-world counterparts: what if you buy a new printer?

Fluent Interface

We didn’t cover this in lecture.
This doesn’t appear in the original Design Patterns book.

Consider the same configuration object we wrote in JavaScript when discussion Singleton:

const configuration = {
  name: "TODOOSE",
  url: "https://todoose.herokuapp.com"
};

If the configuration fields aren’t known beforehand, it doesn’t make sense to have it as a Singleton class, because we don’t know which attributes this class should have. Instead, the configuration may be a map. In newer version of Java you may create a map like this:

var configuration = Map.of(
  "name", "TODOOSE",
  "url", "https://todoose.herokuapp.com"
);

but in older versions the Map.of() method didn’t exist, so you’d have to write the object like this:

var configuration = new HashMap<String, String>();
configuration.put("name", "TODOOSE");
configuration.put("url", "https://todoose.herokuapp.com");

It’s annoying to have to repeat configuration in every line. It’s also annoying that put() returns the values previously associated with the keys, which in our example is null in both cases, because we can’t do anything with these nulls. What if put() instead returned something more useful, namely, the modified map itself? Then we could chain the calls to put(), like this:

var configuration = new HashMap<String, String>()
  .put("name", "TODOOSE")
  .put("url", "https://todoose.herokuapp.com");

This is called a Fluent Interface. In some ways it isn’t as nice as the Map.of() version, because there’s more typing involved, but in some ways it’s actually nicer: for one thing, it supports arbitrarily many fields, as opposed to the Map.of() version which only works up to 10 fields. Also, in this version it’s easy to see at a glance what are the keys and what are the values—something you have to indicate with indentation in the original version, and indentation which may not survive an automated code formatter.

A Fluent Interface really shines when there are multiple methods in an interface that all return the underlying object. In the Map example, suppose that put(), clear(), remove(), and so forth, all returned the corresponding modified maps. Then we could write code like the following to manipulate the configuration:

configuration.remove("name")
             .put("port", "7000")
             .remove("some-other-field")
             // ...

Where can you find Fluent Interfaces on the TODOOSE code base? (Hint: There’s a more obvious Fluent Interface on the server, and a more interesting example somewhere else that is neither the server nor the client(!))

My Answer

How do you implement a Fluent Interface? Create a FluentMap class which acts like a Map, but provides the put() method as described above.

My Answer

Let the fluent methods return this, for example:

public class FluentMap<K, V> {
  private Map<K, V> map = new HashMap<K, V>();

  public FluentMap<K, V> put(K k, V v) {
    map.put(k, v);
    return this;
  }
}

You can use the FluentMap like this:

var configuration = new FluentMap<String, String>()
  .put("name", "TODOOSE")
  .put("url", "https://todoose.herokuapp.com");

What principles are you following or breaking when you use this design pattern?

My Answer

You’re probably breaking the Interface Segregation principle, but you’re keeping things simpler, at least for the client of the interface (that is, the code using that interface).

Decorator

We didn’t cover this in lecture.

When you want to extend (or in general, modify) a class, one possibility is to define a child class. But what if you want to extend (or in general, modify) the functionally of a few objects of a class, and not all of them?

Create a wrapper object that provides the extra functionality (or in general, different behavior), and delegates to the original object when necessary.

Where can you find a Decorator on the TODOOSE code base? (Hint: You implemented a Decorator in an assignment.)

My Answer

The ItemView from Assignment 2 was a Decorator for the Item model:

public class ItemView {
    private Item item;

    public ItemView(Item item) {
        this.item = item;
    }

    public int getIdentifier() {
        return item.getIdentifier();
    }

    public void setIdentifier(int identifier) {
        item.setIdentifier(identifier);
    }

    public String getDescription() {
        return item.getDescription();
    }

    public void setDescription(String description) {
        item.setDescription(description);
    }
}

Modify the FluentMap class from above such that it acts as an Decorator for an existing Map. (Caveat: Technically speaking a Decorator isn’t supposed to change the interface of the decorated object; we’re deviating from the textbook here.)

My Answer
public class FluentMap<K, V> {
    private Map<K, V> map;

    public FluentMap(Map<K, V> map) {
        this.map = map;
    }

    public FluentMap<K, V> put(K k, V v) {
        map.put(k, v);
        return this;
    }
}

Now you use the FluentMap like this:

var configuration = new FluentMap<String, String>(new HashMap<String, String>())
  .put("name", "TODOOSE")
  .put("url", "https://todoose.herokuapp.com");

What are advantages of the Decorator version over the previous one? What are the disadvantages?

My Answer

In the Decorator version the client (that is, whoever is using FluentMap) gets to choose the underlying Map implementation, for example, HashMap vs. TreeMap. This can also be a disadvantage, after all, convention over configuration, and most times you don’t care about the specific implementation. But it can also be an advantage if you care about the specific implementation.

The Decorator version is more verbose to use.

The Decorator version works over existing objects, including those you didn’t create yourself.

What principles are you following or breaking when you use this design pattern?

My Answer

You’re not repeating yourself, because the decorator delegates to the decorated object whenever possible, but the decorated object may now fulfill multiple responsibilities, breaking the single responsibility principle.

Conclusion