Wzorce projektowe: Fabryka Abstrakcyjna (Abstract Factory) w Swift

Fabryka Abstrakcyjna (ang. Abstract Factory) to kolejny wzorzec kreacyjny pomagający nam w tworzeniu obiektów. W przeciwieństwie do buildera, nie jest nastawiona na zmniejszenie ilości konstruktorów, ale na dostarczenie mechanizmu tworzącego obiekty jednej rodziny (jednej grupy). Zapraszam na zapoznanie się z implementacją w Swift.

Opis wzorcahttp://pl.wikipedia.org/wiki/Fabryka_abstrakcyjna_(wzorzec_projektowy)

Chciałem dla Was wymyślić coś ciekawego jeśli chodzi o przykład implementacji tego, ale cokolwiek mi nie przychodziło do głowy w trakcie tworzenia tego posta nie bylo do końca poprawnym przypadkiem. Dlatego pozostanę przy chyba najbardziej popularnym przykładzie Fabryki Abstrakcyjnej, tj fabryki tworzącej komponenty widoków w aplikacji. Załóżmy, że mamy aplikację, która w zależności od pory roku, zmienia kolor przycisków, labelek i tła.

Stwórzmy interfejs Abstrakcyjnej Fabryki:

//
// UIComponentFactory.swift
// AbstractFactory
 
import Foundation
 
protocol UIComponentFactory {
 func createButton() -> Button;
 func createView() -> View;
 func createLabel() -> Label;
}

Fabryka nie jest skomplikowana, za zadanie ma stworzyć 3 rodzaje elementów: Button, View i LabelButtonView i Label to z kolei interfejsy zdefiniowane na potrzeby tego przykładu. Poniżej ich definicje:

// Button.swift
// AbstractFactory
 
import Foundation
 
protocol Button {
 func textColor() -> String;
 func backgroundColor() -> String;
}
// View.swift
// AbstractFactory
 
import Foundation
 
protocol View {
 func backgroundImage() -> String;
}
// Label.swift
// AbstractFactory
 
import Foundation
 
protocol Label {
 func textColor() -> String;
}

Jak pokazuje powyższy kod, są to bardzo proste protokoły. W rzeczywistości modelowane obiekty będą na pewno bardziej skomplikowane i nie będą składać się z 1 metody.

Implementacja powyższych protokołów pokazana jest poniżej. Każdy z elementów interfejsu ma cechy właściwe dla pory roku. Nie będę przytaczał przykładów dla wszystkich pór roku. Zasada pracy jest prosta. Zainteresowanych zapraszam do analizy kodu z repozytorium na GitHub’ie.

Zestaw Winter

// WinterView.swift
// AbstractFactory
 
import Foundation
 
class WinterView : View {
 func backgroundImage() -> String {
   return "snowstorm.jpg"
 }
}
// WinterButton.swift
// AbstractFactory
 
import Foundation
 
class WinterButton : Button {
 func textColor() -> String {
   return "blue"
 }
 
 func backgroundColor() -> String {
   return "white"
 }
 
}
// WinterLable.swift
// AbstractFactory
 
import Foundation
 
class WinterLabel : Label {
 func textColor() -> String {
   return "darkBlue"
 }
}

Zestaw Summer:

// SummerView.swift
// AbstractFactory
 
import Foundation
 
class SummerView : View {
 func backgroundImage() -> String {
   return "greenTrees.jpg"
 }
}
// SummerButton.swift
// AbstractFactory
 
import Foundation
 
class SummerButton : Button {
 func textColor() -> String {
   return "black"
 }
 
 func backgroundColor() -> String {
   return "yellow"
 }
 
}
// SummerLabel.swift
// AbstractFactory
 
import Foundation
 
class SummerLabel : Label {
 func textColor() -> String {
   return "orange"
 }
}

 

Dość wstępu. Pora na kod konkretnych fabryk. Tak jak powyżej, pokażę jedynie część z nich, aby pokazać analogię. Po szczegółu zapraszam do kodu projektu na GitHubie:

