Как смоделировать более ограниченную версию класса с общим кодом? - PullRequest
0 голосов
/ 19 сентября 2019

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

1 голос
/ 19 сентября 2019

Вы можете использовать протокол со связанным типом:

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) {}
}
0 голосов
/ 19 сентября 2019

Решение, предложенное Sweeper.

class Animal {}
class Dog: Animal {}

Использование обобщений + расширений классов с ограничениями типов (где предложение), когда нет сохраненных свойств.

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) {}
}
...