The idea of the factory method pattern is to provide a single interface to create different types of objects that conform to an interface while hiding the actual implementation from the client. This abstraction decouples the client from the underlying implementation of the feature provider.
For example, a program might need to format some numbers in the output. In Julia, we might want to use the Printf package to format numbers, as follows:
Perhaps we do not want to couple with the Printf package because we want to switch and use a different formatting package in the future. In order to make the application more flexible, we can design an interface where numbers can be formatted according to their types. The following interface is described in the doc string:
"""
format(::Formatter, x::T) where {T <: Number}
Format a number `x` using the specified formatter.
Returns a string.
"""
function format end
The format function takes a formatter and a numeric value, x, and returns a formatted string. The Formatter type is defined as follows:
abstract type Formatter end
struct IntegerFormatter <: Formatter end
struct FloatFormatter <: Formatter end
Then, the factory methods basically create singleton types for dispatch purposes:
formatter(::Type{T}) where {T <: Integer} = IntegerFormatter()
formatter(::Type{T}) where {T <: AbstractFloat} = FloatFormatter()
formatter(::Type{T}) where T = error("No formatter defined for type $T")
The default implementation may look like the following, utilizing the Printf package:
using Printf
format(nf::IntegerFormatter, x) = @sprintf("%d", x)
format(nf::FloatFormatter, x) = @sprintf("%.2f", x)
Putting everything in a FactoryExample module, we can run the following testing code:
function test()
nf = formatter(Int)
println(format(nf, 1234))
nf = formatter(Float64)
println(format(nf, 1234))
end
The output is as follows:
Should we ever want to change the formatter in the future, we just need to provide a new implementation with format functions defined for the numeric types that we want to support. This is handy when we have a lot of number-formatting code lying around. The switch to a different formatter involves literally two lines of code changes (in this example).
Let's look at the abstract factory pattern next.