Как смоделировать более ограниченную версию класса с общим кодом?

Мне нужно создать новый класс. Некоторые из его функций уже находятся в другом классе, и с точки зрения предметной области имеет смысл унаследовать от него. Проблема в том, что есть метод, который должен быть более ограничен по типу параметра, поскольку из-за 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) {}
}
Другие вопросы по тегам