Как смоделировать более ограниченную версию класса с общим кодом?
Мне нужно создать новый класс. Некоторые из его функций уже находятся в другом классе, и с точки зрения предметной области имеет смысл унаследовать от него. Проблема в том, что есть метод, который должен быть более ограничен по типу параметра, поскольку из-за LSP (принцип подстановки Лискова) вы не можете его перезаписать.
До сих пор есть код, который я могу изменить,
Для лучшего объяснения позвольте мне привести простой пример:
я имею AnimalShelter
и мне нужно реализовать DogShelter
.
class AnimalShelter {
func usefulMethod(...) {}
func take(x: Animal) {}
}
class DogShelter {
var dogMedianCuteness: String = "normal (= very cute)"
func usefulMethod(...) {}
func take(x: Dog) {}
}
Решение 1: подкласс
Если DogShelter
является подклассом AnimalShelter
тогда он получит usefulMethod(...)
бесплатно, но унаследованный метод take(x: Animal)
не может быть переопределен и загрязняет API DogShelter и не должен ничего делать или выдавать ошибку.
class AnimalShelter {
func usefulMethod(...) {}
func take(x: Animal) {}
}
class DogShelter: AnimalShelter {
var dogMedianCuteness: String = "normal (= very cute)"
func take(x: Dog) {}
}
Решение 2. Протокол + расширение протокола
Если AnimalShelter
а также DogShelter
реализовать протокол, он не точен с точки зрения домена, но общий код usefulMethod(...)
может быть реализовано в протоколе extension
.
protocol UsefulThing {
func usefulMethod(...)
}
extension UsefulThing {
func usefulMethod(...) { ... }
}
class AnimalShelter: UsefulThing {
func take(x: Animal) {}
}
class DogShelter: UsefulThing {
var dogMedianCuteness: String = "normal (= very cute)"
func take(x: Dog) {}
}
Решение 3. Обобщение, создание еще одного суперкласса
Проблема в том take(x: T)
метод, более специализированный на DogShelter
. Отказ от AnimalShelter позволит наследовать без проблем, но все, что до сих пор использовало AnimalShelter, необходимо заменить новым подклассом.AnyAnimalShelter: AnimalShelter
который имеет проблемный take(x: Animal) {}
class AnimalShelter {
usefulMethod(...) {}
}
class AnyAnimalShelter: AnimalShelter {
take(x: Animal) {}
}
class DogShelter: AnimalShelter {
var dogMedianCuteness: String = "normal (= very cute)"
func take(x: Dog) {}
}
Решение 4: Состав
Наследование имеет смысл с точки зрения домена, поэтому босс считает, что лучше оставить.
Итак, я получил код от AnimalShelter
и мне разрешено изменить его, даже если это вызовет недоумение, почему я должен изменить код, который отлично работает в течение многих лет. Мне нужна абстрактная причина того, что насчетtake(x: Animal)
метод ошибочен в AnimalShelter
. Не только для того, чтобы иметь веские аргументы против этого, но и чтобы избежать этого на будущих уроках.
Было бы настоящей проблемой, если бы я не мог изменить код, использующий AnimalShelter
или AnimalShelter
сам.
Вопрос
Как я должен моделировать это?
2 ответа
Вы можете использовать протокол со связанным типом:
protocol Shelter {
associatedtype AnimalType
func take(x: AnimalType)
}
extension Shelter {
func usefulMethod(...)
}
class AnimalShelter : Shelter {
typealias AnimalType = Animal
func take(x: Animal) { ... }
}
class DogShelter : Shelter {
typealias AnimalType = Dog
var dogMedianCuteness: String = "normal (= very cute)"
func take(x: Dog) {}
}
Решение предложено Sweeper.
class Animal {}
class Dog: Animal {}
Используйте универсальные шаблоны + расширения классов с ограничениями типа (предложение where), когда у вас нет сохраненных свойств.
class AnimalShelter<T: Animal> {
func usefulMethod(...) {}
func take(x: T) {}
}
В противном случае используйте решение 2: протоколы, расширения протокола и добавьте к протоколу связанный тип с соответствующим ограничением типа в каждом классе.
protocol Shelter {
var animalType: Any.Type { get }
func usefulMethod() -> String
}
extension Shelter {
func usefulMethod() -> String {
return "useful"
}
}
class AnimalShelter: Shelter {
let animalType: Any.Type = Animal.self
func take(x: animalType) {}
}
class DogShelter: Shelter {
let animalType: Any.Type = Dog.self
var dogMedianCuteness: String = "normal (= very cute)"
func take(x: animalType) {}
}