- Hands-On Design Patterns with Kotlin
- Alexey Soshin
- 803字
- 2021-06-25 20:49:27
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.
- Unity Virtual Reality Projects
- OpenCV for Secret Agents
- Visual C++串口通信技術詳解(第2版)
- Web全棧工程師的自我修養
- TypeScript圖形渲染實戰:基于WebGL的3D架構與實現
- Reactive Android Programming
- SQL Server 2016數據庫應用與開發
- Swift Playgrounds少兒趣編程
- Natural Language Processing with Java and LingPipe Cookbook
- 軟件供應鏈安全:源代碼缺陷實例剖析
- 軟件項目管理實用教程
- Kubernetes進階實戰
- Web編程基礎:HTML5、CSS3、JavaScript(第2版)
- Test-Driven iOS Development with Swift
- ANSYS FLUENT 16.0超級學習手冊