Kotlin设计模式之抽象工厂

提供一个接口,用于创建相关或依赖对象系列,而无需指定它们的具体类。
抽象工厂------GoF

1、抽象工厂示例

为了更好地理解这种设计模式,我们直接跳入一个小例子。假设我们正在玩一款具有地图/景观的游戏(例如 帝国时代或类似的策略游戏)。游戏必须支持不同的地区,例如福雷斯特、沙漠等。根据地区的不同,可能需要不同的对象。森林里有树,沙漠里有仙人掌。事实证明,这种思维当时可以应用许多不同的对象(例如地形、食物等)。如果我们说有"植被"或有"地形",就有可能将游戏与区域完全脱钩。通过这种方式,高级游戏仅了解其他高级类别的一些信息。

但当然Game类必须使用具体对象。这就是工厂发挥作用的地方。它提供了创建具体对象的中央访问点。根据区域的不同,它会创建不同的对象。下面的UML图说明了这一点。所有蓝色的班级都是高级班级。所有绿色的类都是福雷斯特相关的类。所有黄色的类别都是与沙漠相关的类别。

1.1、SOLID原则

在进入代码之前,我们想补充一下OO设计的SOLID原则。这种设计模式是这些原则的主要贡献者。它通过使用接口将高级类与低级类解耦。这会产生可重用的代码。添加新区域不会影响高级类别(它是开放-封闭的)。所有类都非常有凝聚力(它遵循单一职责)。高级类不依赖于实现细节(它遵循依赖倒置)。

事实上,抽象工厂模式很常见。因为它很容易理解,但仍会导致代码分离得很好。

1.2、代码

kotlin 复制代码
// Different landscape classes
open class Vegatation
class Cactus: Vegetation()
class Tree: Vegetation()

open class Terrain
class Sand: Terrain()
class Grass: Trerrain()
kotlin 复制代码
// Factory Interface
interface Factory {
    fun createTerrain(): Terrain
    fun createVegetation(): Vegetation
}
kotlin 复制代码
// Desert Factory
class DesertFactory: Factory {
    override fun createTerrain(): Terrain {
        return Sand()
    }
    
    override fun createVegetation(): Vegetation {
        return Cactus()
    }
}
kotlin 复制代码
// Forrest Factory
class ForrestFactory: Factory {
    override fun createTerrain(): Terrain {
        return Grass()
    }
    
    override fun createVegetation(): Vegetation {
        return Tree()
    }
}
kotlin 复制代码
// Game
class Game(val factory: Factory) {
    private lateinit var terrain: Terrain
    private lateinit var tree: Vegetation
    
    init {
        terrain = factory.createTerrain()
        tree = factory.createVegetation()
    }
}

正如您所看到,实现非常简单。Kotlin提供了不同的方法来实现多态性。第一个是使用基类。此方法用于地形和植被类。第二个选项是使用用于工厂的接口。这取决于您需要使用的上下文,但我们想在代码中显示这两个版本。

2、TDD------测试驱动开发

抽象通常经常与测试驱动开发结合在一起出现。正如所说,TDD将带来SOLID代码,因此在进行TDD时发现这种模式也就不足为奇了。

作为一个例子,考虑一个调用URL的函数。如果调用成功(例如状态码200),它将返回字符串"OK"。否则返回"FAILURE"。

一种实现可能如下。callUrl函数就是需要测试的函数。在内部它使用名为Network的类,该类执行实际的网络访问(此处未实现)。这可能是协程或类似的东西。

kotlin 复制代码
// Not using TDD
class Network(private val url: String = "") {
    private var errorCode = 200
    
    fun isSuccessful(): Boolean {
        return errorCode == 200
    }
    
    fun execute() {
    
    }
}

fun callUrl(url: String): String {
    val network = Network(url)
    network.execute()
    if (network.isSuccessful()) return "OK"
    return "FAILURE"
}

