Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Make initializer for superclass return a specific subclass? [duplicate]

Tags:

swift

subclass

I'm making a unit framework in Swift, which has measurement superclass and subclasses for different units, such as Mass and Volume. A feature is to allow the framework to correctly guess what unit it is created with and returning the correct class. example code:

class Measurement {
   var unitString : String
   init(unknownUnit: String) {
       // checks if the unit is either Volume or Mass, and returns an instance of that class
   }
}

class Volume : Measurement {
    init(unitString: String) {

    }
}

class Mass : Measurement {
    init(unitString: String) {

    }
}

let mass = Mass("kg")                   // class: Mass
let volume = Volume("ml")               // class: Volume
let shouldBeVolume = Measurement("ml")  // class: Volume
let shouldBeMass = Measurement("kg")    // class: Mass

Is it possible to have a inherited class create an object of a specific subclass when initializing it?

Library is named Indus Valley and open source on GitHub

like image 889
bogen Avatar asked May 25 '15 11:05

bogen


3 Answers

It's playing fast and loose with inheritance having the parent class know about its subclasses (very poor anti-pattern!) but this would work...

class Measurement {
    var unitString : String

    class func factory(unknownUnit: String) -> Measurement {
        if unknownUnit == "kg" {
            return Mass(myUnit: unknownUnit)
        } else { // Random default, or make func return Measurement? to trap
            return Volume(myUnit: unknownUnit)
        }
    }

    init(myUnit: String) {
        // checks if the unit is either Volume or Mass, and returns an instance of that class
        self.unitString = myUnit
    }
}

class Volume : Measurement {
}

class Mass : Measurement {
}

let mass = Mass(myUnit: "kg")                   // class: Mass
let volume = Volume(myUnit: "ml")               // class: Volume
let shouldntBeVolume = Measurement(myUnit: "ml")  // class: Measurement
let shouldntBeMass = Measurement(myUnit: "kg")    // class: Measurement
let isVolume = Measurement.factory("ml")  // class: Volume
let shouldBeMass = Measurement.factory("kg")    // class: Mass
like image 168
Grimxn Avatar answered Oct 18 '22 21:10

Grimxn


If you are creating subclass object in superclass then you app will crash because call to init method will be recursive. To test you can just create a hierarchy of class and try to create subclass object in super class.

You can solve this problem by using faced design pattern. Just you need to create one Interface class that internally use all other classes and create the object and return.

class UnitConverter {
    class func measurement(unknownUnit: String) -> Measurement {
        if unknownUnit == "kg" {
            return Mass(unknownUnit)
        } else if unknownUnit == "ml" {
            return Volume(unknownUnit)
        }

        return Measurement(unknownUnit)
    }
}
like image 41
kmithi Avatar answered Oct 18 '22 20:10

kmithi


Depending on the use case, maybe this could be a suitable alternative:

enum Measurement {
    case Mass
    case Volume

    static func fromUnit(unit:String) -> Measurement? {
        switch unit {
            case "mg", "g", "kg": return Mass
            case "ml", "dl", "l": return Volume

            default: return nil
        }
    }
}
like image 1
Jakub Vano Avatar answered Oct 18 '22 20:10

Jakub Vano