Нарушает ли использование enum с соответствующим значением в Swift принцип подстановки Лискова?

#swift #enums #solid-principles #liskov-substitution-principle

#swift #перечисления #solid-принципы #принцип подстановки Лискова

Вопрос:

 enum WeatherType {
    case cloudy(coverage: Int)
    case sunny
    case windy
}
  

Я только что видел это в учебном пособии по Swift и не могу поверить, что они позволяют вам это делать. Теперь, всякий раз, когда я включаю это перечисление, я должен создать специальный случай для cloudy !

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

1. Как говорит Vaca ниже, хотя это очень интересное обсуждение, оно, похоже, не относится конкретно к «Принципу подстановки Лискова».

Ответ №1:

Вы ничего не «должны» делать. Если вам все равно, что такое покрытие, не спрашивайте, что такое покрытие. Если вам все равно, облачно ли, не спрашивайте, облачно ли. Нет ничего особенного в том, как вы пишете переключатель для случая, который имеет связанное значение.

Предположим, у нас есть это:

 let weather = WeatherType.cloudy(coverage:1)
  

Тогда это совершенно законно:

 switch weather {
case .sunny:
    print("it is sunny")
default:
    print("I guess it's cloudy, or maybe windy")
}
  

И это тоже!

 switch weather {
case .cloudy:
    print("it is cloudy")
default:
    print("I guess it's sunny, or maybe windy")
}
  

И ни один закон не требует, чтобы вы вообще писали оператор switch. Если вы просто хотите узнать, есть ли weather .cloudy , просто спросите:

 if case .cloudy = weather {
    print("yes it is cloudy")
} else {
    print("I guess it's sunny, or maybe windy")
}
  

И если вы действительно хотите узнать, что coverage это такое, вам все равно не нужно писать оператор switch:

 if case let .cloudy(cov) = weather {
    print("yes it is cloudy, in fact it is (cov)")
} else {
    print("I guess it's sunny, or maybe windy")
}
  

Чего вы не можете сделать, так это применить == . Это не будет компилироваться:

 if weather == .sunny { // error
  

В этом смысле, да, перечисление со связанным значением ведет себя иначе, чем перечисление без связанного значения (если это то, что вы спрашиваете).

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

1. Спасибо вам за это. Мне просто кажется странным, что все члены enum не используют один и тот же набор параметров. Это, конечно, не совсем LSP, но это аналогичная ситуация, когда взаимозаменяемые объекты имеют разное поведение.

2. Перечисление — это тип. Такая вещь, как .cloudy не является «членом»; это экземпляр. Разные экземпляры не могут иметь разные данные?

3. Да, я думаю, что это нарушает LSP, разные экземпляры могут иметь разные данные, но в одном и том же формате. Если у вас есть перечисления со связанными типами, вы не можете работать с ними одинаково. Это может генерировать беспорядочный код и множество случаев исключения.

Ответ №2:

Теперь, всякий раз, когда я включаю это enum , я должен создать специальный случай для cloudy !

Это не так по нескольким причинам:

  • Вы можете игнорировать cloudy , объединяя его с default , и
  • Сопоставление с образцом позволяет считывать coverage свойство cloudy , рассматривая cloudy его как единичный случай.
  • Когда вы читаете свойство coverage , у вас есть возможность игнорировать его или действовать в соответствии с ним.

Вот несколько примеров:

 switch weatherType {
case .Cloudy(_):
    print("It is cloudy. I ignore the coverage.")
...
}

switch weatherType {
case .Cloudy(let coverage):
    print("It is cloudy. Coverage is (coverage).")
...
}

switch weatherType {
case .Cloudy(let coverage) where coverage > 80:
    print("It is very cloudy.")
case .Cloudy(let coverage) where coverage < 20:
    print("It is slightly cloudy.")
case .Cloudy(_):
    print("It is cloudy.")
...
}
  

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

1. Я бы хотел увидеть комментарий к анонимному понижению здесь.

2. Анонимное голосование против — одна из моих наименее любимых функций Stack Overflow. Как вы должны решить проблему (если она действительно есть), не зная, почему человек проголосовал против? Я никогда не отклоняю ответ другого человека на вопрос, на который я ответил, но я голосую за других, как я только что сделал здесь.

3. Спасибо вам за это. Мне просто кажется странным, что все члены enum не используют один и тот же набор параметров. Это, конечно, не совсем LSP, но это аналогичная ситуация, когда взаимозаменяемые объекты имеют разное поведение.

Ответ №3:

Принцип подстановки Лискова связан с подклассами. Перечисления не допускают подклассов, поэтому здесь это неприменимо.

Я не уверен, в чем заключается ваше возражение по поводу создания специального случая cloudy ; вы должны сделать это в любом случае, switch если хотите конкретно обработать этот случай. Дополнительный синтаксис для захвата связанного значения довольно прост.

 let weather = WeatherType.cloudy(coverage: 17)

switch weather {
case .sunny:
    print("sunny")
case .windy:
    print("windy")
case .cloudy(let coverage):
    print("cloudy with coverage (coverage)")
}
  

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

1. Спасибо вам за это. Мне просто кажется странным, что все члены enum не используют один и тот же набор параметров. Это, конечно, не совсем LSP, но это аналогичная ситуация, когда взаимозаменяемые объекты имеют разное поведение.

2. Я могу понять, что это кажется немного странным, но это очень полезно. На самом деле, опции — это просто перечисления под капотом. Optional<Int> имеет 2 значения перечисления: .none которое представляет nil и .some(Int) . Таким образом, разворачивание необязательного значения на самом деле просто извлекает связанное значение из .some регистра.