第二個要講的是工廠模式
和單例的設計取向不同,單例著重在全域的唯一實體,而工廠模式著重在物件建構的彈性,也很好的處理了物件依賴的關係
client 會了解介面類型,透過工廠幫他產生實體,使其不需知道物件的建構方式,將建構方式限制在工廠內,再更上一層的抽象,還能將工廠也抽象化,使工廠也得以替換
fun main() {
GinFactory().apply{
println(
makeDrink(GinType.BRITISH_GIN).drinkType.getSpice()
)
println(
makeDrink(GinType.HENDRICKS_GIN).drinkType.getSpice()
)
}
}
class GinFactory :DrinkFactory {
override fun makeDrink(expectGin:GinType):Drink{
return when(expectGin){
GinType.BRITISH_GIN -> {
Drink(BritishGin(), 100)
}
GinType.HENDRICKS_GIN -> {
Drink(HendricksGin(), 200)
}
}
}
}
interface DrinkFactory {
fun makeDrink(expectGin:GinType):Drink
}
interface Gin{
fun getSpice():List<String>
fun getPrice():Int
fun getContent():String
}
class BritishGin:Gin {
override fun getSpice():List<String> {
return listOf("alo", "fuck","regret")
}
override fun getPrice():Int = 100
override fun getContent():String = "I am british gin"
}
class HendricksGin:Gin {
override fun getSpice():List<String> {
return listOf("test", "kdnf.", "fgfes")
}
override fun getPrice():Int = 20000000
override fun getContent():String = "I am hendrick's gin"
}
data class Drink(
val drinkType:Gin,
val price:Int
)
enum class GinType{
BRITISH_GIN, HENDRICKS_GIN;
}
工廠函式最常見的寫法就是放在共生物件裡面,就像是靜態工廠方法
class MyLinkedList<T>(
val head: T,
val tail: MyLinkedList<T>?
) {
companion object {
fun <T> of(vararg elements: T): MyLinkedList<T>? {
/*...*/
}
}
}
// Usage
val list = MyLinkedList.of(1, 2)
我們也能用介面來實現ㄌ
class MyLinkedList<T>(
val head: T,
val tail: MyLinkedList<T>?
): MyList<T> {
// ...
}
interface MyList<T> {
// ...
companion object {
fun <T> of(vararg elements: T): MyList<T>? {
// ...
}
}
}
// Usage
val list = MyList.of(1, 2)
但並非一定要用工廠模式才能得到好處,以函式做物件建構(factory function),有時會更適合,首先來比較建構子和建構函示的差異
比如鏈接列表,只透過建構子會變得很麻煩
class MyLinkedList<T>(
val head: T,
val tail: MyLinkedList<T>?
)
val list = MyLinkedList(1, MyLinkedList(2, null))
如果透過建構函式,可以讓我們在呼叫方更加方便
fun <T> myLinkedListOf(
vararg elements: T
): MyLinkedList<T>? {
if(elements.isEmpty()) return null
val head = elements.first()
val elementsTail = elements
.copyOfRange(1, elements.size)
val tail = myLinkedListOf(*elementsTail)
return MyLinkedList(head, tail)
}
val list = myLinkedListOf(1, 2)
此處的建構方法,有著固定的命名習慣
共生物件可以繼承介面,使其對類別擴長
abstract class ActivityFactory {
abstract fun getIntent(context: Context): Intent
fun start(context: Context) {
val intent = getIntent(context)
context.startActivity(intent)
}
fun startForResult(activity: Activity, requestCode:
Int) {
val intent = getIntent(activity)
activity.startActivityForResult(intent,
requestCode)
}
}
class MainActivity : AppCompatActivity() {
//...
companion object: ActivityFactory() {
override fun getIntent(context: Context): Intent =
Intent(context, MainActivity::class.java)
}
}
// Usage
val intent = MainActivity.getIntent(context)
MainActivity.start(context)
MainActivity.startForResult(activity, requestCode)
這裡的抽象共生物件工廠,可以保存狀態,也可以為測試做出假建構
有時我們無法更改介面或是需要再另外的檔案裡建立工廠函式,可以使用拓展的方式
//無法更改的介面
interface Tool {
companion object { /*...*/ }
}
為其寫出拓展
fun Tool.Companion.createBigTool( /*...*/ ) : BigTool {
//...
}
``` kotlin
At the call site we can then write:
``` kotlin
Tool.createBigTool()
此種方法非常方便,能夠讓我們快速地替外部套件寫出工廠方法,唯一的限制就是他需要原本的套件有寫 Companion object
頂層工廠函式也很常見,像是 listOf, mapOf 等等,但使用頂層函式也要非常小心,因為會讓ide 提示變得混亂,尤其是top level 和 class 命名相似時更容易搞混
建構式和 top level一樣,是全局可見的,唯一的差別就是大小寫,而 fake constructor 就是利用這點,使函式看起來像是建構式,而這樣的好處在於
public inline fun <T> List(
size: Int,
init: (index: Int) -> T
): List<T> = MutableList(size, init)
public inline fun <T> MutableList(
size: Int,
init: (index: Int) -> T
): MutableList<T> {
val list = ArrayList<T>(size)
repeat(size) { index -> list.add(init(index)) }
return list
}
除了上面兩點,偽建構式應該和建構式行為一樣,如果要實作快取,就應該以具名函式,nullable的方式,如前面共生物件的方式實現
除上面做法外,還能以 operator fun invoke 得方式實現假建構,但並不建議
那建構函式(factory function)會取代建構式嗎?
不會,建構函式依仍需要建構式,但他相對於建構式更有彈性、獨立於類別且可空
比較工廠模式和工廠函式,工廠模式多出了能持有狀態的好處,他們可以擁有屬性,並利用屬性去對建構作出調整
The second pattern we gonna discuss is factory
Unlike the core idea of singleton, singleton focuses on the only instance globally, but factories focus on the flexibility of the object constructor, and also deal with class dependency well.
Check the image above, client know what interface them will receive, and using factory to build the instance for them, so client won't know the how to build the instance, restrict the constructor method inside factory, we can add more abstract on factory by making abstract for factory as well
fun main() {
GinFactory().apply{
println(
makeDrink(GinType.BRITISH_GIN).drinkType.getSpice()
)
println(
makeDrink(GinType.HENDRICKS_GIN).drinkType.getSpice()
)
}
}
class GinFactory :DrinkFactory {
override fun makeDrink(expectGin:GinType):Drink{
return when(expectGin){
GinType.BRITISH_GIN -> {
Drink(BritishGin(), 100)
}
GinType.HENDRICKS_GIN -> {
Drink(HendricksGin(), 200)
}
}
}
}
interface DrinkFactory {
fun makeDrink(expectGin:GinType):Drink
}
interface Gin{
fun getSpice():List<String>
fun getPrice():Int
fun getContent():String
}
class BritishGin:Gin {
override fun getSpice():List<String> {
return listOf("alo", "fuck","regret")
}
override fun getPrice():Int = 100
override fun getContent():String = "I am british gin"
}
class HendricksGin:Gin {
override fun getSpice():List<String> {
return listOf("test", "kdnf.", "fgfes")
}
override fun getPrice():Int = 20000000
override fun getContent():String = "I am hendrick's gin"
}
data class Drink(
val drinkType:Gin,
val price:Int
)
enum class GinType{
BRITISH_GIN, HENDRICKS_GIN;
}
It is common to write build method inside companion object, this is what we call static factory methods
class MyLinkedList<T>(
val head: T,
val tail: MyLinkedList<T>?
) {
companion object {
fun <T> of(vararg elements: T): MyLinkedList<T>? {
/*...*/
}
}
}
// Usage
val list = MyLinkedList.of(1, 2)
we can also using interface to implement
class MyLinkedList<T>(
val head: T,
val tail: MyLinkedList<T>?
): MyList<T> {
// ...
}
interface MyList<T> {
// ...
companion object {
fun <T> of(vararg elements: T): MyList<T>? {
// ...
}
}
}
// Usage
val list = MyList.of(1, 2)
It doesn't require factory to get the benefit, sometimes using function to build class is better opinion, but first check out pros and cons
like linkedlist, using constructor only annoying yourself
class MyLinkedList<T>(
val head: T,
val tail: MyLinkedList<T>?
)
val list = MyLinkedList(1, MyLinkedList(2, null))
but if we choose constructor function, it will be more convenience from caller
fun <T> myLinkedListOf(
vararg elements: T
): MyLinkedList<T>? {
if(elements.isEmpty()) return null
val head = elements.first()
val elementsTail = elements
.copyOfRange(1, elements.size)
val tail = myLinkedListOf(*elementsTail)
return MyLinkedList(head, tail)
}
val list = myLinkedListOf(1, 2)
The constructor methods has rule for its naming
getInstance
, use for factory function for different class, the type
here should be the target classnewInstance一樣
, use for factory function for different class, the type
here should be the target classcompanion object can implement interface, to extend the class
abstract class ActivityFactory {
abstract fun getIntent(context: Context): Intent
fun start(context: Context) {
val intent = getIntent(context)
context.startActivity(intent)
}
fun startForResult(activity: Activity, requestCode:
Int) {
val intent = getIntent(activity)
activity.startActivityForResult(intent,
requestCode)
}
}
class MainActivity : AppCompatActivity() {
//...
companion object: ActivityFactory() {
override fun getIntent(context: Context): Intent =
Intent(context, MainActivity::class.java)
}
}
// Usage
val intent = MainActivity.getIntent(context)
MainActivity.start(context)
MainActivity.startForResult(activity, requestCode)
The abstract companion factory here, can save state, also build mock contract for test
Sometimes we can't change interface or we need to write factory function in different file, we can choose extend
//interface can't change
interface Tool {
companion object { /*...*/ }
}
using extend function
fun Tool.Companion.createBigTool( /*...*/ ) : BigTool {
//...
}
``` kotlin
At the call site we can then write:
``` kotlin
Tool.createBigTool()
This is very convenience, we can write factory function quickly, but it require the source interface use Companion object
Top level factory function is common, like listOf
mapOf
, but be careful when using top level function, since it make ide prompt being confused
Constructor just like top level, is can see from global, the different is capitalize, and fake constructor using this point to make function look like constructor, to get the following benefit
reifield
public inline fun <T> List(
size: Int,
init: (index: Int) -> T
): List<T> = MutableList(size, init)
public inline fun <T> MutableList(
size: Int,
init: (index: Int) -> T
): MutableList<T> {
val list = ArrayList<T>(size)
repeat(size) { index -> list.add(init(index)) }
return list
}
Besides above, fake contractor should behavior as constructor, if you need cache, using named function, nullable
Will factory functions replace constructor?
No, factory function still require constructor, but it is more flexible, independence and allow nullable
Compare factory pattern and factory function, factory pattern got the advantage of holding state, they are allow to have properties, and using property to adjust constructor