
Factory Method and Abstract Factory in the Swift and iOS Universe
The word "factory" is by far one of the most frequently used by programmers when discussing their (or others) programs. But the meaning embedded in it can be very different: it can be a class that generates objects (polymorphic or not); and a method that creates instances of any type (static or not); it happens, and even just any generating method (including constructors ).
Of course, not everything that generates instances of anything can be called the word "factory." Moreover, under this word two different generative patterns from the Gang of Four arsenal can be hidden - the “factory method" and the "abstract factory" , the details of which I would like to delve into a bit, paying special attention to their classical understanding and implementation.
And I was inspired to write this essay by Joshua Kerivsky (head of “Industrial Logic” ), or rather, his book “Refactoring to Patterns” , which was published at the beginning of the century as part of a series of books founded by Martin Fowler (eminent author of the modern programming classic - the book “ Refactoring " ). If someone has not read or even heard of the first (and I know a lot of them), be sure to add it to your reading list. This is a worthy sequel to both Refactoring and an even more classic book, Objective Design Techniques. Design Patterns . ”
The book, among other things, contains several dozen recipes for getting rid of various"Smells" in the code using design patterns . Including three (at least) “recipes” on the topic under discussion.
Kerivsky in his book gives two cases where the use of this template will be useful.
The first is the encapsulation of knowledge about specific classes connected by a common interface. In this case, only the type that is the factory will have this knowledge. The factory’s public API will consist of a set of methods (static or not) that return instances of a common interface type and have some “talking” names (in order to understand which method needs to be called for a particular purpose).
The second example is very similar to the first (and, in general, all scenarios for using the pattern are more or less similar to each other). This is the case when instances of one or more types of the same group are created in different places in the program. In this case, the factory again encapsulates knowledge about the code that creates the instances, but with a slightly different motivation. For example, this is especially true if the process of creating instances of these types is complex and is not limited to calling the constructor.
To be closer to the topic of development under "iOS" , it is convenient to practice subclasses
Suppose we trade vehicles in an application, and the mapping depends on the type of specific vehicle: we will use different subclasses
Thus, we have a family of objects of one group, instances of types of which are created in the same places depending on some condition (for example, the user clicked on a product in the list, and depending on whether it is a scooter or a bicycle, we create the appropriate controller). Controller constructors have some parameters that also need to be set each time. Are these two arguments in favor of creating a "factory" that alone will have knowledge of the logic for creating the right controller?
Of course, the example is quite simple, and in a real project, in a similar case, introducing a "factory" will be explicit "overengineering". Nevertheless, if we imagine that we do not have two types of vehicles, and the designers have more than one parameter, then the advantages of the “factory” will become more obvious.
So, let's declare an interface that will play the role of an "abstract factory":
(A rather short “guideline” on designing an “API” in the Swift language recommends calling factory methods starting with the word make.)
(An example in the book of gang four is given in “C ++” and is based on inheritance and “virtual” functions Using Swift, of course, we are closer to the protocol-oriented programming paradigm.)
The abstract factory interface contains only two methods: for creating controllers for selling bicycles and scooters. Methods return instances not of specific subclasses, but of a common base class. Thus, the scope of knowledge about specific types is limited to the area in which it is really necessary.
As “concrete factories” we will use two implementations of the abstract factory interface:
In this case, as can be seen from the code, specific factories are responsible for vehicles of different conditions (new and used).
Creating the right controller will now look something like this:
Now briefly go over the use cases that Kerivsky offers in his book.
The first case is related to the encapsulation of specific classes . For example, take the same controllers for displaying data about vehicles:
Suppose we are dealing with a separate module, for example, a plug-in library. In this case, the classes declared above remain (by default)
The second “case” describes the complex initialization of the object , and Kerivsky, as one of the ways to simplify the code and protect the principles of encapsulation, suggests restricting the spread of knowledge about the initialization process outside the factory.
Suppose we wanted to sell cars at the same time. And this is undoubtedly a more complex technique, with a greater number of characteristics. For example, we restrict ourselves to the type of fuel used, the type of transmission and the size of the rim:
An example of initialization of the corresponding controller:
We can put responsibility for all these "little things" on the "shoulders" of a specialized factory:
And create the controller in this way:
The second “single-root” template also encapsulates knowledge about specific generated types, but not by hiding this knowledge within a specialized class, but by polymorphism. Kerivsky in his book gives examples in Java and suggests using abstract classes , but the inhabitants of the Swift universe are not familiar with this concept. We have our own atmosphere here ... and protocols.
As a classic example of the usefulness of the template, we consider the case when in the hierarchy different types have the identical implementation of one method except for the object that is created and used in this method . As a solution, it is proposed to create this object in a separate method and implement it separately, and to raise the general method higher in the hierarchy. Thus, different types will use the general implementation of the method, and the object needed for this method will be created polymorphically.
For example, let's return to our controllers for displaying vehicles:
And suppose that a certain entity is used to display them, for example, a coordinator , which represents these controllers modally from another controller:
In this case, the method
The proposed solution is to make the creation of the used object in a separate method:
And the main method is to provide the basic implementation:
Specific types in this case will take the form:
I tried to cover this simple topic by combining three approaches:
At the same time, I tried to be as close as possible to the textbook structure of the templates, as far as possible, without destroying the principles of the modern approach to development for the iOS system and using the capabilities of the Swift language (instead of the more common C ++ and Java).
As it turned out, it’s rather difficult to find detailed materials on the topic containing applied examples. Most of the existing articles and manuals contain only superficial reviews and abridged examples, which are already quite truncated in comparison with the textbook versions of implementations.
I hope that at least partially I was able to achieve my goals, and the reader - at least partially was interested or at least curious to learn or refresh my knowledge on this topic.
My other materials on design patterns:
And this is a link to my “Twitter”, where I publish links to my essays and a little more than that.
Of course, not everything that generates instances of anything can be called the word "factory." Moreover, under this word two different generative patterns from the Gang of Four arsenal can be hidden - the “factory method" and the "abstract factory" , the details of which I would like to delve into a bit, paying special attention to their classical understanding and implementation.
And I was inspired to write this essay by Joshua Kerivsky (head of “Industrial Logic” ), or rather, his book “Refactoring to Patterns” , which was published at the beginning of the century as part of a series of books founded by Martin Fowler (eminent author of the modern programming classic - the book “ Refactoring " ). If someone has not read or even heard of the first (and I know a lot of them), be sure to add it to your reading list. This is a worthy sequel to both Refactoring and an even more classic book, Objective Design Techniques. Design Patterns . ”
The book, among other things, contains several dozen recipes for getting rid of various"Smells" in the code using design patterns . Including three (at least) “recipes” on the topic under discussion.
Abstract factory
Kerivsky in his book gives two cases where the use of this template will be useful.
The first is the encapsulation of knowledge about specific classes connected by a common interface. In this case, only the type that is the factory will have this knowledge. The factory’s public API will consist of a set of methods (static or not) that return instances of a common interface type and have some “talking” names (in order to understand which method needs to be called for a particular purpose).
The second example is very similar to the first (and, in general, all scenarios for using the pattern are more or less similar to each other). This is the case when instances of one or more types of the same group are created in different places in the program. In this case, the factory again encapsulates knowledge about the code that creates the instances, but with a slightly different motivation. For example, this is especially true if the process of creating instances of these types is complex and is not limited to calling the constructor.
To be closer to the topic of development under "iOS" , it is convenient to practice subclasses
UIViewController
. Indeed, this is definitely one of the most common types in the "iOS" development, it is almost always "inherited" before application, and a particular subclass is often not even important for client code.I will try to keep the code examples as close as possible to the classic implementation from the Gang of Four book, but in real life the code is often simplified in one way or another. And only a sufficient understanding of the template opens the door to its more free use.
Detailed example
Suppose we trade vehicles in an application, and the mapping depends on the type of specific vehicle: we will use different subclasses
UIViewController
for different vehicles. In addition, all vehicles differ in state (new and used):enum VehicleCondition{
case new
case used
}
final class BicycleViewController: UIViewController {
private let condition: VehicleCondition
init(condition: VehicleCondition) {
self.condition = condition
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("BicycleViewController: init(coder:) has not been implemented.")
}
}
final class ScooterViewController: UIViewController {
private let condition: VehicleCondition
init(condition: VehicleCondition) {
self.condition = condition
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("ScooterViewController: init(coder:) has not been implemented.")
}
}
Thus, we have a family of objects of one group, instances of types of which are created in the same places depending on some condition (for example, the user clicked on a product in the list, and depending on whether it is a scooter or a bicycle, we create the appropriate controller). Controller constructors have some parameters that also need to be set each time. Are these two arguments in favor of creating a "factory" that alone will have knowledge of the logic for creating the right controller?
Of course, the example is quite simple, and in a real project, in a similar case, introducing a "factory" will be explicit "overengineering". Nevertheless, if we imagine that we do not have two types of vehicles, and the designers have more than one parameter, then the advantages of the “factory” will become more obvious.
So, let's declare an interface that will play the role of an "abstract factory":
protocol VehicleViewControllerFactory {
func makeBicycleViewController() -> UIViewController
func makeScooterViewController() -> UIViewController
}
(A rather short “guideline” on designing an “API” in the Swift language recommends calling factory methods starting with the word make.)
(An example in the book of gang four is given in “C ++” and is based on inheritance and “virtual” functions Using Swift, of course, we are closer to the protocol-oriented programming paradigm.)
The abstract factory interface contains only two methods: for creating controllers for selling bicycles and scooters. Methods return instances not of specific subclasses, but of a common base class. Thus, the scope of knowledge about specific types is limited to the area in which it is really necessary.
As “concrete factories” we will use two implementations of the abstract factory interface:
struct NewVehicleViewControllerFactory: VehicleViewControllerFactory {
func makeBicycleViewController() -> UIViewController {
return BicycleViewController(condition: .new)
}
func makeScooterViewController() -> UIViewController {
return ScooterViewController(condition: .new)
}
}
struct UsedVehicleViewControllerFactory: VehicleViewControllerFactory {
func makeBicycleViewController() -> UIViewController {
return BicycleViewController(condition: .used)
}
func makeScooterViewController() -> UIViewController {
return ScooterViewController(condition: .used)
}
}
In this case, as can be seen from the code, specific factories are responsible for vehicles of different conditions (new and used).
Creating the right controller will now look something like this:
let factory: VehicleViewControllerFactory = NewVehicleViewControllerFactory()
let vc = factory.makeBicycleViewController()
Factory encapsulating classes
Now briefly go over the use cases that Kerivsky offers in his book.
The first case is related to the encapsulation of specific classes . For example, take the same controllers for displaying data about vehicles:
final class BicycleViewController: UIViewController { }
final class ScooterViewController: UIViewController { }
Suppose we are dealing with a separate module, for example, a plug-in library. In this case, the classes declared above remain (by default)
internal
, and the factory acts as the public “API” of the library, which in its methods returns the base classes of the controllers, thus leaving knowledge about specific subclasses inside the library:public struct VehicleViewControllerFactory {
func makeBicycleViewController() -> UIViewController {
return BicycleViewController()
}
func makeScooterViewController() -> UIViewController {
return ScooterViewController()
}
}
Moving knowledge about creating an object inside a factory
The second “case” describes the complex initialization of the object , and Kerivsky, as one of the ways to simplify the code and protect the principles of encapsulation, suggests restricting the spread of knowledge about the initialization process outside the factory.
Suppose we wanted to sell cars at the same time. And this is undoubtedly a more complex technique, with a greater number of characteristics. For example, we restrict ourselves to the type of fuel used, the type of transmission and the size of the rim:
enum Condition {
case new
case used
}
enum EngineType {
case diesel
case gas
}
struct Engine {
let type: EngineType
}
enum TransmissionType {
case automatic
case manual
}
final class CarViewController: UIViewController {
private let condition: Condition
private let engine: Engine
private let transmission: TransmissionType
private let wheelDiameter: Int
init(engine: Engine,
transmission: TransmissionType,
wheelDiameter: Int = 16,
condition: Condition = .new) {
self.engine = engine
self.transmission = transmission
self.wheelDiameter = wheelDiameter
self.condition = condition
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("CarViewController: init(coder:) has not been implemented.")
}
}
An example of initialization of the corresponding controller:
let engineType = EngineType.diesel
let engine = Engine(type: engineType)
let transmission = TransmissionType.automatic
let wheelDiameter = 18
let vc = CarViewController(engine: engine,
transmission: transmission,
wheelDiameter: wheelDiameter)
We can put responsibility for all these "little things" on the "shoulders" of a specialized factory:
struct UsedCarViewControllerFactory {
let engineType: EngineType
let transmissionType: TransmissionType
let wheelDiameter: Int
func makeCarViewController() -> UIViewController {
let engine = Engine(type: engineType)
return CarViewController(engine: engine,
transmission: transmissionType,
wheelDiameter: wheelDiameter,
condition: .used)
}
}
And create the controller in this way:
let factory = UsedCarViewControllerFactory(engineType: .gas,
transmissionType: .manual,
wheelDiameter: 17)
let vc = factory.makeCarViewController()
Factory method
The second “single-root” template also encapsulates knowledge about specific generated types, but not by hiding this knowledge within a specialized class, but by polymorphism. Kerivsky in his book gives examples in Java and suggests using abstract classes , but the inhabitants of the Swift universe are not familiar with this concept. We have our own atmosphere here ... and protocols.
The book "Gangs of Four" reports that the template is also known as the "virtual constructor", and this is not in vain. In "C ++", virtual is a function that is redefined in derived classes. The language does not give the designer the opportunity to declare virtual, and it is possible that it was an attempt to imitate the desired behavior that led to the invention of this pattern.
Polymorphic object creation
As a classic example of the usefulness of the template, we consider the case when in the hierarchy different types have the identical implementation of one method except for the object that is created and used in this method . As a solution, it is proposed to create this object in a separate method and implement it separately, and to raise the general method higher in the hierarchy. Thus, different types will use the general implementation of the method, and the object needed for this method will be created polymorphically.
For example, let's return to our controllers for displaying vehicles:
final class BicycleViewController: UIViewController { }
final class ScooterViewController: UIViewController { }
And suppose that a certain entity is used to display them, for example, a coordinator , which represents these controllers modally from another controller:
protocol Coordinator {
var presentingViewController: UIViewController? { get set }
func start()
}
In this case, the method
start()
is always used the same way, except that it creates different controllers:final class BicycleCoordinator: Coordinator {
weak var presentingViewController: UIViewController?
func start() {
let vc = BicycleViewController()
presentingViewController?.present(vc, animated: true)
}
}
final class ScooterCoordinator: Coordinator {
weak var presentingViewController: UIViewController?
func start() {
let vc = ScooterViewController()
presentingViewController?.present(vc, animated: true)
}
}
The proposed solution is to make the creation of the used object in a separate method:
protocol Coordinator {
var presentingViewController: UIViewController? { get set }
func start()
func makeViewController() -> UIViewController
}
And the main method is to provide the basic implementation:
extension Coordinator {
func start() {
let vc = makeViewController()
presentingViewController?.present(vc, animated: true)
}
}
Specific types in this case will take the form:
final class BicycleCoordinator: Coordinator {
weak var presentingViewController: UIViewController?
func makeViewController() -> UIViewController {
return BicycleViewController()
}
}
final class ScooterCoordinator: Coordinator {
weak var presentingViewController: UIViewController?
func makeViewController() -> UIViewController {
return ScooterViewController()
}
}
Conclusion
I tried to cover this simple topic by combining three approaches:
- the classic declaration of the existence of the reception, inspired by the book “Gangs of Four”;
- motivation for use, openly inspired by Kerivsky's book;
- applied application as an example of a programming industry close to me.
At the same time, I tried to be as close as possible to the textbook structure of the templates, as far as possible, without destroying the principles of the modern approach to development for the iOS system and using the capabilities of the Swift language (instead of the more common C ++ and Java).
As it turned out, it’s rather difficult to find detailed materials on the topic containing applied examples. Most of the existing articles and manuals contain only superficial reviews and abridged examples, which are already quite truncated in comparison with the textbook versions of implementations.
I hope that at least partially I was able to achieve my goals, and the reader - at least partially was interested or at least curious to learn or refresh my knowledge on this topic.
My other materials on design patterns:
- "Architectural template" Visitor "(" Visitor ") in the universe of" iOS "and" Swift "
- The Iterator Architectural Pattern in the Swift Universe
- "Architectural pattern" Builder "in the universe of" Swift "and" iOS "/" macOS "
And this is a link to my “Twitter”, where I publish links to my essays and a little more than that.