Topic 11
Coupling, Cohesion and Design Patterns
Credits for this week's notes
- Some of the early part of today's notes are based on the originals by a former member of staff John Flackett, but reworded
What is a design pattern?
A software pattern is a documented solution to a commonly-encountered software design problem. It helps allocates responsibilities for different tasks amongst your classes and can help produce reusable, easy to understand, maintainable code.
Following patterns is a way to ensure that your code follows general good design principles:
- Reusability: ability to reuse classes in other applications
- Maintainability: patterns make code cleaner, more understandable, and therefore easier to maintain
- Overall system robustness - patterns help avoid situations where changes in one class result in changes in the whole system which can introduce bugs.
Low coupling and high cohesion
Before looking at a few specific design patterns, we will look at the general principles of low coupling and high cohesion, which many patterns follow.
Low coupling
Low coupling means to minimise the dependency a class has on other classes, which can make the class more reusable and also makes the code easier to read, and, therefore, maintain.
What is wrong with dependency on other classes?
- Harder to reuse
- If class A depends on class B, we cannot reuse it in a scenario where we don't need class B
- Harder to understand in isolation
- It is harder to figure out the inner workings of a class which depends on other classes, compared to a class which does not
- Changes in other classes force changes in the class
- If class A depends on class B, and the public methods in class B change, we have to change class A as a result, which adds to maintenance effort.
Minimise, do not necessarily eliminate, dependencies
Obviously sometimes a class has to depend on another class. For example, a Cinema class might contain a list of Screen objects, which makes sense, as cinemas always contain at least one screen.
However, if a Screen depended on a Cinema class, that would minimise the reusability of the Screen. For example, what if you wanted to show a film outside the context of a cinema, e.g. a private viewing in a social club, etc?
Think of real-world analogies, would the real world object the class is modelling exhibit such a dependency?
Coupling example - College, Course and Student
High coupling design (bad)
- In this example, the
Studentdepends on theCollegeclass - The Student's
enrolOn()method takes a course ID and uses theCollegeto find theCoursewith that ID, and then theStudentadds thatCourseto its list of courses - The
Studentbecomes dependent onCollege - This is bad, because it is an unnecessary dependency, as we will see with the low-coupling design, and means that the Student class could not be used without a
College - Examples where we might want to use students and courses without a college include:
- a company which organises courses for its employees (who would be the students), such as IT or first-aid courses
- individuals or organisations who set up their own private courses, independent of a college
Low coupling alternative
Student no longer depends on College in this example
- The
Studentno longer needs theCollege - Rather than passing in a course ID (which needs a
Collegeto look up theCoursewith that ID) we pass in theCourseobject directly toenrolOn()
Coupling and user interfaces - summary
- You should design your data classes (e.g.
UniversityorStudentin the week 3 exercise) so that they are independent of your GUI - Allows the data classes to be reused with a different UI (console, web, desktop GUI, Android app)
- Avoid all references to the GUI in data classes
- Similarly, avoid console output (
println()orreadln()) as this couples your data classes to the console- Use return values to indicate success or otherwise, e.g. boolean return values, or
nullto indicate an error if a method returns an object, instead
- Use return values to indicate success or otherwise, e.g. boolean return values, or
- By keeping the data classes free of UI code, we are following the software engineering principle of separation of concerns, which states that you should separate out different functions of the system (UI, data) into different classes
High Cohesion
- A highly cohesive class is one where the responsibilities are all closely related, and focused on one thing
- It has a small number of methods, with highly related functionality
- It does the job it's supposed to do, and nothing else!
Example of a high cohesion design
- Imagine a student records system application
- A low cohesion system might have one big
StudentRecordsSystemclass which does all the work - A high cohesion system, by contrast, would have a series of smaller classes, all focused on one particular job, e.g.:
Studentto represent a studentCourseto represent a courseUniversityto manage the system as a whole
- This means that the individual classes could be re-used in other applications, and by separating out code into individual classes, makes the application easier to read, and thus maintain
Specific Design Patterns
Gang of Four Design Patterns
Many of the most common design patterns were invented by the "Gang of Four" (Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides). and published as the book "Design Patterns: Elements of Reusable Object-Oriented Software" (Gamma, E., Helm, R., Johnson, R. and Vlissides, J., 1994, Addison-Wesley).
We will look at a small number of example patterns: feel free to obtain a copy of the Design Patterns book to find out more.
We will be looking at:
- Singleton
- Facade
- Factory
- Observer
Singleton pattern
We have already looked at the Singleton pattern in the context of Exposed. With the singleton pattern, we ensure that we can only ever have one instance of a given object. Kotlin makes the singleton pattern very easy thanks to the object keyword: as we saw last week you can use object to create a single-instance object. Singletons are very useful in any case where it does not make sense for more than one object of a given class to exist, such as a class representing a particular database table. Another example would be a database connection object: in Exposed we do not need to do this but many database APIs require you to connect with a connection object. There will only be one connection object at a time, so it makes sense for it to be a singleton.
Facade pattern
The facade pattern involves wrapping the internals of the system with an outer class known as a facade. The facade provides a limited number of methods which the outside world can use to interact with the system. Inside the facade there might be a series of internal classes, all of which interact with each other. To prevent unexpected behaviour you may want to limit the extent to which the outside world (e.g. the main() or any other unrelated classes) can manipulate the internal classes. So by wrapping them in a facade and strictly limiting interaction to a small number of facade methods, you can make your system more robust and minimise errors.
Example of a facade
We have in fact been working with a simple use of the facade pattern already without realising it. If you think of your university system you will remember that we have a University class which contains a list of Student objects. The outside world cannot interact with the Student objects directly, only through the University, which is the facade in our system. We could potentially extend our university system to also store information about modules, staff, and so on - and similarly, we would wrap these inside the University facade to limit how the outside world can access and manipulate them, improving the robustness of our system.
Another example is the Shop class that we looked at when examining aggregation. Shop is a facade.

