Подтвердить что ты не робот

Как полиморфизм делает мой код более гибким?

Я читаю Head First Object Oriented Design, чтобы лучше понять концепции ООП.

Полиморфизм объясняется следующим образом:

Airplane plane = new Airplane();
Airplane plane = new Jet();
Airplane plane = new Rocket();

Вы можете написать код, который работает над суперклассом, например, с самолетом, но будет работать с любым из подклассов.: - * Хммм.... Я получил этот. *.

Далее объясняется:

- > Итак, как полиморфизм делает код гибким?

Ну, если вам нужна новая функциональность, вы можете написать новый подкласс САМОЛЕТ. Но так как ваш код использует суперкласс, ваш новый класс будет работать без каких-либо изменений в остальной части вашего кода.

Теперь я не понимаю. Мне нужно создать подвал самолета. Например: создаю класс Randomflyer. Чтобы использовать его, мне нужно будет создать его объект. Поэтому я буду использовать:

Airplane plane = new Randomflyer();

Я не понимаю. Даже я бы сразу создал объект подклассов. Тем не менее, я не вижу необходимости менять свой код в любом месте, когда буду добавлять новый подкласс. Как использование суперкласса не позволяет мне внести дополнительные изменения в остальную часть моего кода?

4b9b3361

Ответ 1

Скажите, что у вас есть следующее (упрощенное):

Airplane plane = new MyAirplane();

Затем вы делаете с ним всевозможные вещи:

List<Airplane> formation = ...
// superclass is important especially if working with collections
formation.add(plane);
// ...
plane.flyStraight();
plane.crashTest();
// ... insert some other thousand lines of code that use plane

Вещь есть. Когда вы вдруг решите изменить свой самолет на

Airplane plane = new PterdodactylSuperJet();

весь ваш другой код, который я написал выше, просто будет работать (по-разному, конечно), потому что другой код зависит от интерфейса (read: public methods), предоставляемого общим классом Airplane, а не от фактической реализации, которую вы предоставляете в начале. Таким образом, вы можете передавать разные реализации без изменения вашего другого кода.

Если вы не использовали суперкласс класса Airplane и просто пишели MyAirplane и PterdodactylSuperJet в том смысле, что вы заменяете

MyAriplane plane = new MyAirplane();

с

PterdodactylSuperJet plane = new PterdodactylSuperJet();

тогда у вас есть точка: остальная часть вашего кода может по-прежнему работать. Но это просто происходит, потому что вы написали один и тот же интерфейс (публичные методы) в обоих классах, специально. Если вы (или какой-либо другой разработчик) измените интерфейс в одном классе, перемещение между классами самолетов сделает ваш код непригодным.

Edit

Я имею в виду, что вы специально реализуете методы с теми же сигнатурами как в MyAirplane, так и PterodactylSuperJet, чтобы ваш код работал правильно с обоими. Если вы или кто-то другой измените интерфейс одного класса, ваша гибкость нарушена.

Пример. Скажем, у вас нет суперкласса Airplane, а другой ничего не подозревающий dev модифицирует метод

public void flyStraight()

в MyAirplane до

public void flyStraight (int speed)

и предположим, что ваша переменная plane имеет тип MyAirplane. Тогда большому коду потребуются некоторые изменения; Предположим, что это необходимо. Дело в том, что если вы вернетесь к PterodactylSuperJet (например, чтобы проверить его, сравнить его, множество причин), ваш код не будет работать. Почему Боже почему. Поскольку вам нужно предоставить PterodactylSuperJet с помощью метода flyStraight(int speed), вы не писали. Вы можете это сделать, вы можете исправить, что хорошо.

Это простой сценарий. Но что, если

  • Эта проблема укусит вас в задницу через год после невинной модификации? Вы даже можете забыть, почему вы это сделали в первую очередь.
  • Не один, но тонна modificatios произошла, что вы не можете отслеживать? Даже если вы можете отслеживать, вам нужно получить новый класс до скорости. Почти никогда не бывает легко и не всегда приятно.
  • Вместо двух плоскостных классов у вас есть сто?
  • Любая линейная (или нет) комбинация из вышеперечисленного?

