 
                Swift 享元模式讲解和代码示例
享元是一种结构型设计模式, 它允许你在消耗少量内存的情况下支持大量对象。
模式通过共享多个对象的部分状态来实现上述功能。 换句话来说, 享元会将不同对象的相同数据进行缓存以节省内存。
复杂度:
流行度:
使用示例: 享元模式只有一个目的: 将内存消耗最小化。 如果你的程序没有遇到内存容量不足的问题, 则可以暂时忽略该模式。
识别方法: 享元可以通过构建方法来识别, 它会返回缓存对象而不是创建新的对象。
                                                            以下示例可在
                                Swift Playgrounds
                                上使用。
                                                    
                        
                                                            感谢
                                Alejandro Mohamad 创建了Playground版本。
                                                    
                    概念示例
本例说明了享元设计模式的结构并重点回答了下面的问题:
- 它由哪些类组成?
- 这些类扮演了哪些角色?
- 模式中的各个元素会以何种方式相互关联?
了解该模式的结构后, 你可以更轻松地理解下面基于真实世界的 Swift 应用案例。
Example.swift: 概念示例
import XCTest
/// The Flyweight stores a common portion of the state (also called intrinsic
/// state) that belongs to multiple real business entities. The Flyweight
/// accepts the rest of the state (extrinsic state, unique for each entity) via
/// its method parameters.
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")
    }
}
/// The Flyweight Factory creates and manages the Flyweight objects. It ensures
/// that flyweights are shared correctly. When the client requests a flyweight,
/// 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()
    }
}
class FlyweightConceptual: XCTestCase {
    func testFlyweight() {
        /// The client code usually creates a bunch of pre-populated flyweights
        /// in the initialization stage of the application.
        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])
        /// The client code either stores or calculates extrinsic state and
        /// passes it to the flyweight's methods.
        flyweight.operation(uniqueState: [plates, owner])
    }
}
Output.txt: 执行结果
FlyweightFactory: I have 5 flyweights:
Mercedes BenzC500red
ChevroletCamaro2018pink
Mercedes BenzC300black
BMWX6white
BMWM5red
Client: Adding a car to database.
FlyweightFactory: Reusing existing flyweight.
Flyweight: Displaying shared (["BMW", "M5", "red"]) and unique (["CL234IR", "James Doe"] state.
Client: Adding a car to database.
FlyweightFactory: Can't find a flyweight, creating new one.
Flyweight: Displaying shared (["BMW", "X1", "red"]) and unique (["CL234IR", "James Doe"] state.
FlyweightFactory: I have 6 flyweights:
Mercedes BenzC500red
BMWX1red
ChevroletCamaro2018pink
Mercedes BenzC300black
BMWX6white
BMWM5red
真实世界示例
Example.swift: 真实世界示例
import XCTest
import UIKit
class FlyweightRealWorld: XCTestCase {
    func testFlyweightRealWorld() {
        let maineCoon = Animal(name: "Maine Coon",
                               country: "USA",
                               type: .cat)
        let sphynx = Animal(name: "Sphynx",
                            country: "Egypt",
                            type: .cat)
        let bulldog = Animal(name: "Bulldog",
                             country: "England",
                             type: .dog)
        print("Client: I created a number of objects to display")
        /// Displaying objects for the 1-st time.
        print("Client: Let's show animals for the 1st time\n")
        display(animals: [maineCoon, sphynx, bulldog])
        /// Displaying objects for the 2-nd time.
        ///
        /// Note: The cached appearance object will be reused this time.
        print("\nClient: I have a new dog, let's show it the same way!\n")
        let germanShepherd = Animal(name: "German Shepherd",
                              country: "Germany",
                              type: .dog)
        display(animals: [germanShepherd])
    }
}
extension FlyweightRealWorld {
    func display(animals: [Animal]) {
        let cells = loadCells(count: animals.count)
        for index in 0..<animals.count {
            cells[index].update(with: animals[index])
        }
        /// Using cells...
    }
    func loadCells(count: Int) -> [Cell] {
        /// Emulates behavior of a table/collection view.
        return Array(repeating: Cell(), count: count)
    }
}
enum Type: String {
    case cat
    case dog
}
class Cell {
    private var animal: Animal?
    func update(with animal: Animal) {
        self.animal = animal
        let type = animal.type.rawValue
        let photos = "photos \(animal.appearance.photos.count)"
        print("Cell: Updating an appearance of a \(type)-cell: \(photos)\n")
    }
}
struct Animal: Equatable {
    /// This is an external context that contains specific values and an object
    /// with a common state.
    ///
    /// Note: The object of appearance will be lazily created when it is needed
    let name: String
    let country: String
    let type: Type
    var appearance: Appearance {
        return AppearanceFactory.appearance(for: type)
    }
}
struct Appearance: Equatable {
    /// This object contains the predefined appearance for each cell.
    let photos: [UIImage]
    let backgroundColor: UIColor
}
extension Animal: CustomStringConvertible {
    var description: String {
        return "\(name), \(country), \(type.rawValue) + \(appearance.description)"
    }
}
extension Appearance: CustomStringConvertible {
    var description: String {
        return "photos: \(photos.count), \(backgroundColor)"
    }
}
class AppearanceFactory {
    private static var cache = [Type: Appearance]()
    static func appearance(for key: Type) -> Appearance {
        guard cache[key] == nil else {
            print("AppearanceFactory: Reusing an existing \(key.rawValue)-appearance.")
            return cache[key]!
        }
        print("AppearanceFactory: Can't find a cached \(key.rawValue)-object, creating a new one.")
        switch key {
        case .cat:
            cache[key] = catInfo
        case .dog:
            cache[key] = dogInfo
        }
        return cache[key]!
    }
}
extension AppearanceFactory {
    private static var catInfo: Appearance {
        return Appearance(photos: [UIImage()], backgroundColor: .red)
    }
    private static var dogInfo: Appearance {
        return Appearance(photos: [UIImage(), UIImage()], backgroundColor: .blue)
    }
}
Output.txt: 执行结果
Client: I created a number of objects to display
Client: Let's show animals for the 1st time
AppearanceFactory: Can't find a cached cat-object, creating a new one.
Cell: Updating an appearance of a cat-cell: photos 1
AppearanceFactory: Reusing an existing cat-appearance.
Cell: Updating an appearance of a cat-cell: photos 1
AppearanceFactory: Can't find a cached dog-object, creating a new one.
Cell: Updating an appearance of a dog-cell: photos 2
Client: I have a new dog, let's show it the same way!
AppearanceFactory: Reusing an existing dog-appearance.
Cell: Updating an appearance of a dog-cell: photos 2