FlyWeight Design Pattern in Swift


Greetings, traveler!

We continue to explore Structural Design Patterns. In this article, we will discuss the FlyWeight Pattern. Although this pattern may initially seem challenging, its application can be pretty specific.

So, what exactly is the FlyWeight pattern? It’s a structural pattern that optimizes memory usage by allowing more objects to fit within the available RAM. This efficiency is achieved by sharing a common state among objects rather than duplicating the same data multiple times.

The FlyWeight pattern suggests avoiding storing external state within a class and instead passing it as parameters to methods. This allows the same object to be reused in different contexts. But if you require multiple instances of an internal state object, consider using the Singleton or Factory patterns.

Example of usage

Let’s take the example of a car dealership as an illustration. Let’s say we only sell one car brand, but that brand has three different body styles: sedan, hatchback, and coupe.

enum CardKind: String {
    case sedan, hatchback, coupe
}

The car will act as a flyweight, with one property indicating its kind.

struct CarFlyWeight {
    let kind: CardKind
}

We must create a warehouse for these cars to act as a factory and cache data.

protocol CarFactoryProtocol {
    func makeCar(_ kind: CardKind) -> CarFlyWeight
}

final class WareHouse: CarFactoryProtocol {
    
    private var cache: [CardKind: CarFlyWeight] = [:]
    
    func makeCar(_ kind: CardKind) -> CarFlyWeight {
        guard let car = cache[kind] else {
            let car = CarFlyWeight(kind: kind)
            cache[kind] = car
            return car
        }
        
        return car
    }
    
}

Now, we will create our dealership. We have access to the warehouse and a dictionary containing the necessary information. We need to know the deal’s ID and the kind of car we are selling. With the dealership’s help, we can create new deals and view a report on all existing ones.

final class CarDealership {
    
    private let factory: CarFactoryProtocol
    private var deals: [Int: CarFlyWeight] = [:]
    
    init(factory: CarFactoryProtocol) {
        self.factory = factory
    }
    
    func makeDeal(id: Int, car: CardKind) {
        deals[id] = factory.makeCar(car)
    }
    
    func report() {
        deals.forEach { id, car in
            print("Deal id: \(id); Car: \(car)")
        }
    }
    
}

And now we can use our code.

let dealership = CarDealership(factory: WareHouse())
dealership.makeDeal(id: 1, car: .coupe)
dealership.report()

Conclusion

The Flyweight pattern is a useful design pattern that helps improve performance in situations where many objects share similar data. You can use this pattern to simplify your code, but it can become more complex, which should be avoided.

This concludes the discussion of this pattern. The following article will discuss another structural design pattern, the Proxy.