Flyweight – Design Patterns in Swift

Introduction

Flyweight is a structural design pattern that allows programs to support vast quantities of objects by keeping their memory consumption low. A Flyweight object can store an intrinsic state that is invariant, context-independent, and shareable (for example, the code of character ‘A’ in a given character set). Moreover, it also provides an interface for passing in an extrinsic state that is variant, context-dependent, and can’t be shared (for example, the position of character ‘A’ in a text document).

Flyweight Pattern – UML

Use Flyweight Pattern in Swift

The Flyweight stores a common portion of the state (also called intrinsic state) that belongs to multiple real business entities. It also accepts the rest of the state (extrinsic state, unique for each entity) via its method parameters.

import XCTest

class Flyweight {

    private let sharedState: [String]

    init(sharedState: [String]) {
        self.sharedState = sharedState
    }

    func operation(uniqueState: [String]) {
        print("Flyweight: Displaying shared (\(sharedState)) and unique (\(uniqueState) state.\n")
    }
}

In addition, a dedicated Factory creates and manages the Flyweight objects. It ensures that flyweights are shared correctly. When the client requests an instance, the factory either returns an existing instance or creates a new one, if it doesn’t exist yet.

class FlyweightFactory {

    private var flyweights: [String: Flyweight]

    init(states: [[String]]) {

        var flyweights = [String: Flyweight]()

        for state in states {
            flyweights[state.key] = Flyweight(sharedState: state)
        }

        self.flyweights = flyweights
    }

    /// Returns an existing Flyweight with a given state or creates a new one.
    func flyweight(for state: [String]) -> Flyweight {

        let key = state.key

        guard let foundFlyweight = flyweights[key] else {

            print("FlyweightFactory: Can't find a flyweight, creating new one.\n")
            let flyweight = Flyweight(sharedState: state)
            flyweights.updateValue(flyweight, forKey: key)
            return flyweight
        }
        print("FlyweightFactory: Reusing existing flyweight.\n")
        return foundFlyweight
    }

    func printFlyweights() {
        print("FlyweightFactory: I have \(flyweights.count) flyweights:\n")
        for item in flyweights {
            print(item.key)
        }
    }
}

extension Array where Element == String {

    /// Returns a Flyweight's string hash for a given state.
    var key: String {
        return self.joined()
    }
}

The client code usually creates a bunch of pre-populated flyweights in the initialization stage of the application.

class FlyweightConceptual: XCTestCase {

    func testFlyweight() {

        let factory = FlyweightFactory(states:
        [
            ["Chevrolet", "Camaro2018", "pink"],
            ["Mercedes Benz", "C300", "black"],
            ["Mercedes Benz", "C500", "red"],
            ["BMW", "M5", "red"],
            ["BMW", "X6", "white"]
        ])

        factory.printFlyweights()

        addCarToPoliceDatabase(factory,
                "CL234IR",
                "James Doe",
                "BMW",
                "M5",
                "red")

        addCarToPoliceDatabase(factory,
                "CL234IR",
                "James Doe",
                "BMW",
                "X1",
                "red")

        factory.printFlyweights()
    }

    func addCarToPoliceDatabase(
            _ factory: FlyweightFactory,
            _ plates: String,
            _ owner: String,
            _ brand: String,
            _ model: String,
            _ color: String) {

        print("Client: Adding a car to database.\n")

        let flyweight = factory.flyweight(for: [brand, model, color])

        flyweight.operation(uniqueState: [plates, owner])
    }
}

The client code either stores or calculates extrinsic state and passes it to the flyweight’s methods.

Conclusion

In summary, Flyweight can be recognized by a creation method that returns cached objects instead of creating new ones. This pattern has a single purpose: minimizing memory intake. If your program doesn’t struggle with a shortage of RAM, then you might just ignore this pattern for a while.

Leave a Reply

Your email address will not be published. Required fields are marked *