Замінити умовне поліморфізмом належним чином?


10

Розглянемо два класи Dogі Catобидва, що відповідають Animalпротоколу (з точки зору мови програмування Swift. Це буде інтерфейс у Java / C #).

У нас є екран із змішаним списком собак та котів. Є Interactorклас, який керується логікою за кадром.

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

func tryToDeleteModel(model: Animal) {
    if let model = model as? Cat {
        tellSceneToShowConfirmationAlert()
    } else if let model = model as? Dog {
        deleteModel(model: model)
    }
}

Як можна змінити цей код? Воно очевидно пахне

Відповіді:


9

Ви дозволяєте тип протоколу сам визначає поведінку. Ви хочете обробляти всі протоколи однаковими у всій програмі, за винятком самого класу реалізації. Роблячи це таким чином, дотримуючись принципу заміщення Ліски, яка говорить , що ви повинні бути в змозі передати або Catабо Dog(або будь-які інші протоколи , ви могли б у кінцевому підсумку мати під Animalпотенційно), і він працює індиферентно.

Таким чином , ймовірно , ви б додати isCriticalFUNC в які Animalповинні бути реалізовані як Dogі Cat. Все, що реалізує Dog, поверне помилкове, і все, що реалізує Cat, поверне справжнє.

Після цього вам потрібно буде зробити (Мої вибачення, якщо синтаксис невірний. Не користувач Swift):

func tryToDeleteModel(model: Animal) {
    if model.isCritical() {
        tellSceneToShowConfirmationAlert()
    } else {
        deleteModel(model: model)
    }
}

Існує лише невелика проблема з цим, і що це те , що Dogі Catпротоколи, тобто вони самі по собі не визначають то , що isCriticalповертається, в результаті чого це кожному реалізує класу , щоб вирішити для себе. Якщо у вас є багато реалізацій, можливо, варто витратити час на створення розширюваного класу Catабо, Dogякий вже правильно реалізує isCriticalта ефективно очищає всі реалізуючі класи від необхідності перевизначення isCritical.

Якщо це не відповідає на ваше запитання, напишіть у коментарях, і я відповідно розширю відповідь!


Це трохи неясно , в постановці питання, але Dogі Catописується як класи, в той час як Animalце протокол , який реалізується кожен з цих класів. Так що між питанням і вашою відповіддю є дещо невідповідність.
Калеб

Тож ви пропонуєте дозволити моделі вирішувати, подавати спливаюче підтвердження чи ні? Але що робити, якщо у цьому є важка логіка, як-от показ спливаючих вікон, лише якщо на екрані 10 котів? Логіка залежить від Interactorстану зараз
Андрій Гордєєв

Так, вибачте за незрозуміле запитання, я змінив кілька змін. Має бути більш зрозумілим зараз
Андрій Гордєєв

1
Така поведінка не повинна пов'язуватися з моделлю. Це залежить від контексту, а не самої сутності. Я думаю, що Кіт і Собака швидше будуть POJO. Поведінки повинні поводитися в інших місцях і мати можливість змінюватися відповідно до контексту. Делегування поведінки чи методів, на які поведінка покладатиметься у Кішки чи Собаки, призведе до занадто великої відповідальності на таких заняттях.
Грегорі Ельхаймер

@ GrégoryElhaimer Зверніть увагу, що це не визначає поведінку. Це лише твердження, чи це критичний клас чи ні. Поведінки у всій програмі, які повинні знати, чи це критичний клас, то можуть оцінювати та діяти відповідно. Якщо це дійсно властивість, яка розрізняє, як екземпляри в обох Catі Dogобробляються, це може і має бути загальною властивістю в Animal. Робити щось інше - пізніше просити головного болю з обслуговування.
Ніл

4

Розкажіть проти запитуйте

Умовний підхід, який ви показуєте, ми б назвали " запитати ". Тут клієнт запитує "який ти ти?" і відповідно налаштовує їх поведінку та взаємодію з об'єктами.

Це контрастує з альтернативою, яку ми називаємо " скажи ". Використовуючи , розповідає , натискає більшу частину роботи в поліморфних реалізацію, так що споживаючи клієнтський код простіше, без умовних, і загальних , незалежно від можливих реалізацій.

Оскільки ви хочете використовувати підтвердження, що підтверджує, ви можете зробити це чіткою можливістю інтерфейсу. Таким чином, у вас може бути булевий метод, який необов'язково перевіряється з користувачем і повертає булевий підтвердження. У класах, які не хочуть підтверджувати, вони просто перекривають return true;. Інші реалізації можуть динамічно визначати, чи хочуть вони використовувати підтвердження.

Клієнт, що споживає, завжди використовував метод підтвердження незалежно від конкретного підкласу, з яким він працює, що змушує взаємодію говорити замість запитувати .

(Іншим підходом було б підштовхнути підтвердження до видалення, але це здивує споживачів, які очікують, що операція з видаленням буде успішною.)


Тож ви пропонуєте дозволити моделі вирішувати, подавати спливаюче підтвердження чи ні? Але що робити, якщо у цьому є важка логіка, як-от показ спливаючих вікон, лише якщо на екрані 10 котів? Логіка залежить від Interactorстану зараз
Андрій Гордєєв

2
Гаразд, так, це інше питання, що вимагає різної відповіді.
Ерік Ейдт

2

Визначення необхідності підтвердження є обов'язком Catкласу, тому дозвольте йому виконувати цю дію. Я не знаю Котліна, тому я висловлю речі на C #. Сподіваємось, ідеї потім передаються і Котліну.

interface Animal
{
    bool IsOkToDelete();
}

class Cat : Animal
{
    private readonly Func<bool> _confirmation;

    public Cat (Func<bool> confirmation) => _confirmation = confirmation;

    public bool IsOkToDelete() => _confirmation();
}

class Dog : Animal
{
    public bool IsOkToDelete() => true;
}

Потім, створюючи Catекземпляр, ви надаєте його TellSceneToShowConfirmationAlert, який потрібно буде повернути, trueякщо OK видалити:

var model = new Cat(TellSceneToShowConfirmationAlert);

І тоді ваша функція стає:

void TryToDeleteModel(Animal model) 
{
    if (model.IsOKToDelete())
    {
        DeleteModel(model)
    }
}

1
Це не рухає логіку видалення в модель? Чи не було б набагато краще використовувати інший об’єкт для цього? Можливо, структура даних, як словник <Cat> всередині ApplicationService; перевірте, чи існує Кішка, і чи не відключила вона попередження про підтвердження?
keelerjr12

@ keelerjr12, він перекладає відповідальність за визначення, чи потрібно підтвердження для видалення в Catклас. Я б заперечував, що саме там вона і належить. Він не може вирішити, як це підтвердження досягти (що вводиться), і він не видалить себе. Так що ні, це не переміщує логіку видалення в модель.
Девід Арно

2
Я відчуваю, що такий підхід призведе до тонн і тонн коду, пов'язаного з інтерфейсом, приєднаного до самого класу. Якщо клас призначений для використання в декількох шарах інтерфейсу, проблема зростає. Однак якщо це клас типу ViewModel, а не суб'єкт господарювання, то це здається доречним.
Грем

@Graham, так, це безумовно ризик при такому підході: він покладається на те, що його легко ввести TellSceneToShowConfirmationAlertв екземпляр Cat. У ситуаціях, коли це непроста річ (наприклад, у багатошаровій системі, де ця функціональність лежить на глибокому рівні), такий підхід був би не вдалим.
Девід Арно

1
Точне, до чого я потрапляв. Суб'єкт господарювання проти класу ViewModel. У комерційній сфері кішка не повинна знати про код, пов'язаний з інтерфейсом користувача. Моя сімейна кішка нікого не попереджає. Дякую!
keelerjr12

1

Я б порадив перейти до моделі відвідувачів. Я зробив невелику реалізацію на Java. Я не знайомий зі Свіфт, але ви можете легко адаптувати його.

Відвідувач

public interface AnimalVisitor<R>{
    R visitCat();
    R visitDog();
}

Ваша модель

abstract class Animal { // can also be an interface like VisitableAnimal
    abstract <R> R accept(AnimalVisitor<R> visitor);
}

class Cat extends Animal {
    public <R> R accept(AnimalVisitor<R> visitor) {
         return visitor.visitCat();
     }
}

class Dog extends Animal {
    public <R> R accept(AnimalVisitor<R> visitor) {
         return visitor.visitDog();
     }
}

Виклик відвідувача

public void tryToDelete(Animal animal) {
    animal.accept( new AnimalVisitor<Void>() {
        public Void visitCat() {
            tellSceneToShowConfirmation();
            return null;
        }

        public Void visitDog() {
            deleteModel(animal);
            return null;
        }
    });
}

Ви можете мати стільки реалізацій AnimalVisitor, скільки вам потрібно.

Приклад:

public void isColorValid(Color color) {
    animal.accept( new AnimalVisitor<Boolean>() {
        public Boolean visitCat() {
            return Color.BLUE.equals(color);
        }

        public Boolean visitDog() {
            return true;
        }
    });
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.