提供一个接口,用于创建相关或依赖对象系列,而无需指定它们的具体类。
抽象工厂------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)
}
}