What are the Adapter and Memento Patterns in Swift?


In this article, you will learn about both the pattern in the Swift language. Also, you will see what are advantages of using these patterns. You will see examples of how you can adopt these patterns in your projects.

Using the adapter pattern, you can design a system that allows two different interfaces to work together or communicate between them. You can implement this design pattern with the class or structure by conforming to a certain protocol. Then, you can use an instance of that class or struct to interact with an object that conforms to a different protocol.

Using the memento pattern, you can design a system that allows an object to be restored to a previous state. You can implement this design pattern with the class or structure that can store the states of an object. To manage the stores, you need to create methods for storing and restoring the states.

Both of these design patterns are used to make your code more flexible and reusable by decoupling objects from one another.

Adapter Pattern

When there is an existing class or structure that offers specific functionality, the adapter pattern is employed. You must utilize it in a manner that is incompatible with its current interface, though. The existing class or struct and the new interface can coexist in harmony thanks to the adapter class or struct, which serves as a bridge between them.

Think about the case where you already have a class that offers a list of items. The class, however, is not intended for usage with a table view in iOS. You can develop an adapter class that complies with the UITableViewDataSource protocol to utilize the class with a table view. To supply data for the table view, this adapter class depends on the existing class.

Example

Here is an example of the Adapter pattern in Swift

protocol LegacyData {
   func getData() -> [String]
}
class LegacyDataProvider: LegacyData {
   func getData() -> [String] {
      return ["item1", "item2", "item3"]
   }
}
protocol NewData {
   func getData() -> [String]
}
class NewDataAdapter: NewData {
   private let legacyData: LegacyData
    
   init(legacyData: LegacyData) {
      self.legacyData = legacyData
   }
   
   func getData() -> [String] {
      return legacyData.getData()
   }
}
let legacyDataProvider = LegacyDataProvider()
let newDataAdapter = NewDataAdapter(legacyData: legacyDataProvider)
print(newDataAdapter.getData())

Output

["item1", "item2", "item3"]

Here, LegacyDataProvider is an existing class that provides data in a certain format, but it is not compatible with the new interface that is defined by the NewData protocol. The NewDataAdapter class acts as a bridge between the two, allowing LegacyDataProvider to be used with the new interface.

Some advantages of using the adapter pattern include

  • It allows the integration of incompatible classes

  • It allows for the separation of interface and implementation

  • It allows the reuse of existing classes

  • It allows for the easy addition of new classes

Memento Pattern

When an object's state needs to be saved so that it can be restored later, the Memento pattern is employed. The object's state is stored in the Memento class or struct, and it provides methods for generating, restoring, and accessing the memento.

For example, consider a scenario where you are building a drawing application and you want to allow users to undo and redo their actions. You can use the Memento pattern to store the state of the drawing canvas at each step so that the user can easily undo and redo their actions.

Example

Here is an example of the Memento pattern in Swift

class DrawingCanvas {   
   private var shapes: [Shape] = []
    
   func addShape(_ shape: Shape) {
      shapes.append(shape)
   }    
   func undo() -> [Shape] {
      return Memento.undo(from: &shapes)
   }  
   func redo() -> [Shape] {
      return Memento.redo(from: &shapes)
   }   
   func save() {
      Memento.save(from: shapes)
   }   
   private struct Memento {
      private static var undoStack: [[Shape]] = []
      private static var redoStack: [[Shape]] = []
        
      static func undo(from shapes: inout [Shape]) -> [Shape] {
         guard !undoStack.isEmpty else { return [] }
         let currentState = undoStack.removeLast()
         redoStack.append(shapes)
         return currentState
      }
        
      static func redo(from shapes: inout [Shape]) -> [Shape] {
         guard !redoStack.isEmpty else { return [] }
         let currentState = redoStack.removeLast()
         undoStack.append(shapes)
         return currentState
      }
        
      static func save(from shapes: [Shape]) {
         undoStack.append(shapes)
         redoStack.removeAll()
      }
   }   
   struct Shape {
        
   }
}

Here, the DrawingCanvas class has a private memento property which is an instance of the Memento struct. This property is responsible for saving the state of the DrawingCanvas class at each step. The DrawingCanvas has undo and redo methods that use the undo and redo methods of the memento to restore the state of the canvas.

Some advantages of using the Memento pattern include

  • It allows the undoing and redoing of actions

  • It allows the easy saving and restoring of the state of an object

  • It allows the decoupling of the object and its state

  • It allows the separation of concerns

Conclusion

Two design patterns that can help developers in resolving typical issues with software development are the Adapter and Memento patterns. While the Memento pattern enables an item to save and restore its former state, the Adapter pattern enables objects with incompatible interfaces to cooperate. Both patterns encourage loose coupling and the separation of concerns, and they can help you create code that is more effectively designed. It's crucial to know when to apply these patterns and how to do so correctly in your codebase.

Updated on: 28-Feb-2023

208 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements