Swift Language
Wzory projektowe - Kreatywne
Szukaj…
Wprowadzenie
Wzorce projektowe są ogólnymi rozwiązaniami problemów, które często występują podczas tworzenia oprogramowania. Poniżej znajdują się szablony znormalizowanych najlepszych praktyk w zakresie strukturyzacji i projektowania kodu, a także przykłady typowych kontekstów, w których te wzorce projektowe byłyby odpowiednie.
Wzorce projektowania kreują abstrakcję tworzenia obiektów, aby system był bardziej niezależny od procesu tworzenia, kompozycji i reprezentacji.
Singel
Singletony to często używany wzorzec projektowy, który składa się z pojedynczej instancji klasy współdzielonej przez program.
W poniższym przykładzie tworzymy właściwość static
która przechowuje instancję klasy Foo
. Pamiętaj, że właściwość static
jest wspólna dla wszystkich obiektów klasy i nie może zostać zastąpiona przez podklasę.
public class Foo
{
static let shared = Foo()
// Used for preventing the class from being instantiated directly
private init() {}
func doSomething()
{
print("Do something")
}
}
Stosowanie:
Foo.shared.doSomething()
Pamiętaj, aby zapamiętać private
inicjator:
Dzięki temu twoje singletony są naprawdę wyjątkowe i zapobiegają tworzeniu przez obiekty zewnętrzne własnych instancji twojej klasy dzięki kontroli dostępu. Ponieważ wszystkie obiekty mają domyślny inicjalizator publiczny w Swift, musisz przesłonić swój init i ustawić go jako prywatny. KrakenDev
Metoda fabryczna
W programowaniu klasowym wzorzec metody fabrycznej jest wzorcem kreacyjnym, który wykorzystuje metody fabryczne do rozwiązania problemu tworzenia obiektów bez konieczności określania dokładnej klasy obiektu, który zostanie utworzony. Odniesienie do Wikipedii
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)
W ten sposób nie jesteśmy zależni od rzeczywistej implementacji klasy, dzięki czemu sender()
całkowicie przejrzysty dla tego, kto go używa.
W tym przypadku wszystko, co musimy wiedzieć, to to, że nadawca obsłuży dostarczenie i ujawni metodę o nazwie send()
. Istnieje kilka innych zalet: ograniczenie łączenia klas, łatwiejsze do testowania, łatwiejsze dodawanie nowych zachowań bez konieczności zmiany, kto je konsumuje.
W projektowaniu zorientowanym obiektowo interfejsy zapewniają warstwy abstrakcji, które ułatwiają koncepcyjne objaśnienie kodu i tworzą barierę zapobiegającą zależnościom. Odniesienie do Wikipedii
Obserwator
Wzorzec obserwatora polega na tym, że obiekt, zwany podmiotem, przechowuje listę swoich osób zależnych, zwanych obserwatorami, i powiadamia ich automatycznie o wszelkich zmianach stanu, zwykle przez wywołanie jednej z ich metod. Służy głównie do wdrażania rozproszonych systemów obsługi zdarzeń. Wzorzec Observer jest także kluczowym elementem znanego wzorca architektonicznego model-view-controller (MVC). Odniesienie do Wikipedii
Zasadniczo wzorzec obserwatora jest używany, gdy masz obiekt, który może powiadamiać obserwatorów o niektórych zachowaniach lub zmianach stanu.
Najpierw stwórzmy globalne odwołanie (poza klasą) dla Centrum powiadomień:
let notifCentre = NotificationCenter.default
Świetnie, teraz możemy to nazwać z dowolnego miejsca. Chcielibyśmy wtedy zarejestrować klasę jako obserwatora ...
notifCentre.addObserver(self, selector: #selector(self.myFunc), name: "myNotification", object: nil)
To dodaje klasę jako obserwatora dla „readForMyFunc”. Wskazuje również, że funkcja myFunc powinna zostać wywołana po otrzymaniu tego powiadomienia. Ta funkcja powinna być napisana w tej samej klasie:
func myFunc(){
print("The notification has been received")
}
Jedną z zalet tego wzorca jest to, że można dodać wiele klas jako obserwatorów, a tym samym wykonać wiele akcji po jednym powiadomieniu.
Powiadomienie można teraz po prostu wysłać (lub opublikować, jeśli wolisz) z dowolnego miejsca w kodzie za pomocą wiersza:
notifCentre.post(name: "myNotification", object: nil)
Możesz również przekazać informacje wraz z powiadomieniem jako Słownik
let myInfo = "pass this on"
notifCentre.post(name: "myNotification", object: ["moreInfo":myInfo])
Ale musisz dodać powiadomienie do swojej funkcji:
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
}
Łańcuch odpowiedzialności
W projektowaniu obiektowym wzorzec łańcucha odpowiedzialności to wzorzec projektowy składający się ze źródła obiektów
command
i szeregu obiektówprocessing
. Każdy obiektprocessing
zawiera logikę, która określa typy obiektów poleceń, które może obsługiwać; reszta jest przekazywana do następnego obiektuprocessing
w łańcuchu. Istnieje również mechanizm dodawania nowych obiektówprocessing
na końcu tego łańcucha. Wikipedia
Utworzenie klas, które stanowiły łańcuch odpowiedzialności.
Najpierw tworzymy interfejs dla wszystkich processing
obiektów.
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)
}
}
}
Następnie tworzymy obiekt command
.
struct PurchaseRequest {
var amount : Float
var purpose : String
}
Wreszcie, tworzenie obiektów stanowiących łańcuch odpowiedzialności.
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?
}
Zainicjuj i połącz to razem:
let manager = ManagerPower()
let director = DirectorPower()
let president = PresidentPower()
manager.successor = director
director.successor = president
Mechanizm łączenia łańcuchów obiektów tutaj to dostęp do właściwości
Tworzenie żądania, aby je uruchomić:
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
W programowaniu komputerowym iterator to obiekt, który umożliwia programiście przechodzenie przez kontener, w szczególności listy. 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)
}
}
I przykładem użycia byłoby
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)")
}
Wzór konstruktora
Wzorzec konstruktora to wzorzec projektowy oprogramowania do tworzenia obiektów . W przeciwieństwie do abstrakcyjnego wzorca fabrycznego i wzorca metody fabrycznej, którego celem jest umożliwienie polimorfizmu, intencją wzorca konstruktora jest znalezienie rozwiązania anty-wzorca teleskopowego konstruktora. Anty-wzorzec konstruktora teleskopowego występuje, gdy wzrost kombinacji parametrów konstruktora obiektów prowadzi do wykładniczej listy konstruktorów. Zamiast używać wielu konstruktorów, wzorzec konstruktora używa innego obiektu, konstruktora, który odbiera każdy parametr inicjalizacji krok po kroku, a następnie zwraca powstały skonstruowany obiekt naraz.
Głównym celem wzorca konstruktora jest ustawienie domyślnej konfiguracji obiektu od momentu jego utworzenia. Jest to pośrednik między obiektem, który zostanie zbudowany, a wszystkimi innymi obiektami związanymi z jego budowaniem.
Przykład:
Aby to wyjaśnić, spójrzmy na przykład konstruktora samochodów .
Weź pod uwagę, że mamy klasę Car, która zawiera wiele opcji tworzenia obiektu, takich jak:
- Kolor.
- Ilość miejsc.
- Ilość kół
- Rodzaj.
- Rodzaj przekładni
- Silnik.
- Dostępność poduszek powietrznych.
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
}
}
Tworzenie obiektu samochodowego:
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
*/
Problem powstający podczas tworzenia obiektu samochodu polega na tym, że samochód wymaga utworzenia wielu danych konfiguracyjnych.
Aby zastosować wzorzec konstruktora, parametry inicjatora powinny mieć wartości domyślne, które w razie potrzeby można zmieniać .
Klasa CarBuilder:
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)
}
}
Klasa CarBuilder
definiuje właściwości, które można zmienić, aby edytować wartości utworzonego obiektu samochodu.
CarBuilder
nowe samochody za pomocą 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
*/
Zaletą zastosowania Wzorca konstruktora jest łatwość tworzenia obiektów, które powinny zawierać wiele konfiguracji poprzez ustawienie wartości domyślnych, a także łatwość zmiany tych wartości domyślnych.
Przejdź dalej:
Jako dobrą praktykę wszystkie właściwości wymagające wartości domyślnych powinny znajdować się w oddzielnym protokole , który powinien być implementowany przez samą klasę i jej konstruktora.
Wracając do naszego przykładu, stwórzmy nowy protokół o nazwie 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)
}
}
Korzyścią z zadeklarowania właściwości wymagających wartości domyślnej w protokole jest wymuszenie implementacji każdej nowej dodanej właściwości; Gdy klasa jest zgodna z protokołem, musi zadeklarować wszystkie swoje właściwości / metody.
Weź pod uwagę, że do planu tworzenia samochodu o nazwie „nazwa akumulatora” należy dodać nową funkcję:
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 }
}
Po dodaniu nowej właściwości należy pamiętać, że wystąpią dwa błędy czasu kompilacji, informujące, że zgodność z protokołem CarBluePrint
wymaga zadeklarowania właściwości „batteryName”. To gwarantuje, że CarBuilder
zadeklaruje i ustawi wartość domyślną dla właściwości batteryName
.
Po dodaniu batteryName
nową właściwość CarBluePrint
protokołu, realizacja obu Car
i CarBuilder
klas powinny być:
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)
}
}
Ponownie CarBuilder
nowe samochody za pomocą 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
*/