Выполнение действия на основе одного из трех вариантов более объектно-ориентированным способом

#swift #oop

#swift #ооп

Вопрос:

Мне нужно выполнить вычисление на основе одного из трех вариантов, которые может выбрать пользователь, я не создал никакого реального кода, потому что я не уверен, какой метод использовать, который более распространен в более ООП, чем мой спагетти-код ниже.

Какой наиболее распространенный способ отслеживания measurementType на основе следующего кода? Я никогда не использовал перечисления ни на одном языке, и мне было интересно, подойдет ли это для них.

Есть предложения?

 var measurementType:String = ""

let pounds = "Pounds"
let kilograms = "Kilograms"
let quantity = "Quantity"

// User selection
measurementType = pounds

// Perform calculation based on user's selection
if measurementType == kilograms{
    print("Calculation for Kilograms")
}else if(measurementType == pounds){
    print("Calculation for Pounds")
}else{
    print("Calculation for Quantity")
}
  

Ответ №1:

Я бы не назвал это спагетти-кодом, но, конечно, вы можете использовать enum в этом случае. Кроме того, вы можете захотеть использовать switch оператор вместо multiple if-else .

 enum MeasurementType {
    case Pounds
    case Kilograms
    case Quantity
}

var measurementType: MeasurementType = .Pounds

switch measurementType {
    case .Pounds:
        print("Calculation for Pounds")
    case .Kilograms:
        print("Calculation for Kilograms")
    case .Quantity:
        print("Calculation for Quantity")
    default:
        print("?")
        //or use () / break if the default doesn't interest you
}
  

Комментарии:

1. @ Sebastian — Спасибо, это очень помогает. Теперь давайте сделаем это немного более интересным, если выбор пользователя будет происходить во втором ViewController, а затем результат будет отправлен в основной ViewController через делегирование, где будет выполняться действие, enums все равно будет иметь смысл? Если да, как мне проверить состояние MeasurementType в моем главном ViewController?

2. Конечно, вы все равно можете использовать enum . Просто убедитесь, что вы объявили его вне области действия любого класса или функции, и он будет доступен везде. Не уверен, что я понимаю, что вы имеете в виду, проверяя статус MeasurementType , но вы можете получить к нему доступ так же, как и раньше.

3. @fs_tigre — конечно, я не понимаю, почему нет.

4. Здесь нет необходимости в вспомогательном классе. Перечисление может существовать само по себе, вне любого класса. У вас могут быть файлы, которые просто определяют перечисления.

5. @fs_tigre — перечисления на самом деле являются типами, такими же, как классы или структуры

Ответ №2:

measurementType не является хорошим типом. Фунты и килограммы параллельны. Масса (*) и количество параллельны. Но фунты, килограммы и количество не параллельны. Это тот код, из-за которого вы теряете свой Mars probe.

Во-первых, если это нацелено на iOS 10 или macOS 10.12, посмотрите на новый Measurement тип. Он разработан, чтобы помочь именно в такой ситуации (и имеет множество мощных функций локализованного форматирования). Но давайте предположим, что вы не можете это использовать или просто не хотите (его синтаксис иногда немного тяжелый). Как бы мы построили это чисто?

 enum MassUnit {
    case kilogram(Double)
    case pound(Double)
}

enum Measurement {
    case quantity(Int)
    case mass(MassUnit)
}

let measurement = Measurement.mass(.pound(10))

switch measurement {
case .quantity(let count):
    print("Calculation for Quantity")

case .mass(.kilogram(let mass)):
    print("Calculation for kilogram")

case .mass(.pound(let mass)):
    print("Calculation for pound")

}
  

Обратите внимание, как это объединяет значение измерения с его типом. Они больше не являются отдельными, поэтому их нельзя перепутать, и их можно передавать как одно значение. Обратите внимание также, что это позволяет mass быть Double, в то время как count должен быть Int . Компилятор также подтвердит, что вы рассмотрели все возможные случаи, и выдаст вам ошибку, если вы забудете один.

Конечно, мы можем пойти дальше. Мы могли бы устранить дублирование кода, переведя преобразования в тип. Тогда нам, вероятно, даже не нужно разделять «вычисление для килограмма» и «вычисление для фунта». Например:

 extension MassUnit {
    var kilograms: Double {
        switch self {
        case .kilogram(let kg): return kg
        case .pound(let lb): return 0.454 * lb
        }
    }
}
  

Это позволяет вам расширять, чтобы иметь граммы, камни или любые другие единицы массы, которые вы хотите, при этом легко выполнять все вычисления в килограммах, когда это необходимо (без потери исходной информации о единицах измерения, когда это полезно).

Этот подход также позволяет выполнять прямую математику для масс (что также может исключить ваши ножки переключения «килограмм против фунта»). Вы можете определить сложение следующим образом:

 extension MassUnit {
    static func   (lhs: MassUnit, rhs: MassUnit) -> MassUnit {
        switch (lhs, rhs) {
        case let (.kilogram(lhsValue), .kilogram(rhsValue)):
            return .kilogram(lhsValue   rhsValue)

        case let (.pound(lhsValue), .pound(rhsValue)):
            return .pound(lhsValue   rhsValue)

        case let (.kilogram(lhsValue), .pound(_)):
            return .kilogram(lhsValue   rhs.kilograms)

        case (.pound, .kilogram):
            return rhs   lhs
        }
    }
}
  

Итак, kg kg = kg, lb lb = lb, lb kg = kg. И, конечно, вы можете написать аналогичные операции для умножения на удвоение или тому подобное. Как только вы немного пройдете по этому пути, вы обнаружите, что он чрезвычайно гибкий.

(*) На этом этапе мы проигнорируем педантичные аргументы о том, являются ли фунты измерением силы, и постановим, что «фунт» и «фунт-масса» являются синонимами.

Комментарии:

1. @ Rob — Вау! Большое спасибо за хорошую информацию и хорошо реализованный код здесь. Я ухожу Sebastian от ответа, но ваш ответ заслуживает большего, чем голосование «большой палец вверх». Есть ли способ проголосовать и дать вам очки или что-то в этом роде здесь, в Stackoverflow?