The abstract factory pattern
We already introduced you to a very popular concept in design patterns: factories. Factories are the classes that handle the instantiation of related objects without subclassing. The factory method pattern that we have already seen hides the class name from where an object is instantiated. The abstract factory pattern is more complete as it creates families of related or dependent objects.
Roles
The abstract factory pattern is designed to build objects grouped in families without having to know the concrete class needed to create the object.
This pattern is generally used in the following domains:
- A system that uses products needs to stay independent of how these products are grouped and instantiated
- A system can have several product families that can evolve
Design
The following diagram represents the generic structure of the abstract factory pattern. You will see how products and families are decoupled:
Participants
The abstract factory pattern has a lot of participants:
Abstract
Factory
: This abstract class defines the signature of the different methods that create our products.ConcreteFactory1
andConcreteFactory2
: These are our concrete classes that implement our methods for each products' families. By knowing the family and product, the factory is able to create an instance of the product for that family.IProductA
andIProductB
: These are our interfaces that define our products that are independent of their family. A family is introduced in their concrete subclasses.ProductA
andProductB
: These are the concrete classes that implementIProductA
andIProductB
, respectively.
Collaborations
The Client
class uses one instance of one of the concrete factories to create products throughout the interface of the abstract factory.
Illustration
Our company specializes in manufacturing watches. Our watches are built in two parts: a band and dial. Our watches come in two sizes, so we must adapt the manufacturing of the band and dial according to the size of our watch.
In order to simplify how to manage the manufacturing of our watches, the direction team decided to use one manufacturer who specializes in products that are adapted to the 38 mm model of our watch, and another manufacturer whose products are adapted to the 42 mm model of our watch.
Each of these manufacturers will build a dial and band that are adapted to the dimension of the watch.
Implementation
To implement our pattern, we first need to identify our actors. The two manufacturers represent the ConcreteFactory1
and ConcreteFactory2
classes. These two factories implement the AbstractFactory
method, which tell us that we can create a band or dial. Of course, the concrete factories will create the dial adapted to the size of the watch produced in that manufacture.
Our ConcreteProductA
and ConcreteProductB
classes are the band and the dial; each of these products implements their respective IProductA
and IProductB
interfaces, as shown in the following code:
import UIKit //Our interfaces protocol IWatchBand { var color: UIColor{get set} var size: BandSize{get set} var type: BandType{get set} init(size: BandSize) } protocol IWatchDial { var material: MaterialType{get set} var size: WatchSize{get set} init(size: WatchSize) } //Enums enum MaterialType: String { case Aluminium = "Aluminium", StainlessSteel = "Stainless Steel", Gold = "Gold" } enum BandType: String { case Milanese = "Milanese", Classic = "Classic", Leather = "Leather", Modern = "Modern", LinkBracelet = "LinkBracelet", SportBand = "SportBand" } enum WatchSize: String { case _38mm = "38mm", _42mm = "42mm" } enum BandSize: String { case SM = "SM", ML = "ML" } //prepare our Bands components class MilaneseBand: IWatchBand { var color = UIColor.yellowColor() var size: BandSize var type = BandType.Milanese required init(size _size: BandSize) { size = _size } } class Classic: IWatchBand { var color = UIColor.yellowColor() var size: BandSize var type = BandType.Classic required init(size _size: BandSize) { size = _size } } class Leather:IWatchBand{ var color = UIColor.yellowColor() var size:BandSize var type = BandType.Leather required init(size _size: BandSize) { size = _size } } class Modern: IWatchBand { var color = UIColor.yellowColor() var size: BandSize var type = BandType.Modern required init(size _size: BandSize) { size = _size } } class LinkBracelet: IWatchBand { var color = UIColor.yellowColor() var size: BandSize var type = BandType.LinkBracelet required init(size _size: BandSize) { size = _size } } class SportBand: IWatchBand { var color = UIColor.yellowColor() var size: BandSize var type = BandType.SportBand required init(size _size: BandSize) { size = _size } } //Dials class AluminiumDial: IWatchDial { var material: MaterialType = MaterialType.Aluminium var size: WatchSize required init(size _size:WatchSize){ size = _size } } class StainlessSteelDial: IWatchDial { var material: MaterialType = MaterialType.StainlessSteel var size: WatchSize required init(size _size:WatchSize){ size = _size } } class GoldDial: IWatchDial { var material: MaterialType = MaterialType.Gold var size: WatchSize required init(size _size:WatchSize){ size = _size } } //Our AbstractFactory class WatchFactory { func createBand(bandType: BandType) -> IWatchBand { fatalError("not implemented") } func createDial(materialtype: MaterialType) -> IWatchDial{ fatalError("not implemented") } //our static method that return the appropriated factory. final class func getFactory(size: WatchSize) -> WatchFactory{ var factory: WatchFactory? switch(size){ case ._38mm: factory = Watch38mmFactory() case ._42mm: factory = Watch42mmFactory() } return factory! } } // Concrete Factory 1 for 42 mm class Watch42mmFactory: WatchFactory { override func createBand(bandType: BandType) -> IWatchBand { switch bandType { case .Milanese: return MilaneseBand(size: .ML) case .Classic: return Classic(size: .ML) case .Leather: return Leather(size: .ML) case .LinkBracelet: return LinkBracelet(size: .ML) case .Modern: return Modern(size: .ML) case .SportBand: return SportBand(size: .ML) default: return SportBand(size: .ML) } } override func createDial(materialtype: MaterialType) -> IWatchDial { switch materialtype{ case MaterialType.Gold: return GoldDial(size: ._42mm) case MaterialType.StainlessSteel: return StainlessSteelDial(size: ._42mm) case MaterialType.Aluminium: return AluminiumDial(size: ._42mm) } } } //Concrete Factory 2 for 38mm class Watch38mmFactory: WatchFactory{ override func createBand(bandType:BandType) -> IWatchBand { switch bandType { case .Milanese: return MilaneseBand(size: .SM) case .Classic: return Classic(size: .SM) case .Leather: return Leather(size: .SM) case .LinkBracelet: return LinkBracelet(size: .SM) case .Modern: return Modern(size: .SM) case .SportBand: return SportBand(size: .SM) default: return SportBand(size: .SM) } } override func createDial(materialtype: MaterialType) -> IWatchDial { switch materialtype{ case MaterialType.Gold: return GoldDial(size: ._38mm) case MaterialType.Gold: return StainlessSteelDial(size: ._38mm) case MaterialType.Gold: return AluminiumDial(size: ._38mm) default: return AluminiumDial(size: ._38mm) } } }
Usage
To simulate our client, we will use the following code:
//Here we deliver products from the Manufacture 1 specialized in //products for the 38 mm Watch let manufacture1 = WatchFactory.getFactory(WatchSize._38mm) let productA = manufacture1.createBand(BandType.Milanese) productA.color productA.size.rawValue productA.type.rawValue let productB = manufacture1.createDial(MaterialType.Gold) productB.material.rawValue productB.size.rawValue //Here we delivers products from the Manufacture 2 specialized in //products for the 42 mm Watch let manufacture2 = WatchFactory.getFactory(WatchSize._42mm) let productC = manufacture2.createBand(BandType.LinkBracelet) productC.color productC.size.rawValue productC.type.rawValue let productD = manufacture2.createDial(MaterialType.Gold) productD.material.rawValue productD.size.rawValue
Tip
Downloading the example code
You can download the example code files from your account at http://www.packtpub.com for all the Packt Publishing books you have purchased. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.
The Playground file will display our product's properties, depending on the factory used. The details of product A (the band) and product B (the dial) from the manufacture1
object are shown in the following screenshot:
The details of product C (the band) and product D (the dial) from the manufacture2
object are shown in the following screenshot:
The sizes of the band and the dial adapt to the manufacturer who delivers the product.
Note
We should use the singleton pattern to ensure that we have only one instance of our abstract factory. This instance can be shared between several clients.