Пользовательские кластеры классов в Swift

Это относительно общий шаблон проектирования:

qaru.site/info/494399/...

Он позволяет вам возвращать подкласс из ваших вызовов init.

Я пытаюсь найти лучший метод достижения того же самого, используя Swift.

Я знаю, что очень вероятно, что есть лучший способ достичь того же самого с Swift. Однако мой класс будет инициализирован существующей библиотекой Obj-C, которой я не контролирую. Поэтому он должен работать таким образом и быть вызванным из Obj-C.

Любые указатели будут очень оценены.

Ответ 1

Я не считаю, что этот шаблон можно напрямую поддерживать в Swift, поскольку инициализаторы не возвращают значение, как в Objective C, поэтому вы не получаете возможность вернуть экземпляр альтернативного объекта.

Вы можете использовать метод типа как объект factory - довольно надуманный пример -

class Vehicle
{
    var wheels: Int? {
      get {
        return nil
      }
    }

    class func vehicleFactory(wheels:Int) -> Vehicle
    {
        var retVal:Vehicle

        if (wheels == 4) {
            retVal=Car()
        }
        else if (wheels == 18) {
            retVal=Truck()
        }
        else {
            retVal=Vehicle()
        }

        return retVal
    }

}

class Car:Vehicle
{
    override var wheels: Int {
      get {
       return 4
      }
    }
}

class Truck:Vehicle
{
    override var wheels: Int {
      get {
          return 18
       }
     }
}

main.swift

let c=Vehicle.vehicleFactory(4)     // c is a Car

println(c.wheels)                   // outputs 4

let t=Vehicle.vehicleFactory(18)    // t is a truck

println(t.wheels)                   // outputs 18

Ответ 2

"swifty" способ создания кластеров классов фактически состоит в том, чтобы выставлять протокол вместо базового класса.

По-видимому, компилятор запрещает статические функции для протоколов или расширений протокола.

До тех пор, пока, например, https://github.com/apple/swift-evolution/pull/247 (factory инициализаторы) принимается и реализуется, единственный способ, который я мог бы найти для этого, - это следующее:

import Foundation

protocol Building {
    func numberOfFloors() -> Int
}

func createBuilding(numberOfFloors numFloors: Int) -> Building? {
    switch numFloors {
    case 1...4:
        return SmallBuilding(numberOfFloors: numFloors)
    case 5...20:
        return BigBuilding(numberOfFloors: numFloors)
    case 21...200:
        return SkyScraper(numberOfFloors: numFloors)
    default:
        return nil
    }
}

private class BaseBuilding: Building {
    let numFloors: Int

    init(numberOfFloors:Int) {
        self.numFloors = numberOfFloors
    }

    func numberOfFloors() -> Int {
        return self.numFloors
    }
}

private class SmallBuilding: BaseBuilding {
}

private class BigBuilding: BaseBuilding {
}

private class SkyScraper: BaseBuilding {
}

.

// this sadly does not work as static functions are not allowed on protocols.
//let skyscraper = Building.create(numberOfFloors: 200)
//let bigBuilding = Building.create(numberOfFloors: 15)
//let smallBuilding = Building.create(numberOfFloors: 2)

// Workaround:
let skyscraper = createBuilding(numberOfFloors: 200)
let bigBuilding = createBuilding(numberOfFloors: 15)
let smallBuilding = createBuilding(numberOfFloors: 2)

Ответ 3

Так как init() не возвращает значения, такие как -init в Objective C, использование метода factory кажется самым простым вариантом.

Один трюк состоит в том, чтобы пометить ваши инициализаторы как private, например:

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!"
        }
    }
}

Это приводит к следующему поведению:

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

Это не так хорошо, как Objective C, поскольку private означает, что все подклассы должны быть реализованы в том же исходном файле, и там, что незначительная разность синтаксиса Person.person(x) (или Person.create(x) или что-то еще) вместо просто Person(x), но практически говоря, он работает одинаково.

Чтобы создать экземпляр буквально как Person(x), вы можете превратить Person в прокси-класс, который содержит частный экземпляр фактического базового класса и перенаправляет все на него. Без переадресации сообщений это работает для простых интерфейсов с несколькими свойствами/методами, но для громоздких задач он становится громоздким: P

Ответ 4

Я думаю, что на самом деле шаблон Cluster может быть реализован в Swift с использованием функций runtime. Главное - заменить класс вашего нового объекта подклассом при инициализации. Код ниже работает нормально, хотя я думаю, что больше внимания следует уделять инициализации подкласса.

class MyClass
{
    var name: String?

    convenience init(type: Int)
    {
        self.init()

        var subclass: AnyClass?
        if type == 1
        {
            subclass = MySubclass1.self
        }
        else if type == 2
        {
            subclass = MySubclass2.self
        }

        object_setClass(self, subclass)
        self.customInit()
    }

    func customInit()
    {
        // to be overridden
    }
}

class MySubclass1 : MyClass
{
    override func customInit()
    {
        self.name = "instance of MySubclass1"
    }
}

class MySubclass2 : MyClass
{
    override func customInit()
    {
        self.name = "instance of MySubclass2"
    }
}

let myObject1 = MyClass(type: 1)
let myObject2 = MyClass(type: 2)
println(myObject1.name)
println(myObject2.name)