Factory pattern
The factory pattern involves using a factory object containing a factory method to generate other objects which implement some interface. The outside world need never know about the exact class to which these objects belong - the only thing the object knows, or cares about, is that they are objects implementing a particular interface which will therefore be guaranteed to have particular methods which can be called.
The factory pattern involves the factory method receiving some kind of specification - typically a string - and then generating the appropriate object depending on the value of that string.
Why might you want to use the factory pattern? Because the exact implementation of the objects created by the factory - what class they belong to and what inheritance hierarchy that class might be a part of - is of no concern to the user of those objects, it means that these can change but the application using them need not change at all. It also means that the objects returned from the factory method can belong to different inheritance hierarchies. The only thing that matters is that they implement a particular interface.
Here is an example. Imagine we want to develop an application to convert between different unit systems, such as metres to feet or Celsius to Fahrenheit.
We could use the factory pattern here with a factory method getConverter, which returns an object implementing the UnitConverter interface. This interface specifies two methods convert() (which converts from one unit to another, e.g. metres to feet) and reverseConvert() (which converts from the second unit back to the original, e.g. feet to metres). The factory method always returns some object which implements UnitConverter.
The logic of the factory would return a MetresToFeetConverter object if required by the user or a CelsiusToFahrenheitConverter if the user wants that.
The UnitConverter interface would look something like this:
and we could implement this in a MetresToFeetConverter class as follows:
while the factory might look something like this:
The exact class the UnitConverter belongs to, however, is not something which the developer making use of the factory needs to think about: they just use the factory to obtain some object which implements the UnitTranslator interface and then use its convert() and reverseConvert() methods.
Here is an example of code which would use the factory method:
Another example could involve the DAOs that we looked at last week. Note how we defined the DAO as an interface and then provide a class which implements the interface using Exposed. However, we might have other DAOs which implement the interface using other mechanisms, such as obtaining the data from a flat file (ordinary file, not database) or the web. What we could use in this case is a factory which generates the appropriate DAO using a string passed in as an argument. For example:
The above factory generates a DAO making use of Exposed, plain JDBC (without Exposed), a CSV file, or a web data source, depending on the value of the source parameter passed in. So we could create an ExposedWadsongsDao, for example, with:
Observer pattern
Another pattern which can minimise dependencies and introduce low coupling is the Observer pattern. With the observer pattern, we have two entities:
- The
Observable. AnObservableis an object which typically contains data which changes frequently, such as data from a database. It contains a list of Observers which are notified when the data changes. - The
Observer. AnObserveris an object which observes the Observable, and is notified whenever theObservablechanges. It includes anobserve()method which receives the data to be observed, and can act upon it.
Observable and Observer are typically implemented as interfaces (e.g. Microsoft article on the Observer pattern) and the Observer does not have to know exactly what class the Observable belongs to. It subscribes (adds itself) to the Observable and is thus added to the list of Observers that the Observable contains.
So when the data within the Observable changes, it notifies the Observers which can then react accordingly, e.g. by updating the UI. Examples of the Observer pattern could include:
- a database application. An
Observablecould handle database communication and notify itsObservers (UI elements) when the data changes, so that they can update. - a login system. When the user logs in, various UI elements might need to be updated to enable certain functionality (e.g. enabling buttons which are disabled when logged out). The login system could be the
Observableand the UI elements could be theObservers. - an instant messaging system. This would be a networked (client/server) example of the observer pattern. A messaging server could act as the
Observable, and, via network communication, notify the messaging clients (theObservers) when a new message arrives. In this case, the notifying is asynchronous as it is across a network, and in this kind of case, a variant of Observer known as pub/sub (publisher/subscriber) applies. The messages are typically added to a queue to be sent across the network.
The Observable might be implemented as follows:
Generics
Note the use of:
What does this mean? The
The observer interface might be written as follows. It has to do is observe() the observable and receive the data (of generic type T) sent from the observable, so it only needs to specify one method, observe():
Adding and notifying observers
Note that in Kotlin, interfaces can include full implementations of methods as well as method specifications. We provide full implementations of addObserver() (which adds an observer) and notifyAll() (which notifies all observers of a new value of data.
The Observable only provides one item which must be overridden: the attribute observers which is the list of observers. We are just specifying that this attribute must be present: we are not actually creating it. We must override the variable in any classes implementing the Observable interface.
Implementing the Observable and Observer
Here is how the two interfaces could be implemented in a simple login system (for simplicity, the login system just checks whether the username and password are both equal to admin). Firstly LoginHandler implements the Observable:
Note for LoginHandler, the generic type T is always User. Note how we provide an implementation of the observers list, as discussed above.
The login method handles login, and if the details are correct, all observers are notified of the User object thus created via the Observable's notifyAll().
Several UI elements which need to know whether the user is logged in could then act as Observer objects, implementing the Observer interface. For example:
The advantage of the Observer pattern here is that if multiple UI components of the app need to test whether the user is logged in, they can all implement Observer
Exercises
Important! : please ensure you complete the whole of Topic 7 and Questions 1 to 5 from Topic 8 first.
- Extend the Factory example to include, as well as the metres to feet converter, specific converters for Celsius-Fahrenheit (temperature) and kilojoules to kilocalories (energy). You should be familiar with kilocalories in relation to food intake, but joules are the standard unit of measurement for energy used in chemistry and physics and energy values in kilojoules (1000 joules) are sometimes also mentioned on food packaging.
Note that:
You will need:
- The
Converterinterface; - Three classes implementing the interface, one for each conversion type;
- The factory object, which should understand the strings
celsius-fahrenheitandkilojoules-kilocaloriesas well asmetres-feet, and create and return the appropriate converter.
Write a simple console app using the factory, to allow the user to enter:
- a value
- a converter (the string the factory takes, e.g.
metres-feet) - whether they want to convert from the first unit to the second, or the reverse.
The app should then print the appropriate result.
- Using the Observer pattern, develop a simple login form as a Compose app. The user should be logged in if they enter "admin" for both username and password. Create the
ObservableandObserverinterfaces as shown in the notes, and implement the LoginSystem class. Add anObserverto observe the user logging in, and if theobserve()method of the observer is called, update a state variable holding the currently logged in user (which should be null by default) so that it contains the new user. TheObservableandObservershould work withUserobjects (i.e. theObservableshould send the observers aUserobject when they log in), as shown on the example above.