Если вы написали суперкласс класса Airplane и сделали каждый подкласс переопределенными его соответствующими методами, то, изменив flyStraight() на flyStraight(int) в Airplane, вы были бы вынуждены соответствующим образом адаптировать все подклассы, сохраняя при этом последовательность. Поэтому гибкость не будет изменена.

Редактирование конца

Вот почему суперкласс остается своего рода "папой" в том смысле, что если кто-то изменяет свой интерфейс, все подклассы будут следовать, поэтому ваш код будет более гибким.

Ответ 2

Очень простой пример использования, чтобы продемонстрировать преимущества полиморфизма, - это пакетная обработка списка объектов, не беспокоясь о его типе (т.е. делегируя эту ответственность каждому конкретному типу). Это помогает последовательно выполнять абстрактные операции в коллекции объектов.

Предположим, вы хотите реализовать симулированную программу полета, в которой вы хотели бы летать на каждом и том же самолете, который присутствует в вашем списке. Вы просто вызываете

for (AirPlane p : airPlanes) {
    p.fly();
}

Каждый самолет знает, как летать сам, и вам не нужно беспокоиться о типе самолетов во время этого вызова. Эта однородность в поведении объектов - это то, что дает вам полиморфизм.

Ответ 3

Предположим, что у вас есть методы в классе Controller Planes, например

parkPlane(Airplane plane)

и

servicePlane(Airplane plane)

реализованный в вашей программе. Он не будет BREAK вашего кода. Я имею в виду, что он не должен меняться, пока он принимает аргументы как AirPlane.

Потому что он примет любой самолет, несмотря на фактический тип, flyer, highflyr, fighter и т.д.

Кроме того, в коллекции:

List<Airplane> plane;//Возьмем все ваши самолеты.

Следующий пример очистит ваше понимание.


interface Airplane{
    parkPlane();
    servicePlane();
}

Теперь у вас есть истребитель, который его реализует, поэтому

public class Fighter implements Airplane {

    public void  parkPlane(){
        // Specific implementations for fighter plane to park
    }
    public void  servicePlane(){
        // Specific implementatoins for fighter plane to service.
    }
}

То же самое для HighFlyer и других классов:

public class HighFlyer implements Airplane {

    public void  parkPlane(){
        // Specific implementations for HighFlyer plane to park
    }

    public void  servicePlane(){
        // specific implementatoins for HighFlyer plane to service.
    }
}

Теперь подумайте, что классы вашего контроллера с помощью AirPlane несколько раз,

Предположим, что ваш класс Controller - AirPort, как показано ниже,

public Class AirPort{ 

AirPlane plane;

public AirPlane getAirPlane() {
    return airPlane;
}

public void setAirPlane(AirPlane airPlane) {
    this.airPlane = airPlane;
 }

}

здесь магия приходит как Полиморфизм делает ваш код более гибким, потому что

вы можете сделать свои новые экземпляры типа AirPlane столько, сколько хотите, и вы не меняете

код класса AirPort.

вы можете установить экземпляр AirPlane, как вам нравится (Thats называется также зависимость Intection).

JumboJetPlane // implementing AirPlane interface.
AirBus        // implementing AirPlane interface.

Теперь подумайте: если вы создаете новый тип самолета или удаляете любой тип самолета, это имеет значение для вашего AirPort?

Нет. Поскольку мы можем сказать, что класс AirPort полиморфно ссылается на AirPlane.

Ответ 4

Другие люди более подробно рассмотрели ваши вопросы о полиморфизме в целом, но я хочу ответить на один конкретный фрагмент:

Я не получаю его, даже я бы создал объект подклассов непосредственно.

Это на самом деле большое дело, и люди прилагают много усилий, чтобы избежать этого. Если вы открываете что-то вроде Gang of Four, есть куча шаблонов, посвященных устранению этой проблемы.

