Domain model classes
Object-orientation is great when classes represent actual domain model objects. For example, if we're modeling animal sounds, Dog
and Cat
classes make good sense:
interface IAnimal
{
string Speak();
}
class Dog : IAnimal
{
public string Speak() => "Woof";
}
class Cat : IAnimal
{
public string Speak() => "Meow";
}
Factory pattern
Now, let's say we need to write a function that creates an arbitrary animal and asks it to speak. Unfortunately, there's no way to add a constructor to the IAnimal
interface. One way to work around this limitation is the Factory design pattern, which creates a separate "factory" class for each of our domain model classes:
interface IAnimalFactory
{
IAnimal CreateAnimal();
}
class DogFactory : IAnimalFactory
{
public IAnimal CreateAnimal() => new Dog();
}
class CatFactory : IAnimalFactory
{
public IAnimal CreateAnimal() => new Cat();
}
With the new IAnimalFactory
interface, we can create dogs and cats and make them speak:
class Program
{
static void Main(string[] args)
{
Run(1, new DogFactory());
Run(2, new CatFactory());
}
static void Run(int n, IAnimalFactory factory)
{
var animal = factory.CreateAnimal();
Console.WriteLine($"Animal #{n} says '{animal.Speak()}'");
}
}
// output:
// Animal #1 says 'Woof'
// Animal #2 says 'Meow'
This works fine, but it adds a considerable amount of new code and conceptual overhead, because every domain model class now requires a separate factory class as well. The extra level of indirection can quickly become confusing. Is there a better way?
Factory functions
Functional programming lets us get rid of factory classes altogether. Instead of passing an IAnimalFactory
to Run
, we can instead pass a "factory function" of type Func<IAnimal>
. This takes over the role played by the factory object in the previous implementation:
static void Run(int n, Func<IAnimal> createAnimal)
{
var animal = createAnimal();
Console.WriteLine($"Animal #{n} says '{animal.Speak()}'");
}
We can use lambdas to create our factory functions with little ceremony:
static void Main(string[] args)
{
Run(1, () => new Dog());
Run(2, () => new Cat());
}
The output is exactly the same. By using functions as first-class objects, we've eliminated an entire category of classes and significantly simplified our code base! If you find yourself tempted to create a factory object in the future, consider using a factory function instead.
But, what if I'm building a Beagle and a German Shepherd?
I'd make sense to have two classes that build the same class Dog, but gives me a different ones, based on it's properties.
More code? You're right, but also much more readable.