Pyłek to strukturalny wzorzec projektowy umożliwiający obsługę wielkich ilości obiektów przy jednoczesnej oszczędności pamięci.
Wzorzec Pyłek umożliwia zmniejszenie wymogów w zakresie pamięci RAM poprzez współdzielenie części opisu stanu przez wiele obiektów. Innymi słowy Pyłek przechowuje w pamięci podręcznej te dane, które są wspólne dla wielu różnych obiektów.
Przykład koncepcyjny
Poniższy przykład ilustruje strukturę wzorca Pyłek ze szczególnym naciskiem na następujące kwestie:
Z jakich składa się klas?
Jakie role pełnią te klasy?
W jaki sposób elementy wzorca są ze sobą powiązane?
Poznawszy strukturę wzorca będzie ci łatwiej zrozumieć następujący przykład, oparty na prawdziwym przypadku użycia Swift.
Example.swift: Przykład koncepcyjny
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: Wynik działania
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
Przykład z prawdziwego życia
Example.swift: Przykład z prawdziwego życia
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: Cached object of the appearance 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 a predefined appearance of every 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: Wynik działania
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