Skip to content

Commit 4ceddb8

Browse files
committed
Fixed issues with Abstract Factory pattern
1 parent cd9b66b commit 4ceddb8

File tree

1 file changed

+74
-47
lines changed

1 file changed

+74
-47
lines changed

Common Design Patterns/Creational/AbstractFactory/AbstractFactory.md

+74-47
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
# Abstract Factory Design Pattern
2-
`Abstract Factory` is a creational design pattern that is aimed to simplify creation of objects at run-time without specifying their concrete types. The pattern has some similarities with another creational pattern called `Factory Method`. Don't be confused by the common nature and similar names of the patterns - they are different and have different restrictions.
2+
`Abstract Factory` is a creational design pattern that is aimed to simplify creation of objects at run-time without specifying their concrete types. That is accomplished by incapsulating a group of related factories that operate the same domain-specific types.
33

4-
`Abstract Factory` is implemented using a type rather than a method and can be `inherited` and `extended`. In order to implement the pattern we need to define a type that will act as an `abstract factory` and a `protocol` that will be used as the resulting type of the factory. Which type exactly - will be determined at runtime.
4+
The pattern has some similarities with another creational pattern called `Factory Method`. Don't be confused by the common nature and similar names of the patterns - they are different and have different restrictions and purposes.
55

6-
Our example will be built around the following task: we need to create a land vehicle based on whether there are zombies walking on the Earth or not. If zombie apocalypse happened - then we need a post-apocalyptic car that can shoot rockets or flame. Otherwise, if zombie apocalypse hasn't happened yet 😅, then our factory will produce a very powerful sport car.
6+
`Abstract Factory` is implemented using a type rather than a method, can be `inherited` and `extended`. The pattern hides the related factories that specifically know how to create concrete types that belong to the factory. In order to implement the pattern we need to define a type that will incapsulate the related sub-factories and a `protocol` that will be used as the resulting type of the factory. In some cases there might be several protocols, each for a separate sub-factory. That will allow to produce non-dependent types.
77

8+
<!--act as an `abstract factory` and a `protocol` that will be used as the resulting type of the factory. Which type exactly - will be determined at runtime.-->
9+
10+
We will create two factories: one for creating `postapocalyptic vehicles` and the other will be used to create `regular vehicles`. Then we will build *Abstract Factory* that will be producing various types of vehicles, depending on non type-related properties and without exposing the underlying theme-specific factories.
11+
12+
<!--Our example will be the following: we need to create a land vehicle based on whether there are zombies walking on the Earth or not. If zombie apocalypse happened - then we need a post-apocalyptic car that can shoot rockets or flame. Otherwise, if zombie apocalypse hasn't happened yet 😅, then our factory will produce a very powerful sport car.
13+
-->
814
## Implementation
915

1016
We start from defining `LandVehicle` protocol that will be used as a runtime type for our abstract factory:
@@ -25,7 +31,7 @@ The protocol defines a set of properties that describe main components for our l
2531
Then we create a concrete type for `SportCar` and add conformance to `LandVehicle` protocol:
2632

2733
```swift
28-
class SportCar: LandVehicle, MovableProtocol, RotatableProtocol {
34+
class SportCar: LandVehicle, MovableProtocol, RotatableProtocol, TurboAcceleratable {
2935

3036
// MARK: - LandVehicle properties
3137

@@ -61,11 +67,16 @@ class SportCar: LandVehicle, MovableProtocol, RotatableProtocol {
6167
print("turned to the:", direction)
6268
}
6369

70+
// MARK: - Conformance to TurboAcceleratable protocol
71+
72+
func accelerate() {
73+
print("accelerated!")
74+
}
6475
}
6576

6677
```
6778

68-
`MovableProtocol` and `RotatableProtocol` are protocols that add certain functions for our `SportCar` type - they indicate what capabilites our vehicles have.
79+
`MovableProtocol` and `RotatableProtocol` are protocols that add certain functions for our `SportCar` type - they indicate what capabilities our vehicles have.
6980

7081
The next type that we are going to implement is `PostapocalypticCar`:
7182

@@ -78,9 +89,15 @@ class PostapocalypticCar: LandVehicle, MovableProtocol, RotatableProtocol, Shoot
7889
var wheels: [Wheel]
7990
var body: CarBody
8091

92+
// MARK: - Conformance to ShootableProtocol
93+
94+
var missileType: MissileType
95+
8196
// MARK: - Initializers
8297

