Factory

We'll start with the Factory Method formalized in the book Design Patterns by Gang of Four.

This is one of the first patterns I teach my students. They're usually very anxious about the whole concept of design patterns, since it has an aura of mystery and complexity.  So, what I do is ask them the following question.

Assume you have some class declaration, for example:

class Cat {
val name = "Cat"
}

Could you write a function that returns a new instance of the class? Most of them would succeed:

fun catFactory() : Cat {
return Cat()
}

Check that everything works:

val c = catFactory() 
println(c.name) // Indeed prints "Cat"

Well, that's really simple, right? 

Now, based on the argument we provide it, can this method create one of two objects?

Let's say we now have a Dog:

class Dog {
val name = "Dog"
}

Choosing between two types of objects to instantiate would require only passing an argument:

fun animalFactory(animalType: String) : Cat {
return Cat()
}

Of course, we can't always return a Cat now. So we create a common interface to be returned:

interface Animal {
val name : String
}

What's left is to use the when expression to return an instance of the correct class:

return when(animalType.toLowerCase()) {
"cat" -> Cat()
"dog" -> Dog()
else -> throw RuntimeException("Unknown animal $animalType")
}

That's what Factory Method is all about:

  • Get some value.
  • Return one of the objects that implement the common interface.

This pattern is very useful when creating objects from a configuration. Imagine we have a text file with the following contents that came from a veterinary clinic:

dog, dog, cat, dog, cat, cat

Now we would like to create an empty profile for each animal. Assuming we've already read the file contents and split them into a list, we can do the following:

val animalTypes = listOf("dog", "dog", "cat", "dog", "cat", "cat")

for (t in animalTypes) {
val c = animalFactory(t)
println(c.name)
}
listOf is a function that comes from the Kotlin standard library that creates an immutable list of provided objects.
If your Factory Method doesn't need to have a state, we can leave it as a function.

But what if we want to assign a unique sequential identifier for each animal? Take a look at the following code block:

interface Animal {
val id : Int
// Same as before
}

class Cat(override val id: Int) : Animal {
// Same as before
}

class Dog(override val id: Int) : Animal {
// Same as before
}

Note that we can override values inside the constructor.

Our factory becomes a proper class now:

class AnimalFactory { 
var counter = 0

fun createAnimal(animalType: String) : Animal {
return when(animalType.trim().toLowerCase()) {
"cat" -> Cat(++counter)
"dog" -> Dog(++counter)
else -> throw RuntimeException("Unknown animal $animalType")
}
}
}

So we'll have to initialize it:

val factory = AnimalFactory()
for (t in animalTypes) {
val c = factory.createAnimal(t)
println("${c.id} - ${c.name}")
}

Output for the preceding code is as follows:

1 - Dog 
2 - Dog
3 - Cat
4 - Dog
5 - Cat
6 - Cat

This was a pretty straightforward example. We provided a common interface for our objects (Animal, in this case), then based on some arguments, we decided which concrete class to instantiate. 

What if we decided to support different breeds? Take a look at the following code:

val animalTypes = listOf("dog" to "bulldog", 
"dog" to "beagle",
"cat" to "persian",
"dog" to "poodle",
"cat" to "russian blue",
"cat" to "siamese")
Much like the downTo function we saw in Chapter 1, Getting Started with Kotlin, it looks like an operator, but it's a function that creates a pair of objects: (cat, siamese, in our case). We'll come back to it when we discuss the infix function in depth.
We can delegate the actual object instantiation to other factories:
class AnimalFactory {
var counter = 0
private val dogFactory = DogFactory()
private val catFactory = CatFactory()

fun createAnimal(animalType: String, animalBreed: String) : Animal {
return when(animalType.trim().toLowerCase()) {
"cat" -> catFactory.createDog(animalBreed, ++counter)
"dog" -> dogFactory.createDog(animalBreed, ++counter)
else -> throw RuntimeException("Unknown animal $animalType")
}
}
}

The factory repeats the same pattern again:

class DogFactory {
fun createDog(breed: String, id: Int) = when(breed.trim().toLowerCase()) {
"beagle" -> Beagle(id)
"bulldog" -> Bulldog(id)
else -> throw RuntimeException("Unknown dog breed $breed")
}
}

You can make sure that you understand this example by implementing Beagle, Bulldog, CatFactory, and all the different breeds of cats by yourself.

The last point to note is how we're now calling our AnimalFactory with a pair of arguments:

for ((type, breed) in animalTypes) {
val c = factory.createAnimal(type, breed)
println(c.name)
}

This is called a destructuring declaration, and is useful especially when dealing with such pairs of data. 

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.40.32