7

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

bogen
  • 9,954
  • 9
  • 50
  • 89

3 Answers3

1

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
Grimxn
  • 22,115
  • 10
  • 72
  • 85
1

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)
    }
}
kmithi1
  • 1,737
  • 2
  • 15
  • 18
  • That's only true if the subclass calls the *same* initializer of the parent. However if the subclasses call a special (e.g. private) initializer of the superclass it won't infinitely recurse. That's how it worked in Obj-C. – devios1 Jul 12 '17 at 16:24
1

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
        }
    }
}
Jakub Vano
  • 3,833
  • 15
  • 29
  • Yeah thats my current solution, was wondering if anyone had any for initializers :) – bogen May 25 '15 at 17:19
  • If you want to use the initializer syntax for this, you can change the static method to an init (`init?(unit:String)`) and then use `self = .Mass` or `self = .Volume` to create an instance of `Measurement` – Caleb Kleveter Mar 15 '18 at 15:24