Custom class clusters in Swift

前端 未结 7 2026
灰色年华
灰色年华 2020-12-02 00:04

This is a relatively common design pattern:

https://stackoverflow.com/a/17015041/743957

It allows you to return a subclass from your init calls.

7条回答
  •  没有蜡笔的小新
    2020-12-02 00:12

    Since init() doesn't return values like -init does in Objective C, using a factory method seems like the easiest option.

    One trick is to mark your initializers as private, like this:

    class Person : CustomStringConvertible {
        static func person(age: UInt) -> Person {
            if age < 18 {
                return ChildPerson(age)
            }
            else {
                return AdultPerson(age)
            }
        }
    
        let age: UInt
        var description: String { return "" }
    
        private init(_ age: UInt) {
            self.age = age
        }
    }
    
    extension Person {
        class ChildPerson : Person {
            let toyCount: UInt
    
            private override init(_ age: UInt) {
                self.toyCount = 5
    
                super.init(age)
            }
    
            override var description: String {
                return "\(self.dynamicType): I'm \(age). I have \(toyCount) toys!"
            }
        }
    
        class AdultPerson : Person {
            let beerCount: UInt
    
            private override init(_ age: UInt) {
                self.beerCount = 99
    
                super.init(age)
            }
    
            override var description: String {
                return "\(self.dynamicType): I'm \(age). I have \(beerCount) beers!"
            }
        }
    }
    

    This results in the following behavior:

    Person.person(10) // "ChildPerson: I'm 10. I have 5 toys!"
    Person.person(35) // "AdultPerson: I'm 35. I have 99 beers!"
    Person(35) // 'Person' cannot be constructed because it has no accessible initializers
    Person.ChildPerson(35) // 'Person.ChildPerson' cannot be constructed because it has no accessible initializers
    

    It's not quite as nice as Objective C, since private means all the subclasses need to be implemented in the same source file, and there's that the minor syntax difference Person.person(x) (or Person.create(x) or whatever) instead of simply Person(x), but practically speaking, it works the same.

    To be able to instantiate literally as Person(x), you could turn Person into a proxy class which contains a private instance of the actual base class and forwards everything to it. Without message forwarding, this works for simple interfaces with few properties/methods but it gets unwieldy for anything more complex :P

提交回复
热议问题