Основной подход называется шаблоном Factory. Это выглядит примерно так:

AirplaneFactory factory = new AirplaneFactory();

Airplane planeOne = factory.buildAirplane();
Airplane planeTwo = factory.buildJet();
Airplane planeThree = factory.buildRocket();

Это дает вам больше гибкости, абстрагируясь от экземпляра объекта. Вы можете себе представить такую ​​ситуацию: ваша компания начинает сначала создавать Jet s, поэтому ваш Factory имеет метод buildDefault(), который выглядит так:

public Airplane buildDefault() {
    return new Jet();
}

Однажды ваш босс подходит к вам и говорит вам, что бизнес изменился. То, что действительно хотят люди в эти дни, Rocket - Jet ушло в прошлое.

Без AirplaneFactory вам придется пройти через свой код и заменить, возможно, десятки вызовов new Jet() на new Rocket(). С шаблоном Factory вы можете просто внести изменения, например:

public Airplane buildDefault() {
    return new Rocket();
}

и поэтому масштаб изменений резко сокращается. И поскольку вы кодировали интерфейс Airplane, а не конкретный тип Jet или Rocket, это единственное изменение, которое вам нужно сделать.

Ответ 5

Насколько я понимаю, преимущество заключается в том, что, например, в боевой игре самолета вы должны обновлять все позиции самолетов в каждом цикле, но у вас есть несколько разных самолетов. Скажем, у вас есть:

  • МИГ-21
  • Waco 10
  • Mitsubishi Zero
  • Eclipse 500
  • Mirage

Вы не хотите обновлять свои движения и позиции в отдельности следующим образом:

Mig21 mig = new Mig21();
mig.move();
Waco waco = new Waco();
waco.move();
Mitsubishi mit = new Mitsubishi();
mit.move();
...

Вы хотите иметь суперкласс, который может взять любой из этих подклассов (Airplane) и обновить все в цикле:

airplaneList.append(new Mig21());
airplaneList.append(new Waco());
airplaneList.append(new Mitsubishi());
...
for(Airplane airplane : airplanesList)
    airplane.move()

Это делает ваш код намного проще.

Ответ 6

Вы совершенно правы, что подклассы полезны только тем, кто их создает. Это было хорошо сказано Ричем Хики:

... любой новый класс сам по себе является островом; непригодным для использования любым существующим кодом, написанным кем угодно, где угодно. Поэтому подумайте о том, чтобы бросить ребенка с водой в ванной.

По-прежнему можно использовать объект, который был создан где-то в другом месте. В качестве тривиального примера этого любой метод, который принимает аргумент типа "Object", вероятно, получит экземпляр подкласса.

Однако есть еще одна проблема, которая гораздо более тонкая. В общем, подкласс (например, Jet) не будет работать вместо родительского класса (например, Airplane). Предполагая, что подклассы взаимозаменяемы с родительскими классами, является причиной огромного количества ошибок.

Это свойство взаимозаменяемости известно как Принцип замещения Лискова и изначально было сформулировано как:

Пусть q (x) - свойство, доказуемое об объектах x типа T. Тогда q (y) должно быть доказуемо для объектов y типа S, где S - подтип T.

В контексте вашего примера T - класс самолета, S - класс Jet, x - экземпляры самолета, а y - экземпляры Jet.

"Свойства" q - это результаты методов экземпляров, содержание их свойств, результаты их передачи другим операторам или методам и т.д. Мы можем думать о "доказуемом" как о значении "наблюдаемый"; то есть. это не имеет значения, если два объекта реализованы по-разному, если нет никакой разницы в их результатах. Точно так же не имеет значения, будут ли два объекта вести себя по-разному после бесконечного цикла, так как этот код никогда не будет достигнут.

Определение Jet как подкласса самолета - это тривиальный вопрос синтаксиса: декларация Jet должна содержать токены extends Airplane, и в декларации самолета не должно быть токена final. Для компилятора тривиально проверить, соответствуют ли объекты правилам подклассификации. Однако это не говорит нам, является ли Jet подтипом самолета; то есть. может ли Jet использоваться вместо самолета. Java позволит это, но это не значит, что он будет работать.

Один из способов сделать Jet подтипом самолета - иметь Jet - пустой класс; все его поведение исходит от самолета. Однако даже это тривиальное решение проблематично: самолет и тривиальная Джета будут вести себя по-разному, когда передаются оператору instanceof. Поэтому нам нужно проверить весь код, который использует Airplane, чтобы убедиться, что нет вызовов instanceof. Конечно, это полностью противоречит идеям инкапсуляции и модульности; мы никак не можем проверить код, который еще не существует!

Обычно мы хотим подклассов, чтобы делать что-то по-другому с суперклассом. В этом случае мы должны убедиться, что ни одно из этих различий не наблюдается для любого кода, использующего Airplane. Это еще сложнее, чем синтаксическая проверка для instanceof; нам нужно знать, что делает весь этот код.

Это невозможно из-за теоремы Риса, поэтому нет возможности автоматически проверять автоматическое подтипирование и, следовательно, количество ошибок, которые он вызывает.

По этим причинам многие видят полиморфизм подкласса как анти-шаблон. Существуют и другие формы полиморфизма, которые не страдают от этих проблем, хотя, например, "параметрический полиморфизм" (называемый "generics" в Java).

Принцип замены Лискова

Сравнение суб-классификации и подтипа

Параметрический полиморфизм

Аргументы против подклассификации

Теорема Риса

Ответ 7

Один хороший пример того, когда полиморфизм полезен:

Скажем, у вас есть абстрактный класс Animal, который определяет методы и такие общие для всех животных, такие как makeNoise()

Затем вы можете расширить его с помощью подклассов, таких как Dog, Cat, Tiger.

Каждый из этих животных переопределяет методы абстрактного класса, такие как makeNoise(), чтобы сделать эти поведения специфичными для их класса. Это хорошо, потому что, как ни странно, у каждого животного возникает другой шум.

Вот один пример, где полиморфизм - отличная вещь: коллекции.

Допустим, у меня есть ArrayList<Animal> animals, и он полон нескольких разных животных.

Полиморфизм делает этот код возможным:

for(Animal a: animals) 
{
    a.makeNoise();
}

Поскольку мы знаем, что каждый подкласс имеет метод makeNoise(), мы можем доверить, что это приведет к тому, что каждый объект животного вызовет свою конкретную версию makeNoise() (например, собака лает, кошачье мяуканье, коровы-бабочки, все без вас, даже когда вам приходится беспокоиться о том, какое животное делает.)

Другое преимущество очевидно при работе с командой над проектом. Скажем, еще один разработчик добавил несколько новых животных, даже не рассказывая вам, и у вас есть коллекция животных, у которых теперь есть некоторые из этих новых типов животных (которых вы даже не знаете!). Вы все равно можете вызвать метод makeNoise() (или любой другой метод в суперклассе животных) и надеяться, что каждый тип животных будет знать, что делать.

Приятная вещь в этом суперклассе для животных заключается в том, что вы можете расширить суперкласс и создать столько новых типов животных, сколько хотите, не изменяя никого в суперклассе или не нарушая никакого кода.

Помните золотое правило полиморфизма. Вы можете использовать подкласс везде, где ожидается объект типа суперкласса.

Например:

Animal animal = new Dog;

Требуется некоторое время, чтобы научиться мыслить полиморфно, но как только вы узнаете, что ваш код значительно улучшится.

Ответ 8

Полиморфизм проистекает из наследования. Вся идея состоит в том, что у вас есть общий базовый класс и более конкретные производные классы. Затем вы можете написать код, который работает с базовым классом... и полиморфизм заставляет ваш код работать не только с базовым классом, но и со всеми производными классами.

Если вы решили, что ваш суперкласс имеет метод, скажите getPlaneEngineType(), и вы создадите новый дочерний класс "Jet, который наследуется от Plane" . Plane jet = new Jet() будет/все еще доступен доступ к суперклассу getPlaneEngineType. Хотя вы все равно можете написать свой собственный getJetEngineType(), чтобы в основном переопределить метод суперкласса при супервызове, это означает, что вы можете написать код, который будет работать с ЛЮБОЙ "плоскостью", а не только с помощью Plane или Jet или BigFlyer.

Ответ 9

Полиморфизм является мощным, учитывая, что при необходимости изменить поведение вы можете изменить его, переопределив метод.

Ваш суперкласс наследует свои свойства и поведение в ваших подклассах, расширенных им. Таким образом, безопасно скрывать объект, тип которого также относится к его суперклассу. Эти общие методы для ваших подклассов делают их полезными для реализации API. При этом полиморфизм дает вам возможность расширить функциональность вашего кода.

Ответ 10

Я не думаю, что это хороший пример, поскольку он, похоже, путает онтологию и полиморфизм.

Вы должны спросить себя, какой аспект поведения "Джета" отличается от "Аэроплана", который оправдывал бы усложнение программного обеспечения, чтобы моделировать его с другим подтипом? Предварительный просмотр книги отключается после одной страницы в примере, но дизайн дизайна не кажется оправданным. Всегда задавайте себе вопрос, есть ли разница в поведении, а не просто добавление классов для категоризации вещей - как правило, это лучше сделать со значением свойства или составом стратегий, чем с подклассами.

Пример (упрощенный из крупного проекта, который я возглавляю в начале ноль), будет заключаться в том, что самолёт является окончательным, но имеет различные свойства абстрактных типов, одним из которых является двигатель. Существуют различные способы расчета тяги и использования топлива в двигателе - для быстрой двумерной кубической интерполяции таблицы значений тяги и расхода топлива против Маха и дроссельной заслонки (иногда и давления и влажности), для метода Rockets table, но не требуют компенсации за остановку воздуха на входе в двигатель; для реквизита может использоваться более простое параметризованное уравнение "бутстрап". Таким образом, у вас будет три класса AbstractAeroEngine - JetEngine, RocketEngine и BootstrapEngine, которые будут иметь реализации методов, которые возвращают тягу и расход топлива, учитывая настройку дроссельной заслонки и текущее число Маха. (вы почти никогда не должны подтипировать не абстрактный тип)

Обратите внимание, что различия между типами AbstractAeroEngine, хотя и связаны с различными двигателями реального мира, полностью отличаются от того, как программное обеспечение вычисляет тягу двигателя и использование топлива - вы не строите онтологию классов, которые описывают представление реального мира, но специализируя операции, выполняемые в программном обеспечении, в соответствии с конкретными вариантами использования.

Как использование суперкласса позволяет мне не вносить дополнительные изменения в остальную часть моего кода?

Поскольку все вычисления вашего двигателя являются полиморфными, это означает, что при создании самолета вы можете закрепить все, что подходит для расчета тяги двигателя. Если вы обнаружите, что вам нужно использовать другой метод расчета тяги (как это было раньше, несколько раз), вы можете добавить еще один подтип AeroEngine - пока реализуемая им поставка обеспечивает доверие и расход топлива, тогда остальные системы не заботятся о внутренних различиях - класс самолета все равно спросит свой двигатель о тяге. Самолет только заботится о том, что у него есть двигатель, который он может использовать так же, как и любой другой двигатель, только код создания должен знать тип двигателя, который нужно закрепить на нем, а реализация ScramJetEngine касается только сверхзвуковых реактивных вычислений - части самолета, которые вычисляют подъем и перетаскивание, и стратегия его полета не должна изменяться.

Ответ 11

Полиморфизм получает свойства и все поведения и интерфейсы суперкласса. Так же ли поведение плоскости действительно такое же, как у струи?