Design patterns: Part Two - A brief explanation of structural pattern
Tags: architecture, designpatterns, javascript, swift
Published: 2024-06-16 23:06:30

The picture was taken from [boredpanda](https://www.boredpanda.com/digital-art-cats-brutalist-architecture-cats-of-brutalism/).


### Hello everyone! 
Starting my second post with one of the most basic but never unnecessary topics; Design patterns.

The [previous post](https://dev.to/andres_paladines/design-patterns-part-one-a-brief-explanation-of-creational-pattern-5507) targets `Creational Patterns`, and this one will focus on `Structural Patterns`.

Let's start listing them again:

```
.
├── Creational Patterns (previous post)
│   ├── Singleton
│   ├── Factory Method
│   ├── Builder
│   ├── Abstract Factory
│   ├── Prototype
├── Structural Patterns (current post)
│   ├── Adapter
│   ├── Decorator
│   ├── Facade
│   ├── Composite
│   ├── Proxy
├── Behavioral Patterns (Working on it)
│   ├── Observer
│   ├── Strategy
│   ├── Command
│   ├── Chain of Responsibility
│   ├── Mediator
│   ├── State
│   ├── Template Method
.
```

# Structural Patterns
This post will focus on `Structural Patterns` and how to construct them with swift and javascript. So, Let's start!
## What does that mean by Structural pattern?
It is how classes and objects are composed to form larger structures. These patterns help ensure that if one part of a system changes, the entire structure doesn’t need to change. They facilitate design by identifying a simple way to realize relationships among entities.

---

## Adapter
Basically, it allows incompatible interfaces to work together by acting as a bridge.
**Use case:** Adapting a UIKit View inside SwiftUI.

#### Swift (Adapting UIKit to SwiftUI):
```swift
// This is simple SwiftUI view representing a label
class UILabelView: UIView {
    private let label: UILabel

    init(text: String) {
        self.label = UILabel()
        super.init(frame: .zero)
        self.label.text = text
        self.label.textAlignment = .center
        self.addSubview(label)
        label.ytanslatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            label.centerXAnchor.constraint(equalTo: self.centerXAnchor),
            label.centerYAnchor.constraint(equalTo: self.centerYAnchor)
        ])
}

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented.")
    }
}
```

**Use case:** Adapting a Firebase logger to have extra features in case it cannot be extended (Open close principle, not edit but extend object functionalities).

#### JavaScript:
```javascript
class FirebaseAnalytics {
    logEvent(name, parameters) {
        console.log(`Logging event to Firebase: ${name}, parameters: ${JSON.stringify(parameters)}`);
    }
}

class FirebaseAdapter {
    constructor() {
        this.firebaseAnalytics = new FirebaseAnalytics();
    }

    trackEvent(name, properties) {
        this.firebaseAnalytics.logEvent(name, properties);
    }

    trackUser(name, email) {
        this.trackEvent(name, { method: email });
    }

}

// Usage
const analyticsService = new FirebaseAdapter();
analyticsService.trackEvent("user_signup", { method: "email" });
analyticsService.trackUser("John", "Doe");

```

---

## Decorator
Adds extra responsibilities to an object dynamically without altering its core functionality.
**Use case:** Adding different logging levels (info, debug, error) dynamically.

#### Swift:
```swift
protocol Logger {
    func log(message: String)
}

class BasicLogger: Logger {
    func log(message: String) {
        print(message)
    }
}

class LoggerDecorator: Logger {
    private let wrapped: Logger
    
    init(_ wrapped: Logger) {
        self.wrapped = wrapped
    }
    
    func log(message: String) {
        wrapped.log(message: message)
    }
}

class InfoLogger: LoggerDecorator {
    override func log(message: String) {
        super.log(message: "INFO: \(message)")
    }
}

class ErrorLogger: LoggerDecorator {
    override func log(message: String) {
        super.log(message: "ERROR: \(message)")
    }
}

// Usage
let logger: Logger = InfoLogger(ErrorLogger(BasicLogger()))
let loggerInfo: Logger = InfoLogger(BasicLogger())
logger.log(message: "An error occurred") //ERROR: INFO: An error occurred
loggerInfo.log(message: "An info message was sent") //INFO: An info message was sent
```

#### JavaScript:
```javascript
class BasicLogger {
    log(message) {
        console.log(message);
    }
}

class LoggerDecorator {
    constructor(wrapped) {
        this.wrapped = wrapped;
    }

    log(message) {
        this.wrapped.log(message);
    }
}

class InfoLogger extends LoggerDecorator {
    log(message) {
        super.log(`INFO: ${message}`);
    }
}

class ErrorLogger extends LoggerDecorator {
    log(message) {
        super.log(`ERROR: ${message}`);
    }
}

// Usage
const logger = new ErrorLogger(new InfoLogger(new BasicLogger()));
const loggerInfo = new InfoLogger(new BasicLogger());
logger.log("An error occurred");
loggerInfo.log("This is an info message");

```

---

## Facade
With this, we create a simple interface that covers a complex object or objects, making it/them easier to use.
**Use case:** Providing a simplified interface to a complex set of APIs.

#### Swift:
```swift
class ComplexSystemA {
    func operationA1() { print("Operation A1") }
    func operationA2() { print("Operation A2") }
}

class ComplexSystemB {
    func operationB1() { print("Operation B1") }
    func operationB2() { print("Operation B2") }
}

class Facade {
    private let systemA = ComplexSystemA()
    private let systemB = ComplexSystemB()
    
    func operationOne() {
        systemA.operationA1()
        systemB.operationB1()
    }

    func operationTwo() {
        systemA.operationA2()
        systemB.operationB2()
    }
}

// Usage
let facade = Facade()
facade.operationOne()

```

#### JavaScript:
```javascript
class ComplexSystemA {
    operationA1() { console.log("Operation A1"); }
    operationA2() { console.log("Operation A2"); }
}

class ComplexSystemB {
    operationB1() { console.log("Operation B1"); }
    operationB2() { console.log("Operation B2"); }
}

class Facade {
    constructor() {
        this.systemA = new ComplexSystemA();
        this.systemB = new ComplexSystemB();
    }

    simplifiedOperationOne() {
        this.systemA.operationA1();
        this.systemB.operationB1();
    }
}

// Usage
const facade = new Facade();
facade.simplifiedOperation();

```

---

## Composite
This pattern allow us to compose a tree of objects to represent part or whole hierarchies.
**Use case:** Implementing a File System Structure:
In a file system, files and directories can be treated uniformly. Both can be added, removed, or displayed. Directories can contain files or other directories.

#### Swift:
```swift
//The protocol will define a common interface for leaf and composite objects.
protocol FileSystemComponent {
    var name: String { get }
    func display(indent: String)
}

//File represents the leaf objects
class File: FileSystemComponent {
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    func display(indent: String) {
        print("\(indent)- \(name)")
    }
}

//Directory represents the composite objects
class Directory: FileSystemComponent {
    var name: String
//`components` represents a list of new leafs or composite objects
    private var components = [FileSystemComponent]()
    
    init(name: String) {
        self.name = name
    }
    
    func add(component: FileSystemComponent) {
        components.append(component)
    }
    
    func display(indent: String) {
        print("\(indent)+ \(name)")
        for component in components {
            component.display(indent: indent + "  ")
        }
    }
}

//Finally, the usage of these objecs:
let file1 = File(name: "File1.txt")
let file2 = File(name: "File2.txt")
let file3 = File(name: "File3.txt")

let directory1 = Directory(name: "Directory1")
directory1.add(component: file1)

let directory2 = Directory(name: "Directory2")
directory2.add(component: file2)
directory2.add(component: file3)

let rootDirectory = Directory(name: "RootDirectory")
rootDirectory.add(component: directory1)
rootDirectory.add(component: directory2)

rootDirectory.display(indent: "|")

/*
The result will be the following:
|  + Directory1
|    - File1.txt
|  + Directory2
|    - File2.txt
|    - File3.txt
*/
```

#### JavaScript:
```javascript
class FileSystemComponent {
    constructor(name) {
        this.name = name;
    }

    display(indent) {
        throw new Error("This method must be overridden!");
    }
}

class File extends FileSystemComponent {
    constructor(name) {
        super(name);
    }

    display(indent) {
        console.log(`${indent}- ${this.name}`);
    }
}

class Directory extends FileSystemComponent {
    constructor(name) {
        super(name);
        this.components = [];
    }

    add(component) {
        this.components.push(component);
    }

    display(indent) {
        console.log(`${indent}+ ${this.name}`);
        this.components.forEach(component => {
            component.display(indent + "  ");
        });
    }
}

const file1 = new File("File1.txt");
const file2 = new File("File2.txt");
const file3 = new File("File3.txt");

const directory1 = new Directory("Directory1");
directory1.add(file1);

const directory2 = new Directory("Directory2");
directory2.add(file2);
directory2.add(file3);

const rootDirectory = new Directory("RootDirectory");
rootDirectory.add(directory1);
rootDirectory.add(directory2);

rootDirectory.display("|");

/*
The result will be the following:
|  + Directory1
|    - File1.txt
|  + Directory2
|    - File2.txt
|    - File3.txt
*/
```

---

## Proxy
This pattern is used to provide controlled access to an object by creating a proxy object that performs actions such as access control, lazy initialization, logging, or caching.
**Use case:** Image is downloaded from a URL, cached on the device, and displayed in a SwiftUI view.

#### Swift:
```swift
protocol Image {
    func display() -> UIImage?
}

class RealImage: Image {
    private var filename: String
    private var url: URL
    private var image: UIImage?
    
    init(filename: String, url: URL) {
        self.filename = filename
        self.url = url
        self.image = loadImageFromDisk(filename: filename)
        if self.image == nil {
            self.image = loadImageFromURL(url: url)
        }
    }
    
    private func loadImageFromDisk(filename: String) -> UIImage? {
        let path = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
        let filePath = path.appendingPathComponent(filename)
        return UIImage(contentsOfFile: filePath.path)
    }
    
    private func loadImageFromURL(url: URL) -> UIImage? {
        guard let data = try? Data(contentsOf: url), let image = UIImage(data: data) else {
            return nil
        }
        saveImageToDisk(image: image, filename: filename)
        return image
    }
    
    private func saveImageToDisk(image: UIImage, filename: String) {
        let path = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
        let filePath = path.appendingPathComponent(filename)
        if let data = image.jpegData(compressionQuality: 1.0) {
            try? data.write(to: filePath)
        }
    }
    
    func display() -> UIImage? {
        return image
    }
}

class ProxyImage: Image {
    private var realImage: RealImage?
    private var filename: String
    private var url: URL
    
    init(filename: String, url: URL) {
        self.filename = filename
        self.url = url
    }
    
    func display() -> UIImage? {
        if realImage == nil {
            realImage = RealImage(filename: filename, url: url)
        }
        return realImage?.display()
    }
}

//Presenting ProxyImage in a SwiftUI view
struct ContentView: View {
    @State private var image: UIImage? = nil
    private let proxyImage = ProxyImage(filename: "kitty.jpg", url: URL(string: "https://www.boredpanda.com/blog/wp-content/uploads/2021/06/CK9sRAEMEo1-png__605.jpg")!)
    
    var body: some View {
        VStack {
            if let image = image {
                Image(uiImage: image)
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(width: 300, height: 300)
            } else {
                Text("Loading image...")
                    .onAppear {
                        self.image = self.proxyImage.display()
                    }
            }
        }
    }
}

#Preview {
    ContentView()
}

```

#### Here we'll create an entire web page to make understandable and testable at once.
#### JavaScript:
```javascript
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Proxy Pattern Example</title>
    <style>
        img {
            max-width: 300px;
            height: auto;
        }
    </style>
</head>
<body>
    <div id="app">
        <p>Loading image...</p>
    </div>

    <script>
        class Image {
            display(callback) {
                throw new Error("This method must be overridden!");
            }
        }

        class RealImage extends Image {
            constructor(filename, url) {
                super();
                this.filename = filename;
                this.url = url;
                this.image = this.loadImageFromLocalStorage(filename);
                if (!this.image) {
                    this.loadImageFromURL(url, (img) => {
                        this.image = img;
                        this.saveImageToLocalStorage(filename, img.src);
                        callback(img);
                    });
                } else {
                    callback(this.image);
                }
            }

            loadImageFromLocalStorage(filename) {
                const imageData = localStorage.getItem(filename);
                if (imageData) {
                    const image = new window.Image();
                    image.src = imageData;
                    return image;
                }
                return null;
            }

            loadImageFromURL(url, callback) {
                const xhr = new XMLHttpRequest();
                xhr.open('GET', url, true);
                xhr.responseType = 'blob';
                xhr.onload = function () {
                    if (xhr.status === 200) {
                        const reader = new FileReader();
                        reader.onloadend = function () {
                            const image = new window.Image();
                            image.src = reader.result;
                            callback(image);
                        }
                        reader.readAsDataURL(xhr.response);
                    }
                };
                xhr.send();
            }

            saveImageToLocalStorage(filename, data) {
                localStorage.setItem(filename, data);
            }

            display(callback) {
                if (this.image) {
                    callback(this.image);
                }
            }
        }

        class ProxyImage extends Image {
            constructor(filename, url) {
                super();
                this.realImage = null;
                this.filename = filename;
                this.url = url;
            }

            display(callback) {
                if (this.realImage === null) {
                    this.realImage = new RealImage(this.filename, this.url, callback);
                } else {
                    this.realImage.display(callback);
                }
            }
        }

        document.addEventListener("DOMContentLoaded", () => {
            const proxyImage = new ProxyImage("example.jpg", "./gato2.jpg");
            proxyImage.display((imageElement) => {
                const app = document.getElementById("app");
                app.innerHTML = "";  // Clear the loading text
                app.appendChild(imageElement);
            });
        });
    </script>
</body>
</html>
```

These examples shows how to implement and use each of the specified structural patterns in Swift and JavaScript with use cases.

Next post will focus on `Behavioral Patterns`. See you next time!