Як уникнути зриву?


12

Моє запитання стосується особливого випадку тварини суперкласу.

  1. Моя Animalможе moveForward()і eat().
  2. Sealрозширюється Animal.
  3. Dogрозширюється Animal.
  4. І є особлива істота, яка також розширюється, Animalназивається Human.
  5. Humanреалізує також метод speak()(не реалізований Animal).

У реалізації абстрактного методу, який приймає, Animalя хотів би використати speak()метод. Це, здається, неможливо, не роблячи зриву. Джеремі Міллер написав у своїй статті, що падіння пахне.

Що було б рішенням, щоб у цій ситуації уникнути занепаду?


6
Виправте свою модель. Використання існуючої таксономічної ієрархії для моделювання ієрархії класів зазвичай є неправильною ідеєю. Ви повинні витягувати свої абстракції з існуючого коду, а не створювати абстракції, а потім намагатися вкласти код до нього.
Ейфорія

2
Якщо ви хочете, щоб тварини говорили, то здатність говорити є частиною того, що робить щось твариною: artima.com/interfacedesign/PreferPoly.html
JeffO

8
рухатися вперед? а краби?
Фабіо Марколіні

Який пункт # це в Ефективній Java, BTW?
goldilocks

1
Деякі люди не можуть розмовляти, тому ви намагаєтеся виняток.
Тулен Кордова

Відповіді:


12

Якщо у вас є метод, який повинен знати, чи певний клас типу Human, щоб зробити щось, ви порушуєте деякі принципи SOLID , зокрема:

  • Принцип відкритого / закритого характеру - якщо в майбутньому вам потрібно буде додати новий тип тварини, який може говорити (наприклад, папуга) або зробити щось конкретне для цього типу, ваш існуючий код повинен буде змінити
  • Принцип поділу інтерфейсу - це здається, що ви занадто багато узагальнюєте. Тварина може охоплювати широкий спектр видів.

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

Щось на зразок цього :

public void MakeItSpeak( Human obj );

і не так:

public void SpeakIfHuman( Animal obj );

Можна також створити абстрактний метод за Animalвикликом, canSpeakі кожна конкретна реалізація повинна визначати, чи може вона «говорити».
Брендон

Але мені більше подобається ваша відповідь. Значна складність пов'язана з намаганням трактувати щось подібне до того, що це не так.
Брендон

@Brandon Я думаю, у такому випадку, якщо він не може "говорити", то киньте виняток. Або просто нічого не робити.
BЈоviћ

Отже, ви перевантажуєтесь так: public void makeAnimalDoDailyThing(Animal animal) {animal.moveForward(); animal.eat()}іpublic void makeAnimalDoDailyThing(Human human) {human.moveForward(); human.eat(); human.speak();}
Барт Вебер

1
Пройдіть увесь шлях - makeItSpeak (ISpeakingAnimal тварина) - тоді ви можете мати ISpeakingAnimal реалізацію Animal. Ви також можете мати makeItSpeak (тварина тварини) {говорити, якщо екземпляр ISpeakingAnimal}, але він має слабкий запах.
ptyx

6

Проблема не в тому, що ви збиваєтесь з каналу - це те, на що ви переходите Human. Натомість створіть інтерфейс:

public interface CanSpeak{
    void speak();
}

public abstract class Animal{
    //....
}

public class Human extends Animal implements CanSpeak{
    public void speak(){
        //....
    }
}

public void mysteriousMethod(Animal animal){
    //....
    if(animal instanceof CanSpeak){
        ((CanSpeak)animal).speak();
    }else{
        //Throw exception or something
    }
    //....
}

Таким чином, умова полягає не в тому, що тварина є Human- умова полягає в тому, що вона може говорити. Це означає, що вони mysteriousMethodможуть працювати з іншими, нелюдськими підкласами, Animalнастільки, наскільки вони реалізовані CanSpeak.


в такому випадку він повинен взяти за аргумент екземпляр CanSpeak замість Animal
Newtopian

1
@Newtopian Якщо припустити, що для цього методу нічого не потрібно Animal, і що всі користувачі цього методу будуть містити об'єкт, який вони хочуть надіслати до нього, через CanSpeakпосилання типу (або навіть Humanпосилання типу). Якби це було так, цей метод можна було б використовувати Humanв першу чергу, і нам не потрібно було б впроваджувати CanSpeak.
Ідан Ар'є

Позбавлення інтерфейсу не пов'язане з тим, що конкретно в підписі методу ви можете позбутися інтерфейсу, якщо і тільки якщо є лише Люди і коли-небудь будуть лише люди, які "CanSpeak".
ньютопійський

1
@Newtopian Причина, яку ми запровадили CanSpeakв першу чергу, полягає не в тому, що у нас є щось, що реалізує це ( Human), а в тому, що у нас є щось, що його використовує (метод). Суть у CanSpeakтому, щоб відокремити цей метод від конкретного класу Human. Якби у нас не було методів, які б по- CanSpeakрізному ставились до речей , не було б сенсу розрізняти речі CanSpeak. Ми не створюємо інтерфейс тільки тому, що у нас є метод ...
Idan Arye

2

Ви можете додати спілкування для тварин. Собака гавкає, Людина розмовляє, Тюлень .. е-е .. Я не знаю, що робить тюлень.

Але це здається, що ваш метод розроблений для того, щоб (Animal is Human) говорити ();

Питання, яке ви можете задати, полягає в тому, яка альтернатива? Важко дати пропозицію, оскільки я не знаю точно, чого ви хочете досягти. Існують теоретичні ситуації, коли кращий підйом / оновлення є найкращим підходом.


15
Печатка виходить на власні очі , але лисиця не визначена.

