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:

  1. Singleton Pattern
  2. Factory Pattern
  3. Observer Pattern
  4. Decorator Pattern
  5. Template Pattern

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:

  1. 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).
  2. 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.
  3. 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.
  4. 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:

  1. 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.
  2. 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.
  3. 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.

General Programming Tips: 5 Important Design Patterns

Beitragsnavigation


Ein Gedanke zu „General Programming Tips: 5 Important Design Patterns

  1. 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!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert