???? Weekly Snippets
Every Sunday I write an email newsletter with some thoughts, life lessons and links to articles / books I enjoyed that week. I’d love for you to join.
Design Pattern - Creational: Abstract Factory
Hey friends, as I promised on Instagram I am sharing with you my notes about coding and design pattern.
???? Definition
Abstract Factory is a creational design pattern, which solves the problem of creating entire product families without specifying their concrete classes.
???????? How it works
Abstract Factory defines an interface fro creating all distinct products but leaves the actual product creation to concrete factory classes. Each factory type corresponds to a certain product variety.
The client code calls the creation methods of a factory object instead of creating products directly with a constructor call. Since a factory correspond to a single product variant, all its products will be compatible.
Client code works with factories and products only through their abstract interfaces. This let he client code work with any product variants, created but the factory object. You just create a new concrete factory class and pass it to the client code.
???? Example
The Abstract Factory protocol declares a set of methods that return different abstract products. These products are called a family and are related by a high-level theme or concept. Products of one family are usually able to collaborate among themselves. A family of products may have several variants, but the products of one variant are incompatible with products of another.
protocol AbstractFactory {
func createProductA() -> AbstractProductA
func createProductB() -> AbstractProductB
}
Concrete Factories produce a family of products that belong to a single variant. The factory guarantees that resulting products are compatible. note that signatures of the Concrete Factory0s methods return an abstract product, while inside the method a concrete product is instatiated.
class ConcreteFactory1: AbstractFactory {
func createProductA() -> AbstracProductA {
return ConcreteProductA1()
}
func createProductB() -> AbstracProductB {
return ConcreteProductB1()
}
}
class ConcreteFactory2: AbstractFactory {
func createProductA() -> AbstractProductA {
return ConcreteProductA2()
}
func createProductB() -> AbstractProductB {
return ConcreteProductB2()
}
}
Each Concrete Factory has a corresponding product variant. Each distinct product of product family should have a base protocol. All variants of the product must implement this protocol.
protocol AbstractProductA {
func usefulFunctionA() -> String
}
class ConcreteProductA1: AbstractProductA {
func usefulFunctionA() -> String {
return "The result of the product A1."
}
}
class ConcreteProductA2: AbstractProductA {
func usefulFunctionA() -> String {
return "The result of the product A2."
}
}
The base protocol of another product. All product can interact with each other, but proper interaction is possible only between 2 products of the same concrete variant.
protocol AbstractProductB {
/// Product B is able to do its own thing...
func usefulFunctionB() -> String
/// ...but it also can collaborate with the ProductA.
///
/// The Abstract Factory makes sure that all products
/// it creates are of the same variant and thus, compatible.
func anotherUsefulFunctionB(collaborator: AbstractProductA) -> String
}
Concrete Products are created by corresponding Concrete Factories.
class ConcreteProductB1: AbstractProductB {
func usefulFunctionB() -> String {
return "The result of the product B1."
}
/// This variant, Product B1, is only able to work correctly with the
/// variant, Product A1. Nevertheless, it accepts any instance of
/// AbstractProductA as an argument.
func anotherUsefulFunctionB(collaborator: AbstractProductA) -> String {
let result = collaborator.usefulFunctionA()
return "The result of the B1 collaborating with the (\\(result))"
}
}
class ConcreteProductB2: AbstractProductB {
func usefulFunctionB() -> String {
return "The result of the product B2."
}
/// This variant, Product B2, is only able to work correctly with the
/// variant, Product A2. Nevertheless, it accepts any instance of
/// AbstractProductA as an argument.
func anotherUsefulFunctionB(collaborator: AbstractProductA) -> String {
let result = collaborator.usefulFunctionA()
return "The result of the B2 collaborating with the (\\(result))"
}
}
The client code works with factories and products only through abstract type: AbstractFactory and AbstractProduct. This let you pass any factory or product subclass to the client code without breaking it.
class Client {
// ...
static func someClientCode(factory: AbstractFactory) {
let productA = factory.createProductA()
let productB = factory.createProductB()
print(productB.usefulFunctionB())
print(productB.anotherUsefulFunctionB(collaborator: productA))
}
// ...
}
The output:
Client: Testing client code with the first factory type:
The result of the product B1.
The result of the B1 collaborating with the (The result of the product A1.)
Client: Testing the same client code with the second factory type:
The result of the product B2.
The result of the B2 collaborating with the (The result of the product A2.)
???? Weekly Snippets
Every Sunday I write an email newsletter with some thoughts, life lessons and links to articles / books I enjoyed that week. I’d love for you to join.