Design Patterns – part 2

Author Author:
Innokrea Team
Date of publication: 2024-11-13
Caterogies: Programming

Hello, today at Innokrea, we continue the topic of design patterns in computer science. If you are curious about previous articles regarding SOLID principles or design patterns, we encourage you to visit the links below:
https://www.innokrea.pl/wzorce-projektowe-czesc-1/
https://www.innokrea.com/solid-clean-code-in-object-oriented-programming/

Today, we will explore two new design patterns: Adapter and Memento.

 

Adapter

This pattern is used when we need to connect incompatible interfaces within a program. Creating what is known as an adapter and appropriately linking classes allows us to translate one message into another and facilitate integration between two classes. It is often used when the interface of a certain library or a piece of legacy code is incompatible with our project, or when we need to connect two fragments of a system.

Let’s assume we have an older piece of software that returns data from a sensor, such as temperature and humidity, in a textual format that is undesirable for modern applications. We receive data in the form of a text string, e.g., “22.55”. We would like to have the data in JSON format, which is standard for today’s web applications. Of course, in this example, we could modify our test code, but often this is not possible because it is part of an integrated library. The solution will therefore be the adapter pattern. Let’s take a look at an example.

SensorDataJsonParser Interface – Defines a method for obtaining data in JSON format.

LegacySensorDriver Class – Represents an old driver that provides data in a format that is unreadable by the new system, which we cannot modify.

LegacyToJsonSensorAdapter Class – An adapter that implements the SensorDataJsonParser interface and transforms data from LegacySensorDriver into JSON format.

 

Class Diagram for Adapter Example

Diagram 1 – Class Diagram for Our Adapter Example

 

Let’s also take a look at how the following implementation code looks.

The SensorDataJsonParser Interface defines the contract between the client class (Main in this case) and the Adapter.

public interface SensorDataJsonParser {
   JSONObject parseSensorDataToJson();
}

 

The LegacySensorDriver class contains the old code that we cannot modify, which provides data in an undesirable format.

public class LegacySensorDriver {
   public String getSensorData() {
       Random random = new Random();
       double temperature = 15 + random.nextDouble() * 20;
       double humidity = 20 + random.nextDouble() * 20;
       return String.format("%.1f,%.1f", temperature, humidity);
   }
}

public class LegacyToJsonSensorAdapter implements SensorDataJsonParser {
   private final LegacySensorDriver legacySensorDriver;

   public LegacyToJsonSensorAdapter(LegacySensorDriver legacySensorDriver) {
       this.legacySensorDriver = legacySensorDriver;
   }

   @Override
   public JSONObject parseSensorDataToJson() {
       String rawData = legacySensorDriver.getSensorData();
       String[] dataParts = rawData.split(",");
       double temperature = Double.parseDouble(dataParts[0]);
       double humidity = Double.parseDouble(dataParts[1]);

       JSONObject sensorDataJson = new JSONObject();
       sensorDataJson.put("temperature", temperature);
       sensorDataJson.put("humidity", humidity);

       return sensorDataJson;
   }
}

 

After passing the LegacySensorDriver object and calling the parseSensorDataToJson method (implemented by the interface), we receive data returned in the appropriate format, i.e., JSON.

public class Main {
   public static void main(String[] args) {
       LegacySensorDriver legacyDriver = new LegacySensorDriver();
       LegacyToJsonSensorAdapter adapter = new LegacyToJsonSensorAdapter(legacyDriver);
       JSONObject sensorDataJson = adapter.parseSensorDataToJson();
       System.out.println(sensorDataJson.toString(2));
   }
}

 

Memento

The Memento pattern is a behavioral pattern that allows the state of an object to be captured and restored later. It is useful in applications that need to support undo functionality, such as text editors or video games.

The Memento pattern consists of three main components:

  • Originator – A class that holds the current state of the object and methods to change this state, i.e., methods for saving its state in a Memento object and restoring it from that object.
  • Memento – A class that stores the state of the Originator object. Its role is to encapsulate the state of the Originator object, meaning that external classes do not have access to the internal details of the Originator object.
  • Caretaker – A class that manages the history of Memento objects. It handles the saving and restoring of the Originator’s states but does not have access to the internal state data.

 

Class diagram for the Memento pattern

Diagram 2 – Class diagram for the Memento pattern

 

Sequence diagram for the Memento pattern

Diagram 3 – Sequence diagram for the Memento pattern

 

Let’s take a look at how the code for the Memento pattern looks.

Using an interface ensures that the Caretaker class depends on an abstraction rather than a concrete implementation.

public interface IMemento {
   public String getSavedText();
}

 

The state of the Originator class is meant to be saved. To achieve this, a Memento class has been created within the object.

public class Originator {
   private String text;

   public void setText(String text) {
       this.text = text;
   }

   public String getText() {
       return text;
   }

   public IMemento saveToMemento() {
       return new Memento(text);
   }

   public void restoreFromMemento(IMemento memento) {
       if (!(memento instanceof Memento))
       {
           throw new IllegalArgumentException("Unknown memento class: " + memento.getClass());
       }
       this.text = memento.getSavedText();
   }

   private static class Memento implements IMemento{
       private String savedText;

       public Memento(String text) {
           this.savedText = text;
       }

       public String getSavedText() {
           return savedText;
       }
   }
}

 

The Caretaker class depends on the IMemento interface and stores a collection of the object’s state change history. When the save() method is called, it saves the object’s state in the collection using the Originator class and the method provided in that class. Restoring the state involves retrieving the history from the collection and overwriting the state of the Originator object.

public class Caretaker {
   private final Stack history = new Stack<>();
   private final Originator originator;

   public Caretaker(Originator originator) {
       this.originator = originator;
   }

   public void save() {
       history.push(this.originator.saveToMemento());
   }

   public void undo() {
       if (!history.isEmpty()) {
           this.originator.restoreFromMemento(history.pop());
           System.out.println("Restoring the state!");
       }
       else {
           System.out.println("No previous state");
       }
   }
}

 

The Main class, which demonstrates an example of saving and restoring the state.

public class Main {
   public static void main(String[] args) {
       Originator originator = new Originator();
       Caretaker careTaker = new Caretaker(originator);

       originator.setText("test1");
       careTaker.save();
       System.out.println(originator.getText());

       originator.setText("test2");
       careTaker.save();
       System.out.println(originator.getText());

       originator.setText("test3");
       System.out.println(originator.getText());
       careTaker.undo();
       System.out.println(originator.getText());
   }
}

 

Summary

Today, we explored two new design patterns from the behavioral category: Adapter and Memento. The Adapter pattern allows for the integration of incompatible interfaces, facilitating the connection of older components with modern applications. Meanwhile, the Memento pattern helps manage an object’s state, which is useful in situations that require undoing changes made within a program. If you’re interested in more patterns and programming tips, we encourage you to follow our blog. The code is available at our GitHub page.

 

Sources:

https://refactoring.guru/

See more on our blog:

User Identity and Access Management – What’s the Deal with IDP?

User Identity and Access Management – What’s the Deal with IDP?

What user identity is? Why managing access is essential for businesses? How an IDP (Identity Provider) works? You will find the answer to these questions in the article.

Security

Design Patterns – part 1

Design Patterns – part 1

Programmer, this article is for you! Grab a handful of useful information about design patterns.

Programming

Blockchain – Payments in the World of Cryptocurrencies

Blockchain – Payments in the World of Cryptocurrencies

Blockchain - discover the world of transactions, cryptocurrencies, and electronic payments.

FinancialSecurity