// WinterComponentFactory.swift
// AbstractFactory
 
import Foundation
 
class WinterComponentFactory : UIComponentFactory {
 func createButton() -> Button {
   return WinterButton();
 }
 
 func createView() -> View {
   return WinterView()
 }
 
 func createLabel() -> Label {
   return WinterLabel()
 }
 
}
// SummerComponentFactory.swift
// AbstractFactory
 
import Foundation
 
class SummerComponentFactory : UIComponentFactory {
 func createButton() -> Button {
   return SummerButton();
 }
 
 func createView() -> View {
   return SummerView()
 }
 
 func createLabel() -> Label {
   return SummerLabel()
 }
 
}

OK, więc mamy interfejs fabryki, mamy konkretne fabryki, interfejsy elementów oraz konkretne elementy. Jak wiedzieć jakiej fabryki użyć? To już kwestia zależna od konkretnego przypadku. Jednym razem konfiguracja będzie wczytywana z pliku, innym razem wstrzykiwana przez jakiś mechanizm Dependency Injection, jeszcze innym razem będzie znana już podczas czasu kompilacji, bo preprocesor zajmie się dobraniem odpowiedniego kodu w zależności od wybranego targetu. W testach poniżej nie skupiałem się w ogóle na tym zagadnieniu. Pokazałem jedynie, że posługując się interfejsami (protocol) jestem w stanie pobrać interesujące mnie wartości, oraz że zastosowane fabryki tworzą obiekty konkretnych interfejsów prawidłowo. Ponownie jak wyżej, tutaj zamieszczam jedynie 2 przykłady. Więcej można obejrzeć w kodzie na GitHub’ie.

// AbstractFactoryTests.swift
// AbstractFactoryTests
 
import UIKit
import XCTest
 
class AbstractFactoryTests: XCTestCase {
 
 ...
 
 func testWinterFactory() {
   let componentFactory : UIComponentFactory = WinterComponentFactory()
   let button : Button = componentFactory.createButton()
   let label : Label = componentFactory.createLabel()
   let view : View = componentFactory.createView()
 
   XCTAssertEqual(button.backgroundColor(), "white", "Incorrect button background color")
   XCTAssertEqual(button.textColor(), "blue", "Incorrect button text color")
 
   XCTAssertEqual(label.textColor(), "darkBlue", "Incorrect label text color")
 
   XCTAssertEqual(view.backgroundImage(), "snowstorm.jpg", "Incorrect view background image")
 }
 
 ...
 
 func testSummerFactory() {
   let componentFactory : UIComponentFactory = SummerComponentFactory()
   let button : Button = componentFactory.createButton()
   let label : Label = componentFactory.createLabel()
   let view : View = componentFactory.createView()
 
   XCTAssertEqual(button.backgroundColor(), "yellow", "Incorrect button background color")
   XCTAssertEqual(button.textColor(), "black", "Incorrect button text color")
 
   XCTAssertEqual(label.textColor(), "orange", "Incorrect label text color")
 
   XCTAssertEqual(view.backgroundImage(), "greenTrees.jpg", "Incorrect view background image")
 }
 
}

Jak widać powyżej, posługując się protokołami, nie musimy wiedzieć jakiego typu jest dany obiekt. Nie ma to znaczenia. Dodatkowo, jeśli rodzaj konkretnej fabryki byłby wczytywany z jakiejś konfiguracji lub ustawiony przez preprocessor, Nawet nie wiedzielibyśmy jakiej klasy obiekty są zwracane.

Dziękuję za wytrwanie aż do tego miejsca 🙂 i zapraszam na następne posty o wzorcach.


Projekt Xcode i pliki źródłowe z przykładowym kodem można znaleźć tutaj.


Ten post jest częścią cyklu o wzorcach projektowych w Objective-C i Swift. Strona Wzorce projektowe w Objective-C i Swift – Kompendium  zbiera w jednym miejscu wzorce i przypisane do nich artykuły. Zapraszam do lektury.

Dodaj komentarz