如何在Swift中实现"抽象"属性初始化器

3

到目前为止,我只参与过Objective-C项目,现在开始了我的第一个Swift项目。

我知道Swift不支持抽象类,但我想知道在Swift中建模/解决这个问题的最佳方法是什么:

// Abstract implementation
public abstract class MyClass {
    private SomeBaseClass someProperty; 

    MyClass() {
        initProperties();
    }

    abstract void initProperties();  // Init someProperty with some child class of SomeBaseClass.
}

// Objectiv-C
@implementation MyClass

- (id)init {
    self = [super init];

    if (self) {
        [self initProperties];     
    }

    return self;
}

- (void)initProperties {
    // Override in inherited classes
}


// Swift
class MyClass {
    // Should not be optional since MyClass should be required to have someProperty != nil
    let someProperty: SomeBaseClass;

    override init() {
        super.init();
        initProperties();
    }

    func initProperties() {
         // Cannot be empty since someProperty is non-optional and needs to be initialized
         // Cannot be empty since Swift does not support abstract methods
    }
}

当然,将someProperty定义为可选的SomeBaseClass?也是可能的,但在这种情况下,每次使用该属性时都必须进行测试和解包。
有更好的方法来解决这个问题吗?
编辑:
我知道Swift使用协议来创建类似于抽象类的抽象。然而,我不明白这个概念如何解决具体的问题/问题。
在其他编程语言中,抽象类MyClass可以在许多不同的地方使用属性someProperty,同时留下将该属性初始化为具体子类的负担。
尽管我阅读了@MohamendS链接的文章以及可能重复回答的答案,但我不明白如何使用协议实现相同的功能。
- MyClass 只有一个抽象函数,而所有其他函数都已实现。因此MyClass本身不能成为协议,因为协议不能实现函数(它们能吗?) - MyClass 只能实现另一个定义了必须有一个 initProperties 方法的协议。但在这种情况下, MyClass 需要提供此方法的实现,这使我们回到了同样的问题。
我想我看不到树木,但协议怎么能帮助这里呢?

我不明白这个概念如何解决具体的问题/疑问。这是问题的核心:这个问题不具体,它是一个抽象的问题。如果上面的代码确实是你计划发布的实际代码,那么答案就是“删除someProperty,因为它从未被使用。”但对于你的具体问题,几乎肯定不是正确的答案。我们只是不知道你的具体问题是什么,所以很难讨论哪种协议是适当的。协议是一种不同于抽象类的思考问题的方式。 - Rob Napier
更新了,也许这能给你一个想法。 - Mohmmad S
2个回答

4

Swift 中的抽象概念是使用 protocols 实现的,建议阅读文章以了解更多,并附有示例。

 protocol Abstraction {
    var foo: String { get }
    func fee()
    init(with foo: String)
}

class A: Abstraction {

    required init(with foo: String) {
        self.foo = foo 
    }
    var foo: String = ""

    func fee() {

      }
}

编辑:根据你的观点,协议无法实现函数。

但是,您可以使用扩展来扩展这些协议并给它们一个初始实现,因此您不必在类中实现它们。当您感到需要时,可以在下面的代码中查看。

    class A: Abstraction {

    required init(with foo: String) {
        self.foo = foo
    }
    var foo: String = ""

    //you don't have to implement func foo anymore as its extended in the extension
}

extension Abstraction {
    func fee() {
        print("ok")
    }
}

let a = A(with: "foo")
a.fee() // this will trigger the extension implementation,

现在,为了在每次确认时不必输入它们,您可以在扩展体内使用 init,请查看下面的代码。
protocol Abstraction {
    var foo: String { get set }
    func fee()
    init()
    init(with foo: String)
}


class A: Abstraction {
    required init() { }
    var foo: String = ""

    //you don't have to implement func foo anymore as its extended in the extension 
   // you don't have to implement the custom init anymore too 

}

extension Abstraction {
    init(with foo: String) {
        self.init()
        self.foo = foo
    }
    func fee() {
        print("ok")
    }
}

2
是的,值得指出的是,您可以在协议中拥有一个“init”方法。 - Joakim Danielson
1
@JoakimDanielson 我已经在示例中添加了它。 - Mohmmad S

3
根据MyClass的使用方式,对于这个问题有许多可能的答案。就目前而言,没有理由将someProperty放在基类中,也绝对没有必要使用initProperties()方法(应该在init中实现)。
我知道这只是一个例子,但它展示了创建不必要层次结构的常见问题。虽然可以使用半抽象的基类编写此代码,但通常应避免这样做,因此首先要考虑的问题是您使用这个东西的目的,我们是否可以完全避免这个问题?
针对所给问题的回答,您可能需要创建一个默认的SomeBaseClass,以便抽象类可以直接分配someProperty = SomeBaseClass()
如果这不可能,一般会使用!类型:
let someProperty: SomeBaseClass!

你需要使用fatalError来实现initProperties()

最初的回答:

func initProperties() { fatalError("Implement in subclass") }

或者,将 someProperty 实现为计算属性,并根据子类中的某些其他属性实现它会更方便。

最初的回答:

你可以选择将someProperty实现为计算属性,并根据子类中的某些其他属性实现它。这样做可能更加方便。

var someProperty: SomeBaseClass { fatalError() }

但这真的是最后的手段。每当你发现自己不得不写fatalError时,你可能走错了路,你不需要一个技巧来解决它;你需要重新考虑问题。
首先,你应该考虑一下MyClass是如何被使用的,并且考虑它是否可以成为一个值类型。另外,你应该考虑一下它是否可以成为一个与用例相匹配的协议。协议不仅仅是隐藏实现的抽象接口。它们是对符合特定问题的类型的视图。这就是为什么有一个Collection协议,提供对许多不相关类型的大量算法的访问,而没有一个只是为了隐藏Array的实现的ArrayProtocol。不要将MyClass变成MyClassProtocol。问一下什么样的算法想要使用像这样的类型。
当你发现自己创建了交织的类型层次结构(某些东西的子类需要其他东西的子类)时,你通常是沿着错误的方向切分问题。你应该重新思考是否可以将SomeBaseClass的不同部分实际上作为MyClass的一部分(通常这会使SomeBaseClass更简单;例如,纯数据而不是具有逻辑)。
这里没有一个正确的答案。它取决于MyClass的性质,所以我们无法用抽象的术语来讨论它。像抽象类一样,解决抽象问题常常会使你走上错误的道路。通常最好从具体类型开始,然后找到它们的相似之处并提取它们。
即使这样说,这里也值得展示一个简单、天真的协议会是什么样子。 (这可能甚至是正确的协议。)
public protocol MyProtocol {
    var someProperty: SomeBaseClass { get }
}

那就是它。这就是您当前抽象类中实际表达的全部内容(而且不清楚someProperty是否为公共属性;如果它是私有的,那么该协议将为空)。
实现结构体看起来会像这样:
struct MyStruct: MyProtocol {
    var someProperty: SomeBaseClass
}

或者如果你需要一个引用类型,你可以使用一个final类:

最初的回答

final class MyClass: MyProtocol {
    var someProperty: SomeBaseClass
    init() {
        someProperty = ...
    }
}

或者如果你想要继承,你可以使用一个非final类:最初的回答。
class MyClass: MyProtocol {
    var someProperty: SomeBaseClass
    init() {
        someProperty = ...
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接