Swift中的静态函数封装,通过传递Protocols.Type比OO封装更好,同样可测试



假设我有一个不需要共享和存储状态的函数;我应该使用静态类/结构/枚举来保存函数吗?我在很多地方读到,使用静态函数来保存代码是一种糟糕的设计,因为静态函数不符合SOLID原则,被认为是过程代码。可测试性似乎是存在的,因为我可以通过注入mock静态枚举来隔离具有注入的静态枚举的父类。

例如,我可以通过使用静态函数的协议进行封装并具有多态性:

静态协议方法

enum StaticEnum: TestProtocol {
static func staticMethod() {
print("hello")
}
}
enum StaticEnum2: TestProtocol {
static func staticMethod() {
print("hello2")
}
}
protocol TestProtocol {
static func staticMethod()
}
class TestClass {
let staticTypes: [TestProtocol.Type]
init (staticTypes: [TestProtocol.Type]) {
self.staticTypes = staticTypes
}
}
class TestFactory {
func makeTestClass() -> TestClass {
return TestClass(staticTypes: [StaticEnum.self, StaticEnum2.self])
}
}

面向对象方法

class InstanceClass: TestProtocol {
func instanceMethod() {
print("hello")
}
}
class InstanceClass2: TestProtocol {
func instanceMethod() {
print("hello2")
}
}
protocol TestProtocol {
func instanceMethod()
}
class TestClass {
let instances: [TestProtocol]
init (instances: [TestProtocol]) {
self.instances = instances
}
}
class TestFactory {
func makeTestClass() -> TestClass {
return TestClass(instances: [InstanceClass(), InstanceClass2()])
}
}

静态版本仍然允许协议多态性,因为您可以让多个枚举遵守静态协议。此外,在创建静态函数的第一个调度调用之后不需要初始化。使用静态协议方法有什么缺点吗?

为什么静态方法会违反SOLID

我在很多地方读到,使用静态函数来保存代码是一种糟糕的设计,因为静态函数不符合SOLID原则,被认为是过程代码。

我怀疑您已经在其他语言(如Java)的上下文中阅读过这方面的内容,为了简单起见,我将使用Java作为具体的示例。

事实上,Java也是如此。但是,与其简单地引用static为什么是魔鬼,不如了解确切的细节。

在Java中,static方法是真正静态的。它们实际上与一个全局函数没有什么不同,只是碰巧与一个类同名。

因此,它违反了依赖性反转原则(SOLID中的"D")。Java中静态方法的任何使用都是这样的情况:调用代码依赖于具体化(方法的特定实现),而不是抽象化(描述该方法的接口),这使得多态性变得不可能。从本质上讲,没有办法用一种实现来替代另一种实现。

但斯威夫特没有

斯威夫特的情况并非如此。

在Swift中,当一个类型符合协议时,该类型的实例保证满足协议对实例方法、属性和下标的要求,就像Java一样。

但正如您所发现的,当Swift类型符合协议时,该类型本身就保证满足静态方法、静态属性、静态下标和初始值设定项的协议要求。

这超出了Java接口所能表达的范围。

我喜欢通过将每个Swift协议想象成(最多)两个传统Java接口合一的方式来可视化它。

当你有:

protocol MyProtocol {
static func myStaticMethod() {}
func myInstanceMethod() {}
}
struct MyImplementation: MyProtocol {
static func myStaticMethod() {}
func myInstanceMethod() {}
}

它的行为就像你有(psuedo代码):

interface MyProtocol_MetaHalf {
static func myStaticMethod() {}
}
interface MyProtocol_InstancedHalf {
func myInstanceMethod() {}
}
struct MyImplementation.Type: MyProtocol_MetaHalf {
static func myStaticMethod() {}
}
struct MyImplementation: MyProtocol_InstancedHalf {
func myInstanceMethod() {}
}

您可能会意识到这与Java接口的常用方式极其相似。你通常会有一对,interface Foointerface FooFactory。我认为;工厂;Java中的类源于它们的接口无法表达静态需求,因此原本可以是静态方法的东西不得不被拆分为新类上的实例方法。

正如您所看到的,Swift的静态方法可以满足协议要求,并且可以对它们进行多态调用。示例:

protocol MyProtocol {
static func staticMethod()
}
struct Imp1: MyProtocol {
static func staticMethod() { print("Imp1") }
}
struct Imp2: MyProtocol {
static func staticMethod() { print("Imp1") }
}
let someImplementation: MyProtocol.Type = Imp1.self
// A polymorphic call to a static method
someImplementation.staticMethod()

因此,您可以看到Java接口的可测试性问题并没有转化为Swift的接口。您可以轻松地实现一个符合具有静态需求的协议的新mock类型,并为静态方法提供mock实现。


所有这些都意味着:你的提议是可能的(很明显,你提供了一个工作演示)。但这引出了一个问题:你为什么要这么做?

既然类型可以,为什么要使用元类型

拥有一个既支持类型(描述其实例、对象的方法)又支持元类型(描述实例、类型的方法)的类型系统对于表达诸如";我有一个小部件,它与一个制造小部件的东西配对,下面是它们如何组合成一个协议/类/结构">

但你所做的基本上。。。拨高";元级别";一个。在您的示例中,使用类型而不是对象,使用元类型而不是类型。但从根本上讲,您还没有实现任何无法使用对象+类型的典型配对更容易/更清晰地表达的东西。

以下是我如何表达你所写的内容:

struct Implementation1: TestProtocol {
func instanceMethod() {
print("hello")
}
}
struct Implementation2: TestProtocol {
func instanceMethod() {
print("hello2")
}
}
protocol TestProtocol {
func instanceMethod()
}
class TestClass {
let implementations: [TestProtocol]
init(implementations: [TestProtocol]) {
self.implementations = implementations
}
}
class TestFactory {
func makeTestClass() -> TestClass {
return TestClass(implementations: [Implementation1(), Implementation2()])
}
}

假设我有一个不需要共享和存储状态的函数

他们今天可能不需要,但也许他们将来会需要。也许不是。目前,我只是使用空结构对实现进行建模。

这可能是一个强调";我只需要这个";,vs";我坚信我只想要一件这样的东西;。

这听起来可能很傻/很明显,但你会惊讶于在这里频繁发帖询问它们如何成为单例的两个实例:)

如果您坚决认为您永远不需要状态,那么也许您可以使用singleton强制执行一个对象性约束:

struct Implementation1: TestProtocol {
public static var shared: Self { Self() }

private init() {}

func instanceMethod() {
print("hello")
}
}

最新更新