Because I never had a real programmer job and most of what I learnt regarding software development was more or less self thaught, I recently started to pursue a master’s degree in software engineering. And there seem to be more like me out there, that learnt coding more by doing than from school or university. That’s why I want to share some simple, but interesting tips I’ve learnt.
One of the things I want to share are some basic design patterns. Design pattern are best practices of how to design code for specific use cases. They might come in handy for you, if you develop Polarion extensions.
I will go through the following patterns:
Singleton Pattern
In object oriented programming you need to have an instance of a class in order to use it’s functions and attributes – with the exception of static classes. But sometimes you only want to have one instance of e.g. a logger class. Because why would you need more than one instance of the same logger in an application?
This is when you use the singleton design pattern to enforce that only one instance of the logger will be created during the application lifetime. You do this by making the constructor private and you get the instance of the logger via a function „getInstance()“ instead of using the constructor. The logger class itself knows then, if already an instance of itself exists (stored in „instance“) and if not, it will call the constructor to create one:
public class Logger { private static Logger instance; private Logger() {} public static Logger getInstance() { if (instance == null) { instance = new Logger(); } return instance; } public void log(String message) { System.out.println(message); } }
While static classes aren’t a design pattern, I would like to also have a quick look at them. A static class is used when you want to group related methods together, but don’t need to store any instance data or have any instance methods. Static classes are similar to singletons, but they cannot be instantiated and don’t have any instance variables. They can be used to define utility methods, for example:
public class MathUtils { public static int add(int a, int b) { return a + b; } public static int subtract(int a, int b) { return a - b; } public static int multiply(int a, int b) { return a * b; } public static int divide(int a, int b) { return a / b; } }
In general, you should use a singleton when you need to maintain state for a single instance of a class, and you should use a static class when you want to group related methods together, but don’t need any instance state or behavior.
Factory Pattern
A pattern that creates objects without specifying the exact class of object that will be created. This makes the code more flexible, because the client doesn’t have to know the specific object that will be created. Here is an example:
interface Shape { void draw(); } class Circle implements Shape { @Override public void draw() { System.out.println("Drawing a Circle"); } } class Rectangle implements Shape { @Override public void draw() { System.out.println("Drawing a Rectangle"); } } class ShapeFactory { public Shape getShape(String shapeType) { if (shapeType == null) { return null; } if (shapeType.equalsIgnoreCase("CIRCLE")) { return new Circle(); } else if (shapeType.equalsIgnoreCase("RECTANGLE")) { return new Rectangle(); } return null; } }
The client code can use the ShapeFactory
to create Shape
objects without having to know the specific class of the object that will be created. This makes the code more flexible, as new shapes can be added or existing shapes can be modified without affecting the client code.
Observer Pattern
The observer pattern is useful in situations where there is a one-to-many relationship between objects, and when changes to one object need to notify multiple other objects. This could be usefull for rich page widgets that utilize asynchronous methods and consist of multiple components to provide high performance and have to be kept in sync. Some common examples of when the observer pattern is useful include:
- Model-View-Controller (MVC) Architectures: The observer pattern is often used in MVC architectures to keep the view (the display) in sync with the model (the data).
- Event handling: The observer pattern can be used to handle events in a GUI, where events generated by user interactions with the GUI need to be handled by multiple objects.
- Broadcasting notifications: The observer pattern can be used to broadcast notifications to multiple objects, such as sending notifications about new email messages, stock prices, or weather updates.
- Dynamic systems: The observer pattern can be used to build dynamic systems where objects can be added or removed at runtime, and changes to one object need to be notified to all other objects in the system.
Here is an example:
interface Observer { void update(float temperature, float humidity, float pressure); } interface Subject { void registerObserver(Observer o); void removeObserver(Observer o); void notifyObservers(); } class WeatherData implements Subject { private List<Observer> observers; private float temperature; private float humidity; private float pressure; public WeatherData() { observers = new ArrayList<>(); } @Override public void registerObserver(Observer o) { observers.add(o); } @Override public void removeObserver(Observer o) { int i = observers.indexOf(o); if (i >= 0) { observers.remove(i); } } @Override public void notifyObservers() { for (Observer observer : observers) { observer.update(temperature, humidity, pressure); } } public void setMeasurements(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; notifyObservers(); } } class CurrentConditionsDisplay implements Observer { private float temperature; private float humidity; @Override public void update(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; display(); } public void display() { System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity"); } }
In this example, the Observer
interface defines a update()
method that must be implemented by concrete observer classes. The Subject
interface defines methods for registering, removing, and notifying observers. The WeatherData
class implements the Subject
interface and maintains a list of observers. When the weather data changes, the WeatherData
object notifies its observers by calling the update()
method on each of them.
The CurrentConditionsDisplay
class implements the Observer
interface and displays the current temperature and humidity. The client code can create instances of WeatherData
and CurrentConditionsDisplay
objects, and register the CurrentConditionsDisplay
object as an observer of the WeatherData
object. Whenever the weather data changes, the WeatherData
object will notify its observer and the CurrentConditionsDisplay
object will update its display.
Decorator Pattern
A pattern that allows behavior to be added to an individual object. Some use cases where this pattern makes sense are:
- User interface components: The pattern can be used to add new functionality to user interface components, such as adding a border, padding, or shadow to a text box, or adding a tooltip to a button.
- Dynamic content: The pattern can be used to dynamically add or change the content of a web page, such as adding advertisements, recommendations, or custom content to a web page.
- Performance optimization: The pattern can be used to add performance optimization logic to web pages or components, by decorating them with caching decorators that cache frequently used data or by adding load balancing decorators that distribute the load among multiple servers.
You can see how it works at the example of a webpage and to add a header and a footer to it:
interface Page { String display(); } class BasicPage implements Page { @Override public String display() { return "Basic Page"; } } abstract class PageDecorator implements Page { protected Page page; public PageDecorator(Page page) { this.page = page; } @Override public String display() { return page.display(); } } class HeaderDecorator extends PageDecorator { public HeaderDecorator(Page page) { super(page); } @Override public String display() { return "<header>" + super.display() + "</header>"; } } class FooterDecorator extends PageDecorator { public FooterDecorator(Page page) { super(page); } @Override public String display() { return super.display() + "<footer>Footer</footer>"; } } public class DecoratorExample { public static void main(String[] args) { Page page = new BasicPage(); page = new HeaderDecorator(page); page = new FooterDecorator(page); System.out.println(page.display()); // Output: // <header>Basic Page</header><footer>Footer</footer> } }
In this example, the BasicPage
class implements the Page
interface, which represents a simple web page. The PageDecorator
class is an abstract class that implements the Page
interface and adds a reference to a Page
object. The HeaderDecorator
and FooterDecorator
classes are concrete decorator classes that add a header and footer to the Page
object, respectively.
In the main
method, a BasicPage
object is created and decorated with a HeaderDecorator
and a FooterDecorator
. The final result is a web page with a header and footer. The decorator pattern allows for adding or changing the behavior of the BasicPage
object dynamically, without changing its class.
Template Pattern
In a web application, you might want to define a common structure for rendering a web page, such as rendering the header, body, and footer. However, each page might have a different header, body, and footer, so you would use the Template Pattern to define the common structure and allow each page to provide its own implementation. Here is an example:
abstract class WebPage { public void display() { // Step 1: Render the header renderHeader(); // Step 2: Render the body renderBody(); // Step 3: Render the footer renderFooter(); } protected abstract void renderHeader(); protected abstract void renderBody(); protected abstract void renderFooter(); } class HomePage extends WebPage { @Override protected void renderHeader() { System.out.println("Home Page Header"); } @Override protected void renderBody() { System.out.println("Home Page Body"); } @Override protected void renderFooter() { System.out.println("Home Page Footer"); } } class AboutPage extends WebPage { @Override protected void renderHeader() { System.out.println("About Page Header"); } @Override protected void renderBody() { System.out.println("About Page Body"); } @Override protected void renderFooter() { System.out.println("About Page Footer"); } } public class TemplatePatternExample { public static void main(String[] args) { WebPage homePage = new HomePage(); homePage.display(); // Output: // Home Page Header // Home Page Body // Home Page Footer WebPage aboutPage = new AboutPage(); aboutPage.display(); // Output: // About Page Header // About Page Body // About Page Footer } }
In this example, the WebPage
abstract class defines a template method display
that consists of three steps: render the header, render the body, and render the footer. The renderHeader
, renderBody
, and renderFooter
methods are declared as abstract and left for subclasses to implement.
The HomePage
and AboutPage
classes are concrete subclasses of WebPage
that implement the abstract methods. When a HomePage
or AboutPage
object is created and its display
method is called, the header, body, and footer are rendered according to the specific implementation of each page.
The Template Pattern provides a way to define the structure of a web page, and to allow subclasses to provide the specific implementation of each part. This pattern makes it easy to maintain and extend the code, as new pages can be added or existing pages can be changed, without affecting the structure of the web page.
These are just some examples for design patterns. To improve the maintainability and efficiency of your applications, you could try to read about more patterns. Maybe there are already best practice solutions for your problem.
Excellent. I did the same – started coding when I was around 12, worked in software off and on through university and then got recruited by a development company during the dotcom boom and never finished my senior year of undergrad. I’m now 18 years into a Systems Engineering role… and haven’t looked back:) Great blog you have here!