Vraag Groepsclusters op maat in Swift


Dit is een relatief veel voorkomend ontwerppatroon:

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

Hiermee kunt u een subklasse van uw terugsturen init noemt.

Ik probeer de beste methode te bedenken om hetzelfde te bereiken met Swift.

Ik weet wel dat het zeer waarschijnlijk is dat er een betere methode is om hetzelfde met Swift te bereiken. Mijn klas zal echter worden geïnitialiseerd door een bestaande Obj-C-bibliotheek, waar ik geen controle over heb. Dus het moet op deze manier werken en op te halen zijn bij Obj-C.

Alle aanwijzingen worden zeer op prijs gesteld.


11
2018-06-03 11:33


oorsprong


antwoorden:


Ik geloof niet dat dit patroon direct in Swift kan worden ondersteund, omdat initialisers geen waarde teruggeven zoals in Objective C - dus je krijgt geen mogelijkheid om een ​​alternatieve objectinstantie te retourneren.

Je kunt een typemethode gebruiken als een objectenfabriek - een redelijk gekunsteld voorbeeld is -

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

7
2018-06-03 12:27



De "swifty" manier om klasseclusters te creëren zou eigenlijk zijn om een ​​protocol te tonen in plaats van een basisklasse.

Blijkbaar verbiedt de compiler statische functies op protocollen of protocol-uitbreidingen.

Tot b.v. https://github.com/apple/swift-evolution/pull/247 (fabrieksinitializers) wordt geaccepteerd en geïmplementeerd, de enige manier om dit te doen is het volgende:

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)

4
2018-06-20 15:18



Sinds init() retourneert geen waarden als -init doet in Objective C, het gebruik van een fabrieksmethode lijkt de gemakkelijkste optie.

Een truc is om je initializers als te markeren private, soortgelijk:

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

Dit resulteert in het volgende gedrag:

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

Het is niet zo leuk als Objective C sindsdien private betekent dat alle subklassen moeten worden geïmplementeerd in hetzelfde bronbestand, en daar is dat kleine verschil in syntaxis Person.person(x) (of Person.create(x) of wat dan ook) in plaats van simpelweg Person(x), maar praktisch gezien werkt het hetzelfde.

Letterlijk kunnen converteren als Person(x), je zou kunnen draaien Person in een proxy-klasse die een privé-instantie van de feitelijke basisklasse bevat en daarin alles doorstuurt. Zonder het doorsturen van berichten werkt dit voor eenvoudige interfaces met een paar eigenschappen / methoden, maar het wordt onpraktisch voor alles wat complexer is: P


1
2017-09-13 14:05



Ik denk dat het clusterpatroon in Swift feitelijk kan worden geïmplementeerd met behulp van runtime-functies. Het belangrijkste punt is om de klasse van uw nieuwe object te vervangen door een subklasse bij het initialiseren. De onderstaande code werkt prima, hoewel ik denk dat er meer aandacht moet worden besteed aan de initialisatie van de subklasse.

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)

0
2018-06-07 10:36