83-
init() {
98+
init(missileType: MissileType) {
99+
self.missileType = missileType
100+
84101
let engineOne = Engine(id: "V8", horsepower: 850)
85102
let engineTwo = Engine(id: "V6", horsepower: 550)
86103

@@ -112,68 +129,78 @@ class PostapocalypticCar: LandVehicle, MovableProtocol, RotatableProtocol, Shoot
112129

113130
// MARK: - Conformance to ShootableProtocol
114131

115-
func shoot(missileOf type: MissileType) {
116-
print("shoot missile of type: ", type)
132+
func shoot() {
133+
print("shoot missile of type: ", missileType)
117134
}
118-
119135
}
120136
```
121-
Additionally `PostapocalypticCar` conforms to `ShootableProtocol` which adds shooting capability using `MissileType` (rockets, bullets or flame). The conformance to this protocol adds some differentiative characteristic to anti-zombie car.
137+
Additionally `PostapocalypticCar` conforms to `ShootableProtocol` which adds shooting capability using `MissileType` (rockets, bullets or flame). The conformance to this protocol adds some differentiate characteristic to anti-zombie car.
122138

139+
Each of the `LandVehicle` types incapsulate the functionality related to the instantiation of `self`, so the factory will be free from set up code. Please note that it's not required, your factories may be responsible for setting up the related types.
123140

124-
Each of the `LandVehicle` types incapsulate the functionality related to the instantiation of `self`, so the factory will be free from set up code. Please note that it's not required, your abstract factory may be responsible for setting up the related types.
141+
Next, we create theme-specific factories:
125142

126143
```swift
127-
struct VehicleFactory {
128-
129-
// MARK: - Properties
144+
class ShootableVehicleFactory {
130145

131-
var areThereZombies = false
132-
133-
// MARK: - Methods
146+
func produce(with missileType: MissileType) -> LandVehicle {
147+
return missileType == .fire ? SteampunkTruck(missileType: .fire) : PostapocalypticCar(missileType: missileType)
148+
}
149+
}
150+
151+
class RegularVehicleFactory {
134152

135153
func produce() -> LandVehicle {
136-
return areThereZombies ? producePostApocalypticCar() : produceSportCar()
154+
return SportCar()
137155
}
156+
}
157+
```
158+
159+
`SteampunkTruck` is similar to `PostapocalypticCar` except some type specific details (the code for *SteampunkTruck* can be found in the corresponding `.playground` file). The two factories specifically operate on either `Shootable` or `Regular` vehicles. For instance we can add some other land vehicle without a weapon system and add it to our `RegularVehicleFactory`.
160+
161+
```swift
162+
struct VehicleFactory {
163+
164+
// MARK: - Private properties
138165

139-
// MARK: - Private helpers
166+
private let shootableVehicleFactory = ShootableVehicleFactory()
167+
private let regularVehicleFactory = RegularVehicleFactory()
140168

141-
private func produceSportCar() -> LandVehicle {
142-
return SportCar()
143-
}
169+
// MARK: - Factory
144170

145-
private func producePostApocalypticCar() -> LandVehicle {
146-
return PostapocalypticCar()
171+
func produce(areThereZombies: Bool, hasWeapSystem: Bool) -> LandVehicle {
172+
173+
switch (zombies: areThereZombies, weaponSystem: hasWeapSystem) {
174+
case (true, true):
175+
return shootableVehicleFactory.produce(with: .rocket)
176+
case (false, true):
177+
return shootableVehicleFactory.produce(with: .fire)
178+
default:
179+
return regularVehicleFactory.produce()
180+
}
147181
}
148-
149182
}
150183
```
151184

152-
The factory is implemented using a basic boolean rule that is used to determine which type will be instantiated at runtime and produced.
185+
The factory is implemented using boolean values that are used to determine which type will be instantiated at runtime and then produced by the theme-specific factory.
153186

154187
```swift
155-
// Create the factory
156-
var vehicleFactory = VehicleFactory()
157-
// Set the state of the factory to produce postapocalyptic land vehicles
158-
vehicleFactory.areThereZombies = true
159-
160-
// Cast the produces vehicle as PostapocalypticCar
161-
let postapocalypticCar = vehicleFactory.produce() as? PostapocalypticCar
162-
// Shoot a rocket! 🚀 Kill all the zombies! 😄
163-
postapocalypticCar?.shoot(missleOf: .rocket)
164-
165-
// After killing all the zombies we set the state of the factory to produce SportCar
166-
vehicleFactory.areThereZombies = false
167-
let sportCar = vehicleFactory.produce()
168-
// Now we have a normal 🚗
169-
```
188+
// Create the factory 🏭
189+
let vehicleFactory = VehicleFactory()
190+
191+
// Produce a vehicle with the specified properties
192+
let steampunkTruck = vehicleFactory.produce(areThereZombies: false, hasWeapSystem: true) as? SteampunkTruck
193+
// Use car-specific functionality
194+
steampunkTruck?.shoot()
170195

171-
The usage of the factory is pretty straightforward:
172196

173-
- We instantiate the `Abstract Factory`
174-
- We change the factory state to produce `PostapocalypticCar` type
175-
- We shoot the rocket and kill all the zombies 🚀
176-
- Then we again change the state of the factory to produce sport car 🚗
197+
// Produce another vehicle based on different initial requirements
198+
let sportCar = vehicleFactory.produce(areThereZombies: true, hasWeapSystem: false) as? SportCar
199+
// Use turbo acceleration available for Sport Car 🚗:
200+
sportCar?.accelerate()
201+
```
202+
203+
The usage of the factory is pretty straightforward. We have created the factory for vehicles and then produced our first vehicle that is a regular vehicle but has weapon system. Our factory will delegate this work to `ShootableVehicleFactory` that will produce for us `SteampunkLandVehicle` instance. Next, we produce another vehicle but adopted for zombie apocalypse and without weapon system.
177204

178205
## Conclusion
179-
`Abstract Factory` is a great and very easy to implement creational pattern! It provides means for creating objects based on external parameters, configure them differently and provide specific type capabilities based on run-time requirements. Since, the implementation is built around concrete type, we can use `type extension` and `inheritance` in order to create families of factories or domain specific factories. Use this pattern when you have related types and you don't want to expose the way they are constructed and when you have run-time factors that affect the type that needs to be selected.
206+
`Abstract Factory` provides means for creating objects based on external parameters, configure them differently and provide specific type capabilities based on run-time requirements, by delegating the work to sub-factories that operate on theme-specific types. Since, the factory is built around concrete type, we can use `type extension` and `inheritance` to extend the capabilities. Use this pattern when you have related, unstructured types and you don't want to expose the way they are constructed. Also this pattern helps when you have run-time factors that affect the type that needs to be selected and constructed.

0 commit comments

Comments
 (0)