2

У цьому випадку реалізацією за замовчуванням speak()у AbstractAnimalкласі буде:

void speak() throws CantSpeakException {
  throw new CantSpeakException();
}

У цей момент у вас є реалізація за замовчуванням у класі Анотація - і вона веде себе правильно.

try {
  thingy.speak();
} catch (CantSeakException e) {
  System.out.println("You can't talk to the " + thingy.name());
}

Так, це означає, що у вас є коди, розповсюджені по коду, щоб обробити кожного speak, але альтернативою цьому є if(thingy is Human)обговорення всіх виступів замість цього.

Перевага винятку полягає в тому, що якщо у вас є якийсь інший предмет, який говорить (папуга), вам не потрібно буде повторно виконувати всі ваші тести.


1
Я не думаю, що це є вагомою причиною для використання винятку (якщо це не код python).
Брайан Чен

1
Я не буду відмовлятися від голосування, оскільки це технічно спрацює, але мені дуже не подобається такий дизайн. Навіщо визначати метод на батьківському рівні, який батько не може реалізувати? Якщо ви не знаєте, що таке об'єкт (і якщо б у вас не виникло цієї проблеми), вам потрібно завжди використовувати спробувати / catch, щоб перевірити, чи є виключення, яке можна уникнути. Ви навіть можете використовувати canSpeak()метод, щоб краще впоратися з цим.
Брендон

2
Я працював над проектом, який мав тону дефектів, пов’язаних з чиїмсь рішенням переписати купу робочих методів, щоб кинути виняток, оскільки вони використовують застарілу реалізацію. Він міг виправити реалізацію, але натомість вирішив кидати винятки скрізь. Природно, ми вступили в QA із сотнями помилок. Тож я упереджений, щоб кидати винятки в методах, які не слід називати. Якщо вони не повинні називатися, видаліть їх .
Брендон

2
Альтернатива кидати виняток у цьому випадку може нічого не робити. Зрештою, вони не можуть говорити :)
BЈовић

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

1

Спускання з місця іноді необхідне та доречне. Зокрема, це часто доцільно у тих випадках, коли в когось є об'єкти, які можуть мати, а можуть і не мати певну здатність, і хочеться використовувати цю здатність, коли вона існує під час обробки об'єктів без такої здатності якось за замовчуванням. Приклади просто, припустимо String, запитується, чи дорівнює він якомусь іншому довільному об'єкту. Для того, Stringщоб один дорівнював іншому String, він повинен вивчити масив довжини та резервного символу другого рядка. Якщо а Stringзапитують, чи дорівнює Dogвін, то він не може отримати доступ до довжини Dog, але це не повинно бути; натомість, якщо об'єкт, з яким Stringмає порівнювати себе, не єString, порівняння має використовувати поведінку за замовчуванням (повідомляючи, що інший об'єкт не є рівним).

Час, коли зрив каналу слід вважати найбільш сумнівним, це коли "відомий" об'єкт, який викидається, належного типу. Загалом, якщо об'єкт, як відомо, є a Cat, для його використання слід використовувати змінну типу Cat, а не змінну типу Animal. Однак бувають випадки, коли це не завжди працює. Наприклад, Zooколекція може містити пари об'єктів у слотах парного / непарного масиву, сподіваючись, що об'єкти кожної пари зможуть діяти один на одного, навіть якщо вони не зможуть діяти на об'єкти інших пар. У такому випадку об’єкти в кожній парі все одно повинні прийняти неспецифічний тип параметра, такий, що вони могли б синтаксично передавати об'єкти з будь-якої іншої пари. Таким чином, навіть якщо Cat'splayWith(Animal other)метод буде працювати лише тоді, коли otherбув a Cat, Zooпотрібно було б мати можливість передавати йому елемент an Animal[], тому його тип параметра повинен бути, Animalа не Cat.

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


У випадку рядка вам слід скористатися методом Object.equalToString(String string). Тоді у вас є boolean String.equal(Object object) { return object.equalStoString(this); }Отже, не потрібні збитки: ви можете використовувати динамічну диспетчерську роботу.
Джорджіо

@Giorgio: Динамічна диспетчеризація має свою користь, але це, як правило, навіть гірше, ніж опускання.
supercat

спосіб динамічної диспетчеризації взагалі навіть гірший, ніж пригнічення? я хоч, це навпаки
Брайан Чен,

@BryanChen: Це залежить від вашої термінології. Я не думаю, що Objectнемає жодного equalStoStringвіртуального методу, і я визнаю, я не знаю, як цитований приклад навіть працював би на Java, але в C #, динамічна відправка (на відміну від віртуальної диспетчеризації) означала б, що компілятор по суті має зробити пошук на основі рефлексії вперше, коли метод використовується в класі, який відрізняється від віртуальної диспетчеризації (яка просто здійснює виклик через слот в таблиці віртуального методу, який повинен містити дійсну адресу методу).
supercat

З точки зору моделювання, я віддаю перевагу динамічній диспетчеризації взагалі. Це також об'єктно-орієнтований спосіб вибору процедури на основі типу вхідних параметрів.
Джорджіо

1

У реалізації абстрактного методу, який приймає тварин, я хотів би використовувати метод speak ().

У вас є кілька варіантів:

  • Використовуйте відображення для дзвінка, speakякщо він існує. Перевага: відсутність залежності від Human. Недолік: зараз існує прихована залежність від назви "говорити".

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

  • Вниз до Human. Це має той недолік, що вам доведеться змінювати код, коли ви хочете говорити іншим підкласом. В ідеалі ви хочете розширити програми, додаючи код, не повторюючись і не змінюючи старий код.

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