Swift Language
Ontwerppatronen - Creatief
Zoeken…
Invoering
Ontwerppatronen zijn algemene oplossingen voor problemen die vaak voorkomen bij softwareontwikkeling. Hierna volgen sjablonen van gestandaardiseerde best practices bij het structureren en ontwerpen van code, evenals voorbeelden van gemeenschappelijke contexten waarin deze ontwerppatronen geschikt zijn.
Creatieve ontwerppatronen abstraheren de instantiatie van objecten om een systeem onafhankelijker te maken van het proces van creatie, compositie en representatie.
eenling
Singletons zijn een vaak gebruikt ontwerppatroon dat bestaat uit een enkele instantie van een klasse die door een programma wordt gedeeld.
In het volgende voorbeeld maken we een static
eigenschap die een instantie van de klasse Foo
. Onthoud dat een static
eigenschap wordt gedeeld tussen alle objecten van een klasse en niet kan worden overschreven door subklasse.
public class Foo
{
static let shared = Foo()
// Used for preventing the class from being instantiated directly
private init() {}
func doSomething()
{
print("Do something")
}
}
Gebruik:
Foo.shared.doSomething()
Vergeet niet de private
initialisatie te onthouden:
Dit zorgt ervoor dat uw singletons echt uniek zijn en voorkomt dat externe objecten hun eigen instanties van uw klas kunnen creëren door middel van toegangscontrole. Aangezien alle objecten een standaard openbare initialisatie hebben in Swift, moet u uw init negeren en privé maken. KrakenDev
Fabriek methode
In op klassen gebaseerde programmering is het fabrieksmethodepatroon een creatiepatroon dat fabrieksmethoden gebruikt om het probleem van het maken van objecten aan te pakken zonder de exacte klasse van het object dat moet worden gemaakt, op te geven. Wikipedia referentie
protocol SenderProtocol
{
func send(package: AnyObject)
}
class Fedex: SenderProtocol
{
func send(package: AnyObject)
{
print("Fedex deliver")
}
}
class RegularPriorityMail: SenderProtocol
{
func send(package: AnyObject)
{
print("Regular Priority Mail deliver")
}
}
// This is our Factory
class DeliverFactory
{
// It will be responsable for returning the proper instance that will handle the task
static func makeSender(isLate isLate: Bool) -> SenderProtocol
{
return isLate ? Fedex() : RegularPriorityMail()
}
}
// Usage:
let package = ["Item 1", "Item 2"]
// Fedex class will handle the delivery
DeliverFactory.makeSender(isLate:true).send(package)
// Regular Priority Mail class will handle the delivery
DeliverFactory.makeSender(isLate:false).send(package)
Hierdoor zijn we niet afhankelijk van de echte implementatie van de klasse, waardoor de sender()
volledig transparant is voor wie het gebruikt.
In dit geval hoeven we alleen maar te weten dat een afzender de bezorging afhandelt en de methode send()
blootlegt. Er zijn verschillende andere voordelen: het verminderen van klassenkoppeling, gemakkelijker te testen, gemakkelijker om nieuw gedrag toe te voegen zonder te hoeven veranderen wie het consumeert.
Binnen objectgeoriënteerd ontwerp bieden interfaces abstractielagen die een conceptuele verklaring van de code mogelijk maken en een barrière creëren die afhankelijkheden voorkomt. Wikipedia referentie
Waarnemer
Het waarnemerspatroon is waar een object, het onderwerp genoemd, een lijst bijhoudt van zijn afhankelijken, waarnemers genoemd, en hen automatisch op de hoogte brengt van eventuele statusveranderingen, meestal door een van hun methoden aan te roepen. Het wordt hoofdzakelijk gebruikt om gedistribueerde systemen voor gebeurtenisafhandeling te implementeren. Het waarnemerspatroon is ook een belangrijk onderdeel van het bekende architecturale patroon – view – controller (MVC). Wikipedia referentie
Kortom, het waarnemerspatroon wordt gebruikt wanneer u een object hebt dat waarnemers op de hoogte kan stellen van bepaald gedrag of veranderingen in de toestand.
Laten we eerst een algemene referentie (buiten een klasse) maken voor het Berichtencentrum:
let notifCentre = NotificationCenter.default
Geweldig nu kunnen we dit overal noemen. We willen dan een klas als waarnemer registreren ...
notifCentre.addObserver(self, selector: #selector(self.myFunc), name: "myNotification", object: nil)
Dit voegt de klasse toe als waarnemer voor "readForMyFunc". Het geeft ook aan dat de functie myFunc moet worden aangeroepen wanneer die melding wordt ontvangen. Deze functie moet in dezelfde klasse worden geschreven:
func myFunc(){
print("The notification has been received")
}
Een van de voordelen van dit patroon is dat u veel klassen als waarnemers kunt toevoegen en dus na één melding veel acties kunt uitvoeren.
De melding kan nu eenvoudig worden verzonden (of gepost als u dat wilt) vanuit vrijwel elke plaats in de code met de regel:
notifCentre.post(name: "myNotification", object: nil)
U kunt ook informatie doorgeven met de melding als een woordenboek
let myInfo = "pass this on"
notifCentre.post(name: "myNotification", object: ["moreInfo":myInfo])
Maar dan moet u een melding toevoegen aan uw functie:
func myFunc(_ notification: Notification){
let userInfo = (notification as NSNotification).userInfo as! [String: AnyObject]
let passedInfo = userInfo["moreInfo"]
print("The notification \(moreInfo) has been received")
//prints - The notification pass this on has been received
}
Keten van verantwoordelijkheid
In objectgeoriënteerd ontwerp is het patroon van verantwoordelijkheidsketen een ontwerppatroon dat bestaat uit een bron van
command
en een reeksprocessing
. Elkprocessing
bevat logica die de typen opdrachtobjecten definieert die het aankan; de rest wordt doorgegeven aan het volgendeprocessing
in de keten. Er bestaat ook een mechanisme voor het toevoegen van nieuweprocessing
aan het einde van deze keten. Wikipedia
De klassen opzetten die de verantwoordelijkheidsketen vormden.
Eerst maken we een interface voor alle processing
.
protocol PurchasePower {
var allowable : Float { get }
var role : String { get }
var successor : PurchasePower? { get set }
}
extension PurchasePower {
func process(request : PurchaseRequest){
if request.amount < self.allowable {
print(self.role + " will approve $ \(request.amount) for \(request.purpose)")
} else if successor != nil {
successor?.process(request: request)
}
}
}
Vervolgens maken we het command
.
struct PurchaseRequest {
var amount : Float
var purpose : String
}
Eindelijk, het creëren van objecten die de verantwoordelijkheidsketen vormden.
class ManagerPower : PurchasePower {
var allowable: Float = 20
var role : String = "Manager"
var successor: PurchasePower?
}
class DirectorPower : PurchasePower {
var allowable: Float = 100
var role = "Director"
var successor: PurchasePower?
}
class PresidentPower : PurchasePower {
var allowable: Float = 5000
var role = "President"
var successor: PurchasePower?
}
Initieer en keten het samen:
let manager = ManagerPower()
let director = DirectorPower()
let president = PresidentPower()
manager.successor = director
director.successor = president
Het mechanisme voor het koppelen van objecten hier is eigendomstoegang
Verzoek maken om het uit te voeren:
manager.process(request: PurchaseRequest(amount: 2, purpose: "buying a pen")) // Manager will approve $ 2.0 for buying a pen
manager.process(request: PurchaseRequest(amount: 90, purpose: "buying a printer")) // Director will approve $ 90.0 for buying a printer
manager.process(request: PurchaseRequest(amount: 2000, purpose: "invest in stock")) // President will approve $ 2000.0 for invest in stock
iterator
Bij computerprogrammering is een iterator een object waarmee een programmeur een container, met name lijsten, kan doorkruisen. Wikipedia
struct Turtle {
let name: String
}
struct Turtles {
let turtles: [Turtle]
}
struct TurtlesIterator: IteratorProtocol {
private var current = 0
private let turtles: [Turtle]
init(turtles: [Turtle]) {
self.turtles = turtles
}
mutating func next() -> Turtle? {
defer { current += 1 }
return turtles.count > current ? turtles[current] : nil
}
}
extension Turtles: Sequence {
func makeIterator() -> TurtlesIterator {
return TurtlesIterator(turtles: turtles)
}
}
En gebruiksvoorbeeld zou zijn
let ninjaTurtles = Turtles(turtles: [Turtle(name: "Leo"),
Turtle(name: "Mickey"),
Turtle(name: "Raph"),
Turtle(name: "Doney")])
print("Splinter and")
for turtle in ninjaTurtles {
print("The great: \(turtle)")
}
Bouwer patroon
Het bouwpatroon is een ontwerppatroon voor software voor het maken van objecten . In tegenstelling tot het abstracte fabriekspatroon en het fabrieksmethodiepatroon waarvan het de bedoeling is polymorfisme mogelijk te maken, is de bedoeling van het bouwpatroon om een oplossing te vinden voor het telescopische constructor-antipatroon. Het antipatroon van de telescopische constructor treedt op wanneer de toename van de combinatie van objectconstructorparameters leidt tot een exponentiële lijst van constructors. In plaats van het gebruik van meerdere constructors, gebruikt het builderpatroon een ander object, een builder, die elke initialisatieparameter stap voor stap ontvangt en vervolgens het resulterende geconstrueerde object in één keer retourneert.
Het hoofddoel van het bouwerspatroon is om een standaardconfiguratie in te stellen voor een object dat is gemaakt. Het is een intermediair tussen het te bouwen object en alle andere objecten die verband houden met het bouwen.
Voorbeeld:
Laten we voor de duidelijkheid een voorbeeld van een autobouwer bekijken .
Bedenk dat we een autoklasse hebben die veel opties bevat om een object te maken, zoals:
- Kleur.
- Aantal zitplaatsen.
- Aantal wielen.
- Type.
- Type versnelling.
- Motor.
- Airbag beschikbaarheid.
import UIKit
enum CarType {
case
sportage,
saloon
}
enum GearType {
case
manual,
automatic
}
struct Motor {
var id: String
var name: String
var model: String
var numberOfCylinders: UInt8
}
class Car: CustomStringConvertible {
var color: UIColor
var numberOfSeats: UInt8
var numberOfWheels: UInt8
var type: CarType
var gearType: GearType
var motor: Motor
var shouldHasAirbags: Bool
var description: String {
return "color: \(color)\nNumber of seats: \(numberOfSeats)\nNumber of Wheels: \(numberOfWheels)\n Type: \(gearType)\nMotor: \(motor)\nAirbag Availability: \(shouldHasAirbags)"
}
init(color: UIColor, numberOfSeats: UInt8, numberOfWheels: UInt8, type: CarType, gearType: GearType, motor: Motor, shouldHasAirbags: Bool) {
self.color = color
self.numberOfSeats = numberOfSeats
self.numberOfWheels = numberOfWheels
self.type = type
self.gearType = gearType
self.motor = motor
self.shouldHasAirbags = shouldHasAirbags
}
}
Een auto-object maken:
let aCar = Car(color: UIColor.black,
numberOfSeats: 4,
numberOfWheels: 4,
type: .saloon,
gearType: .automatic,
motor: Motor(id: "101", name: "Super Motor",
model: "c4", numberOfCylinders: 6),
shouldHasAirbags: true)
print(aCar)
/* Printing
color: UIExtendedGrayColorSpace 0 1
Number of seats: 4
Number of Wheels: 4
Type: automatic
Motor: Motor(id: "101", name: "Super Motor", model: "c4", numberOfCylinders: 6)
Airbag Availability: true
*/
Het probleem ontstaat bij het maken van een autoobject dat de auto veel configuratiegegevens vereist om te worden gemaakt.
Voor het toepassen van het Builder-patroon moeten de initialisatieparameters standaardwaarden hebben die indien nodig kunnen worden gewijzigd .
CarBuilder-klasse:
class CarBuilder {
var color: UIColor = UIColor.black
var numberOfSeats: UInt8 = 5
var numberOfWheels: UInt8 = 4
var type: CarType = .saloon
var gearType: GearType = .automatic
var motor: Motor = Motor(id: "111", name: "Default Motor",
model: "T9", numberOfCylinders: 4)
var shouldHasAirbags: Bool = false
func buildCar() -> Car {
return Car(color: color, numberOfSeats: numberOfSeats, numberOfWheels: numberOfWheels, type: type, gearType: gearType, motor: motor, shouldHasAirbags: shouldHasAirbags)
}
}
De klasse CarBuilder
definieert eigenschappen die kunnen worden gewijzigd om de waarden van het gemaakte auto-object te bewerken.
Laten we nieuwe auto's bouwen met de CarBuilder
:
var builder = CarBuilder()
// currently, the builder creates cars with default configuration.
let defaultCar = builder.buildCar()
//print(defaultCar.description)
/* prints
color: UIExtendedGrayColorSpace 0 1
Number of seats: 5
Number of Wheels: 4
Type: automatic
Motor: Motor(id: "111", name: "Default Motor", model: "T9", numberOfCylinders: 4)
Airbag Availability: false
*/
builder.shouldHasAirbags = true
// now, the builder creates cars with default configuration,
// but with a small edit on making the airbags available
let safeCar = builder.buildCar()
print(safeCar.description)
/* prints
color: UIExtendedGrayColorSpace 0 1
Number of seats: 5
Number of Wheels: 4
Type: automatic
Motor: Motor(id: "111", name: "Default Motor", model: "T9", numberOfCylinders: 4)
Airbag Availability: true
*/
builder.color = UIColor.purple
// now, the builder creates cars with default configuration
// with some extra features: the airbags are available and the color is purple
let femaleCar = builder.buildCar()
print(femaleCar)
/* prints
color: UIExtendedSRGBColorSpace 0.5 0 0.5 1
Number of seats: 5
Number of Wheels: 4
Type: automatic
Motor: Motor(id: "111", name: "Default Motor", model: "T9", numberOfCylinders: 4)
Airbag Availability: true
*/
Het voordeel van het toepassen van het Builder-patroon is het gemak van het maken van objecten die veel configuraties moeten bevatten door standaardwaarden in te stellen, en het gemak van het wijzigen van deze standaardwaarden.
Breng het verder:
Als een goede praktijk moeten alle eigenschappen waarvoor standaardwaarden nodig zijn, in een gescheiden protocol worden opgenomen , dat door de klasse zelf en de bouwer moet worden geïmplementeerd.
Terug naar ons voorbeeld, laten we een nieuw protocol maken met de naam CarBluePrint
:
import UIKit
enum CarType {
case
sportage,
saloon
}
enum GearType {
case
manual,
automatic
}
struct Motor {
var id: String
var name: String
var model: String
var numberOfCylinders: UInt8
}
protocol CarBluePrint {
var color: UIColor { get set }
var numberOfSeats: UInt8 { get set }
var numberOfWheels: UInt8 { get set }
var type: CarType { get set }
var gearType: GearType { get set }
var motor: Motor { get set }
var shouldHasAirbags: Bool { get set }
}
class Car: CustomStringConvertible, CarBluePrint {
var color: UIColor
var numberOfSeats: UInt8
var numberOfWheels: UInt8
var type: CarType
var gearType: GearType
var motor: Motor
var shouldHasAirbags: Bool
var description: String {
return "color: \(color)\nNumber of seats: \(numberOfSeats)\nNumber of Wheels: \(numberOfWheels)\n Type: \(gearType)\nMotor: \(motor)\nAirbag Availability: \(shouldHasAirbags)"
}
init(color: UIColor, numberOfSeats: UInt8, numberOfWheels: UInt8, type: CarType, gearType: GearType, motor: Motor, shouldHasAirbags: Bool) {
self.color = color
self.numberOfSeats = numberOfSeats
self.numberOfWheels = numberOfWheels
self.type = type
self.gearType = gearType
self.motor = motor
self.shouldHasAirbags = shouldHasAirbags
}
}
class CarBuilder: CarBluePrint {
var color: UIColor = UIColor.black
var numberOfSeats: UInt8 = 5
var numberOfWheels: UInt8 = 4
var type: CarType = .saloon
var gearType: GearType = .automatic
var motor: Motor = Motor(id: "111", name: "Default Motor",
model: "T9", numberOfCylinders: 4)
var shouldHasAirbags: Bool = false
func buildCar() -> Car {
return Car(color: color, numberOfSeats: numberOfSeats, numberOfWheels: numberOfWheels, type: type, gearType: gearType, motor: motor, shouldHasAirbags: shouldHasAirbags)
}
}
Het voordeel van het declareren van de eigenschappen die standaardwaarde nodig hebben in een protocol, is het dwingen om nieuwe toegevoegde eigenschappen te implementeren; Wanneer een klasse voldoet aan een protocol, moet deze alle eigenschappen / methoden opgeven.
Overweeg dat er een vereiste nieuwe functie is die moet worden toegevoegd aan de blauwdruk van het maken van een auto met de naam "batterijnaam":
protocol CarBluePrint {
var color: UIColor { get set }
var numberOfSeats: UInt8 { get set }
var numberOfWheels: UInt8 { get set }
var type: CarType { get set }
var gearType: GearType { get set }
var motor: Motor { get set }
var shouldHasAirbags: Bool { get set }
// adding the new property
var batteryName: String { get set }
}
Nadat u de nieuwe eigenschap hebt toegevoegd, moet u er rekening mee houden dat er tijdens de compilatie twee fouten optreden die CarBluePrint
dat voor het voldoen aan het CarBluePrint
protocol de eigenschap 'batteryName' moet worden aangegeven. Dat garandeert dat CarBuilder
zal verklaren en stel een standaardwaarde voor batteryName
eigenschap.
Na het toevoegen van batteryName
nieuwe woning aan CarBluePrint
protocol, de uitvoering van beide Car
en CarBuilder
moeten klassen:
class Car: CustomStringConvertible, CarBluePrint {
var color: UIColor
var numberOfSeats: UInt8
var numberOfWheels: UInt8
var type: CarType
var gearType: GearType
var motor: Motor
var shouldHasAirbags: Bool
var batteryName: String
var description: String {
return "color: \(color)\nNumber of seats: \(numberOfSeats)\nNumber of Wheels: \(numberOfWheels)\nType: \(gearType)\nMotor: \(motor)\nAirbag Availability: \(shouldHasAirbags)\nBattery Name: \(batteryName)"
}
init(color: UIColor, numberOfSeats: UInt8, numberOfWheels: UInt8, type: CarType, gearType: GearType, motor: Motor, shouldHasAirbags: Bool, batteryName: String) {
self.color = color
self.numberOfSeats = numberOfSeats
self.numberOfWheels = numberOfWheels
self.type = type
self.gearType = gearType
self.motor = motor
self.shouldHasAirbags = shouldHasAirbags
self.batteryName = batteryName
}
}
class CarBuilder: CarBluePrint {
var color: UIColor = UIColor.red
var numberOfSeats: UInt8 = 5
var numberOfWheels: UInt8 = 4
var type: CarType = .saloon
var gearType: GearType = .automatic
var motor: Motor = Motor(id: "111", name: "Default Motor",
model: "T9", numberOfCylinders: 4)
var shouldHasAirbags: Bool = false
var batteryName: String = "Default Battery Name"
func buildCar() -> Car {
return Car(color: color, numberOfSeats: numberOfSeats, numberOfWheels: numberOfWheels, type: type, gearType: gearType, motor: motor, shouldHasAirbags: shouldHasAirbags, batteryName: batteryName)
}
}
Laten we opnieuw nieuwe auto's bouwen met de CarBuilder
:
var builder = CarBuilder()
let defaultCar = builder.buildCar()
print(defaultCar)
/* prints
color: UIExtendedSRGBColorSpace 1 0 0 1
Number of seats: 5
Number of Wheels: 4
Type: automatic
Motor: Motor(id: "111", name: "Default Motor", model: "T9", numberOfCylinders: 4)
Airbag Availability: false
Battery Name: Default Battery Name
*/
builder.batteryName = "New Battery Name"
let editedBatteryCar = builder.buildCar()
print(editedBatteryCar)
/*
color: UIExtendedSRGBColorSpace 1 0 0 1
Number of seats: 5
Number of Wheels: 4
Type: automatic
Motor: Motor(id: "111", name: "Default Motor", model: "T9", numberOfCylinders: 4)
Airbag Availability: false
Battery Name: New Battery Name
*/