然而,这段代码即使不是不可能测试,也是非常困难的。测试中网络访问不受控制。这可以通过使用抽象工厂模式引入模拟网络对象来改变。下面的例子证明了这一点。

kotlin 复制代码
interface Network {
    fun isSuccessful(): Boolean
    fun execute()
}

interface NetworkFactory {
    fun createNetwork(url: String): Network
}
kotlin 复制代码
// Concrete Implementation of Network
class NetworkImpl(private val url: String = ""): Network {
    private var errorCode = 200
    
    override fun isSuccessful(): Boolean {
        return errorCode == 200
    }
    
    override fun execute() {
    
    }
}

class NetworkFactoryImpl: NetworkFactory {
    override fun createNetwork(url: String): Network {
        return NetworkImpl(url)
    }
}
kotlin 复制代码
// Mock Network Implementation
class NetworkMock: Network {
    override fun isSuccessful(): Boolean {
        return true
    }
    
    override fun execute() {
    
    }
}

class NetworkMockFactory: NetworkFactory {
    override fun createNetwork(url: String): Network {
        return NetworkMock()
    }
}

测试中的功能仅略有变化。默认情况下它使用网络工厂的具体实现。然而在测试过程中它接受返回Mock对象的工厂。由于我们控制着这个工厂以及模拟对象,因此我们可以手动控制网络行为。此外,对于使用此功能的客户端,没有任何变化。函数的签名是相同的(感谢默认参数)。在内部,即使只改变了一行,它也极大地提高了代码质量,因为现在,函数依赖于接口,而不是具体的对象。

kotlin 复制代码
// Using the factory
fun callUrl(url: String, factory: NetworkFactory = NetworkFactoryImpl()): String {
    val network = factory.createNetwork(url)
    network.execute()
    if (network.isSuccessful()) return "OK"
    return "FAILURE"
}

3、替代方案/相关模式

有一些模式可以与抽象工厂一起使用或者可以用作替代。

3.1、建造者模式

当具体对象如此不同以至于他们不共享相同的接口时,通常使用构建器模式。然而结构是相似的。

3.2、单例模式

由于工厂通常是单个实例,因此控制它们的创建是有意义的。单例模式确保只能创建一个工厂对象,并且易于访问。但缺点是他会稍微降低灵活性。

3.3、装饰者模式

使用此模式的一个优点,它提供了一个完美地访问点,通过使用装饰器模式向对象添加附加行为。例如,在前面有关Network类的示例中,我们可以添加一个装饰器来记录每个网络访问。这可以完成,无需触及所有网络类。

kotlin 复制代码
// Using a decorator
class LogNetworkDecorator(private var network: Network): Network {
    override fun isSuccessful(): Boolean {
        return network.isSuccessful()
    }
    
    override fun execute() {
        // log
        return network.execute()
    }
}

class NetworkFactoryImpl: NetworkFactory {
    override fun createNetwork(url: String): Network {
        var obj = NetworkImpl(url)
        return LogNetworkDecorator(obj)
    }
}
相关推荐
龙哥·三年风水4 小时前
活动系统开发之采用设计模式与非设计模式的区别-后台功能总结
设计模式·php·tinkphp6
一头老羊5 小时前
前端常用的设计模式
设计模式
Jouzzy5 小时前
【Android安全】Ubuntu 16.04安装GDB和GEF
android·ubuntu·gdb
极客先躯5 小时前
java和kotlin 可以同时运行吗
android·java·开发语言·kotlin·同时运行
严文文-Chris5 小时前
【设计模式-组合】
设计模式
kimloner6 小时前
工厂模式(二):工厂方法模式
java·设计模式·工厂方法模式
Good_tea_h8 小时前
Android中的单例模式
android·单例模式
丶白泽9 小时前
重修设计模式-结构型-桥接模式
java·设计模式·桥接模式
南郁10 小时前
把设计模式用起来!(3)用不好模式?之时机不对
设计模式
Lill_bin12 小时前
Lua编程语言简介与应用
开发语言·数据库·缓存·设计模式·性能优化·lua