Родительские объекты
При работе с объектами в GameMaker IDE можно установить иерархию " родитель/ребенок ". Для этого нужно нажать кнопку "Parent" в редакторе объектов, а затем выбрать другой объект из браузера ассетов. который будет "родителем" редактируемого объекта:
Итак, каждый объект в вашем игровом проекте может иметь родительский объект, но что это значит? Когда у объекта есть родительский объект, он может обмениваться кодом, действиями и событиями с этим родителем. Такой обмен называется "наследованием", а объект, у которого есть родитель, называется "дочерним". Дочерний объект не только может делиться кодом со своим родителем, но вы можете выполнять проверки и запускать код на родительских объектах, и он будет автоматически включать и дочерние объекты, что экономит много времени и энергии.
Если это звучит сложно, то другой способ взглянуть на родительский объект — это способ "сгруппировать" объекты под одним зонтиком, чтобы они имели общие черты, не теряя при этом своей уникальной индивидуальности. Возможно, это все еще не проясняет ситуацию, поэтому давайте приведем несколько примеров.
Допустим, у вас есть объект "игрок" и четыре различных объекта "враг". Теперь вы хотите, чтобы игрок умер, если он коснется любого из этих четырех объектов, и это обычно влечет за собой четыре различных события столкновения с четырьмя различными наборами действий или кода, по одному для каждого из вражеских объектов. Но если мы создадим родительский объект для всех врагов, то мы сможем создать одно событие столкновения только с родительским объектом, и оно сработает независимо от того, какой из четырех "дочерних" вражеских объектов коснется игрока. коснется игрока. Удобная штука! В реальном объекте GameMaker вы увидите примерно следующее:
Слева у нас есть четыре отдельных события столкновения, а справа — одно событие столкновения, поскольку мы создали "родительский" объект и назначили ему все вражеские объекты. Обратите внимание, что родительский объект не должен иметь никаких событий или кода в нем.
Другим примером родительского поведения может быть игра, в которой вы хотите создать 10 различных по внешнему виду объектов и заставить их всех вести себя одинаково. Для этого вы создадите один родительский объект и в нем все ваши поведенческие действия или код в нужных событиях, а затем вы создадите десять объектов без действий или кода, но с разными sprites , и назначите им родительский объект. Теперь, когда вы поместите эти экземпляры в комнату, все они будут вести себя одинаково, но выглядеть по-разному, поскольку они "наследуют" события родительского объекта.
Наконец, вы можете использовать воспитание, чтобы "смешивать и сопоставлять" события и поведение. Мы проиллюстрируем это на последнем примере. Допустим, вам нужны два монстра: один двигается вверх и вниз, а другой — влево и вправо. чтобы они имели одинаковое здоровье, стреляли в игрока и ранили игрока при столкновении с ними. В этом случае вы видите, что почти все события должны иметь одинаковые действия, за исключением одного или двух, которые управляют движением. Итак, опять же, мы можем сделать один объект родителем другого, но в этом случае мы также определяем определенные события для дочернего объекта. Эти события "переопределяют" родительские события, то есть всякий раз, когда событие для дочернего объекта содержит действия, они выполняются вместо действий, содержащихся в событии родителя. Если вы также хотите выполнить родительское событие, вы можете вызвать так называемое "унаследованное" событие с помощью функции event_inherited() , или визуальное действие GML Call Parent Event.
Слева вверху находится родительский объект с 5 событиями в нем, а справа вы видите "дочерний" объект. Дочерний объект также имеет 5 событий, но два из них переопределяют унаследованные от родителя события (события Step и Draw ), а три других выделены серым цветом, так как они являются событиями, унаследованными от родителя. Унаследованные события также будут иметь значок "переопределение родителя" рядом с ними в редакторе событий:
Когда вы нажимаете на унаследованное событие, откроется редактор кода, в котором будет показан унаследованный родительский код, но вы не сможете редактировать этот код, так как его можно редактировать только в самом родительском объекте. Вы можете нажать правую кнопку мыши на любом из родительских событий, чтобы открыть следующее меню опций:
Здесь вы можете выбрать два варианта Открыть родительское событие, чтобы просмотреть код, или же вы можете выбрать Наследовать событие событие или переопределить событие. Если вы выберете Наследовать , то редактор кода откроется с функцией event_inherited() уже (или действием Call Parent Event, если вы используете GML Visual). Любой последующий код, который вы поместите в это событие, теперь будет выполняться так же, как и код родительского объекта. имеет. Если вы выберете Переопределить событие, то окно кода также откроется, только теперь функция event_inherited() не будет вызываться, поэтому все, что вы добавите сюда, будет выполняться вместо кода в родительском объекте.
ПРИМЕЧАНИЕ: Из редактора кода вы можете быстро перейти к родительскому объекту, нажав правую кнопку мыши и выбрав Go To Object из всплывающего меню, или (если событие было переопределено) вы можете выбрать Open Inherited Event, чтобы перейти непосредственно в редактор кода с кодом родительского события в нем.
Когда в коде вы нацеливаетесь на родительский объект, код будет применяться и к "детям" родительского объекта. Это происходит, когда в действии вы указываете, что действие должно быть применено к экземплярам определенного объекта, а в коде это происходит, когда вы используете with() оператор. Это будет работать и при вызове функций кода, таких как instance_position() , instance_number() , и т.д., где — если вы предоставите родительский объект — все экземпляры родительского и дочернего экземпляров будут включены в проверку. И наконец, родительские функции работают, когда вы ссылаетесь на переменные в других объектах, как в приведенном выше примере с монстром, если я установлю скорость врага 1 равной 10, то скорость врага 2 также будет равна десяти, поскольку он является дочерним объектом врага 1.
Обычно считается хорошей практикой в большинстве случаев создавать один базовый родительский объект, чтобы этот базовый объект содержал все поведение по умолчанию, но никогда не использовать его экземпляр в игре. Лучше использовать все дочерние объекты и использовать родительский объект только в ситуациях, подобных тем, что я описал выше. ситуациях, подобных тем, которые я описал выше, для столкновений, для ссылок на переменные и т.д. Вы также должны понимать, что у родителей тоже могут быть родители! Очевидно, что вы не можете создать цикл "родитель 1 является ребенком родителя 2 является ребенком родителя 1", но вы можете создать так называемую "иерархию объектов", где "родитель 3 является ребенком родителя 2 является ребенком родителя 1". Это чрезвычайно полезно для структурирования вашей игры, и вам настоятельно рекомендуется научиться использовать этот механизм.
Наследование
Ключевое слово parent
Наследование — это некий механизм, посредством которого один или несколько классов можно получить из некоторого базового класса.
Класс Product (базовый класс) — должен содержать общие свойста и общие методы (общую логику).
Поэтому удаляем из класса все частные свойства и методы. — Оставляем свойства общие для всех товаров.
— Конструктр будет задавать свойства, общие для всех товаров.
— Получим через гетеры getName() и getPrice() общие свойства товаров.
— Метод getProduct() — будет возвращать инфрмацию о товаре, используя общие свойства.
Файл Product.php
<?php
class Product
<
// общие свойства товаров
public $name ; // наименование товара
public $price ; // цена товара
// конструктр будет задавать свойства, общие для всех
public function __construct ( $name , $price )
<
$this -> name = $name ;
$this -> price = $price ;
>
// метод getProduct() — будет возвращать инфрмацию о товаре, используя общие свойства
public function getProduct ( $type = ‘notebook’ )
<
$out = «<hr><b>О товаре:<b><br>
Наименование: < $this ->name > <br>
Цена: < $this ->price > <br> » ;
>
// общие гетеры:
public function getName ()
<
return $this -> name ;
>
public function getPrice ()
<
return $this -> price ;
>
>
?>
В индексом файле в экземпляры класса передаем по два параметра: название и цена. Получаем общую для всех типов товаров информацию с помощью метода getProduct() .
Файл index.php
<?php
error_reporting (- 1 );
require_once ‘classes/Product.php’ ;
function debug ( $data )
<
echo ‘<pre>’ . print_r ( $data , 1 ) . ‘</pre>’ ;
>
// В экземпляре класса Product (книжка) — передаем только два параметра
$book = new Product (‘ Три мушкетера’ , 20 );
// экземпляр класса Product (ноутбук)
$notebook = new Product ( ‘Dell’ , 1000 );
// распечатаем данные объекты
debug ( $book );
echo ‘<br>’ ;
debug ( $notebook );
// получаем информацию о продукте с помощью метода getProduct()
echo $book -> getProduct ();
echo $notebook -> getProduct ();
?>
Выведет:
Product Object
(
[name] => Три мушкетера
[price] => 20
)
В результате у нас выводится информация, общая для всех типов товаров: названия товаров и их цена. Специфичная информация каждого конкретного товара не выводится.
Поскольку у нас два типа товаров (ноутбук и книги), то есть смысл создать два дочерних класса: NotebookProduct и BookProduct.
Создаем дочерний класс NotebookProduct и добавим в него специфичные свойства, в нашем случае — $cpu .
Для того, что-бы один класс мог наследовать другой класс, используется ключевое слово extends ( extends — расширять), и название класса, который расширяем — Product.
В этом случае все методы и свойства родительского класса Product — наследуются и они нам доступны.
Чтобы получить специфичные свойства и методы — опишем их в дочернем классе NotebookProduct. Объявим свойство $cpu — специфическое свойство для Notebook. В классе Product не было этого свойства, а в классе NotebookProduct мы его добавили — тем самым расширили класс Product.
Также добавим метод getCpu() .
При создании объекта нам необходимо заполнить свойства значением. Для этого напишем конструктор ( __construct ).
Когда мы определяем метод с тем же названием ( __construct ), что и в родительском классе, то мы его перезаписываем ( переопределяем ), то есть, теперь будет работать этот метод, а не метод в родительском классе. Если мы хотим получить ту же самую функциональность, мы должны объявить те же самые параметры из родительского класса плюс добавить наши параметры: $name,$price плюс $cpu .
Ключевое слово parent
Для того, чтобы не дублировать код, используем ключевое слово parent и оператор разрешения области видимости «::«.
С помощью ключевого слова parent ( parent — это слово указывает на родительский класс) и оператора разрешения области видимости «::» мы обращаемся к родительскому классу и вызваем нужный нам метод ( __construct ), который требует два параметра $name и $price :
parent::__construct($name, $price) .
Затем дописываем нужную нам функциональность (добавляем наш параметр):
Теперь наш конструктор (перезаписанный) вызывает сначала родительский конструктор, который отработает, и затем будет отработывать добавленный нами код.
Аналогично переопределяем родительский метод getProduct() :
Вызываем родительскую функциональность:
— и сохраняем ее в переменную $out ,
и далее дописываем переменную $out нашей новой функциональностью (информацией о процессоре)
— и возвращаем переменную $out .
Файл NotebookProduct.php
// создаем дочерний класс NotebookProduct
// добавляем специфичные свойства
// для того, что-бы один класс мог наследовать другой класс,
// ипользуется ключевое слово (extends — расширять),
// и класс, который расширяем — Product
class NotebookProduct extends Product
<
// специфическое свойство для Notebook (мы расширили класс Product)
public $cpu ;
// когда мы определяем метод с тем же названием, что и в родительском классе,
// то мы его перезаписываем (переопределяем),
// то есть, теперь будет работать этод метод, а не метод в родительском классе
// мы должны объявить те же самые параметры из родительского класса
// плюс добавить наши параметры
public function __construct ( $name , $price , $cpu )
<
//$this->name=$name; // параметры из родительского класса
//$this->price=$price;
// с помощью ключевого слова (parent — это слово указывает на
// родительский класс) и оператора (:: — разрешение области видимости)
// мы обратились к родительскому классу и вызвали нужный
// нам метод (__construct), который требует два параметра $name и $price
parent :: __construct ( $name , $price );
// дописываем нужную нам функциональность (добавляем наш параметр)
$this -> cpu = $cpu ;
>
// аналогично переопределяем родительский метод getProduct()
public function getProduct ()
<
// строки из метода (getProduct() класса Product) переносим
// сюда и присваиваем переменной $out
$out = parent :: getProduct ();
// дописываем переменную $out нашей новой функциональностью
$out .= «Процессор: < $this ->cpu > <br>» ;
// возвращаем переменную $out
return $out ;
>
// метод getCpu()
public function getCpu ()
<
return $this -> cpu ;
>
>
?>
Тоже самое сделаем и для дочернего класса BookProduct:
Файл BookProduct.php
class BookProduct extends Product
<
public $numPages ;
public function __construct ( $name , $price , $numPages )
<
parent :: __construct ( $name , $price );
$this -> numPages = $numPages ;
>
public function getProduct ()
<
$out = parent :: getProduct ();
$out .= «Кол-во страниц:: < $this ->cpu > <br>» ;
return $out ;
>
public function getNumPeges ()
<
return $this -> numPages ;
>
>
?>
В итоге мы получили два класса NotebookProduct и BookProduct, которые расширяют родительский класс Product.
В индексном файле подключаем наши классы и создаем объекты уже этих классов и передаем им параметры из соответствующих конструкторов.
Файл index.php
<?php
error_reporting (- 1 );
require_once ‘classes/Product.php’ ;
// подключаем класс NotebookProduct
require_once ‘classes/NotebookProduct.php’ ;
// подключаем класс BookProduct
require_once ‘classes/BookProduct.php’ ;
function debug ( $data )
<
echo ‘<pre>’ . print_r ( $data , 1 ) . ‘</pre>’ ;
>
// создаем объект класса BookProduct и передаем параметры из конструктора BookProduct
$book = new BookProduct (‘ Три мушкетера’ , 20 , 1000 );
// создаем объект класса NotebookProduct и передаем параметры из конструктора NotebookProduct
$notebook = new NotebookProduct ( ‘Dell’ , 1000 , ‘Intel’ );
// распечатаем данные объекты
debug ( $book );
echo ‘<br>’ ;
debug ( $notebook );
// получаем информацию о продукте с помощью метода getProduct()
echo $book -> getProduct ();
echo $notebook -> getProduct ();
?>
Получим:
BookProduct Object
(
[numPages] => 1000
[name] => Три мушкетера
[price] => 20
)
NotebookProduct Object
(
[cpu] => Intel
[name] => Dell
[price] => 1000
)
———————————————————————————
О товаре:
Наименование: Три мушкетера
Цена: 20
Кол-во страниц: 1000
———————————————————————————
О товаре:
Наименование: Dell
Цена: 1000
Процессор: Intel
— В результате получаем вместо двух объектов класса Product, конкретные объекты класса BookProduct и класса NotebookProduct.
Никаких пустых свойств: для книжки — cpu и для ноутбука — numPages здесь нет.
Проблема дублирования кода, проблема каких-то лишних свойств для того или иного объекта — решены. При этом, если у нас появятся новые типы товаров, нам достаточно описать соответствующие классы и при этом наследоваться от суперкласса (родительского класса), в котором уже описана какая-то общая логока. И вся эта логика будет наследоваться дочерними классами. В дочерних классах останется только дописать специфичную логику для данного класса.
Наследовать можно один класс только от одного другого класса. Но PHP поддерживает цепочку наследований: сейчас мы наследуем класс BookProduct от класса Product ( расширяем класс Product), но также мы можем создать какой-нибудь класс, который будет наследоваться от класса BookProduct ( расширять класс BookProduct). Этот класс получит все свойства и методы, которые содержатся в классе BookProduct и в классе Product, то есть, он будет наследовать сразу два класса. Эта цепочка наследований не ограничена.
Наследование в C++: beginner, intermediate, advanced
В этой статье наследование описано на трех уровнях: beginner, intermediate и advanced. Expert нет. И ни слова про SOLID. Честно.
Beginner
Что такое наследование?
Наследование является одним из основополагающих принципов ООП. В соответствии с ним, класс может использовать переменные и методы другого класса как свои собственные.
Класс, который наследует данные, называется подклассом (subclass), производным классом (derived class) или дочерним классом (child). Класс, от которого наследуются данные или методы, называется суперклассом (super class), базовым классом (base class) или родительским классом (parent). Термины “родительский” и “дочерний” чрезвычайно полезны для понимания наследования. Как ребенок получает характеристики своих родителей, производный класс получает методы и переменные базового класса.
Наследование полезно, поскольку оно позволяет структурировать и повторно использовать код, что, в свою очередь, может значительно ускорить процесс разработки. Несмотря на это, наследование следует использовать с осторожностью, поскольку большинство изменений в суперклассе затронут все подклассы, что может привести к непредвиденным последствиям.
В этом примере, метод turn_on() и переменная serial_number не были объявлены или определены в подклассе Computer . Однако их можно использовать, поскольку они унаследованы от базового класса.
Важное примечание: приватные переменные и методы не могут быть унаследованы.
Типы наследования
В C ++ есть несколько типов наследования:
- публичный ( public )- публичные ( public ) и защищенные ( protected ) данные наследуются без изменения уровня доступа к ним;
- защищенный ( protected ) — все унаследованные данные становятся защищенными;
- приватный ( private ) — все унаследованные данные становятся приватными.
Для базового класса Device , уровень доступа к данным не изменяется, но поскольку производный класс Computer наследует данные как приватные, данные становятся приватными для класса Computer .
Класс Computer теперь использует метод turn_on() как и любой приватный метод: turn_on() может быть вызван изнутри класса, но попытка вызвать его напрямую из main приведет к ошибке во время компиляции. Для базового класса Device , метод turn_on() остался публичным, и может быть вызван из main .
Конструкторы и деструкторы
В C ++ конструкторы и деструкторы не наследуются. Однако они вызываются, когда дочерний класс инициализирует свой объект. Конструкторы вызываются один за другим иерархически, начиная с базового класса и заканчивая последним производным классом. Деструкторы вызываются в обратном порядке.
Важное примечание: в этой статье не освещены виртуальные десктрукторы. Дополнительный материал на эту тему можно найти к примеру в этой статье на хабре.
Конструкторы: Device -> Computer -> Laptop .
Деструкторы: Laptop -> Computer -> Device .
Множественное наследование
Множественное наследование происходит, когда подкласс имеет два или более суперкласса. В этом примере, класс Laptop наследует и Monitor и Computer одновременно.
Проблематика множественного наследования
Множественное наследование требует тщательного проектирования, так как может привести к непредвиденным последствиям. Большинство таких последствий вызваны неоднозначностью в наследовании. В данном примере Laptop наследует метод turn_on() от обоих родителей и неясно какой метод должен быть вызван.
Несмотря на то, что приватные данные не наследуются, разрешить неоднозначное наследование изменением уровня доступа к данным на приватный невозможно. При компиляции, сначала происходит поиск метода или переменной, а уже после — проверка уровня доступа к ним.
Intermediate
Проблема ромба
Проблема ромба (Diamond problem)- классическая проблема в языках, которые поддерживают возможность множественного наследования. Эта проблема возникает когда классы B и C наследуют A , а класс D наследует B и C .
К примеру, классы A , B и C определяют метод print_letter() . Если print_letter() будет вызываться классом D , неясно какой метод должен быть вызван — метод класса A , B или C . Разные языки по-разному подходят к решению ромбовидной проблем. В C ++ решение проблемы оставлено на усмотрение программиста.
Ромбовидная проблема — прежде всего проблема дизайна, и она должна быть предусмотрена на этапе проектирования. На этапе разработки ее можно разрешить следующим образом:
- вызвать метод конкретного суперкласса;
- обратиться к объекту подкласса как к объекту определенного суперкласса;
- переопределить проблематичный метод в последнем дочернем классе (в коде — turn_on() в подклассе Laptop ).
Если метод turn_on() не был переопределен в Laptop, вызов Laptop_instance.turn_on() , приведет к ошибке при компиляции. Объект Laptop может получить доступ к двум определениям метода turn_on() одновременно: Device:Computer:Laptop.turn_on() и Device:Monitor:Laptop.turn_on() .
Проблема ромба: Конструкторы и деструкторы
Поскольку в С++ при инициализации объекта дочернего класса вызываются конструкторы всех родительских классов, возникает и другая проблема: конструктор базового класса Device будет вызван дважды.
Виртуальное наследование
Виртуальное наследование (virtual inheritance) предотвращает появление множественных объектов базового класса в иерархии наследования. Таким образом, конструктор базового класса Device будет вызван только единожды, а обращение к методу turn_on() без его переопределения в дочернем классе не будет вызывать ошибку при компиляции.
Примечание: виртуальное наследование в классах Computer и Monitor не разрешит ромбовидное наследование если дочерний класс Laptop будет наследовать класс Device не виртуально ( class Laptop: public Computer, public Monitor, public Device <>; ).
Абстрактный класс
В С++, класс в котором существует хотя бы один чистый виртуальный метод (pure virtual) принято считать абстрактным. Если виртуальный метод не переопределен в дочернем классе, код не скомпилируется. Также, в С++ создать объект абстрактного класса невозможно — попытка тоже вызовет ошибку при компиляции.
Интерфейс
С++, в отличии от некоторых ООП языков, не предоставляет отдельного ключевого слова для обозначения интерфейса (interface). Тем не менее, реализация интерфейса возможна путем создания чистого абстрактного класса (pure abstract class) — класса в котором присутствуют только декларации методов. Такие классы также часто называют абстрактными базовыми классами (Abstract Base Class — ABC).
Advanced
Несмотря на то, что наследование — фундаментальный принцип ООП, его стоит использовать с осторожностью. Важно думать о том, что любой код который будет использоваться скорее всего будет изменен и может быть использован неочевидным для разработчика путем.
Наследование от реализованного или частично реализованного класса
Если наследование происходит не от интерфейса (чистого абстрактного класса в контексте С++), а от класса в котором присутствуют какие-либо реализации, стоит учитывать то, что класс наследник связан с родительским классом наиболее тесной из возможных связью. Большинство изменений в классе родителя могут затронуть наследника что может привести к непредвиденному поведению. Такие изменения в поведении наследника не всегда очевидны — ошибка может возникнуть в уже оттестированом и рабочем коде. Данная ситуация усугубляется наличием сложной иерархии классов. Всегда стоит помнить о том, что код может изменяться не только человеком который его написал, и пути наследования очевидные для автора могут быть не учтены его коллегами.
В противовес этому стоит заметить что наследование от частично реализованных классов имеет неоспоримое преимущество. Библиотеки и фреймворки зачастую работают следующим образом: они предоставляют пользователю абстрактный класс с несколькими виртуальными и множеством реализованных методов. Таким образом, наибольшее количество работы уже проделано — сложная логика уже написана, а пользователю остается только кастомизировать готовое решение под свои нужды.
Интерфейс
Наследование от интерфейса (чистого абстрактного класса) преподносит наследование как возможность структурирования кода и защиту пользователя. Так как интерфейс описывает какую работу будет выполнять класс-реализация, но не описывает как именно, любой пользователь интерфейса огражден от изменений в классе который реализует этот интерфейс.
Интерфейс: Пример использования
Прежде всего стоит заметить, что пример тесно связан с понятием полиморфизма, но будет рассмотрен в контексте наследования от чистого абстрактного класса.
Приложение выполняющее абстрактную бизнес логику должно настраиваться из отдельного конфигурационного файла. На раннем этапе разработки, форматирование данного конфигурационного файла до конца сформировано не было. Вынесение парсинга файла за интерфейс предоставляет несколько преимуществ.
Отсутствие однозначности касательно форматирования конфигурационного файла не тормозит процесс разработки основной программы. Два разработчика могут работать параллельно — один над бизнес логикой, а другой над парсером. Поскольку они взаимодействуют через этот интерфейс, каждый из них может работать независимо. Данный подход облегчает покрытие кода юнит тестами, так как необходимые тесты могут быть написаны с использованием мока (mock) для этого интерфейса.
Также, при изменении формата конфигурационного файла, бизнес логика приложения не затрагивается. Единственное чего требует полный переход от одного форматирования к другому — написания новой реализации уже существующего абстрактного класса (класса-парсера). В дальнейшем, возврат к изначальному формату файла требует минимальной работы — подмены одного уже существующего парсера другим.
Заключение
Наследование предоставляет множество преимуществ, но должно быть тщательно спроектировано во избежание проблем, возможность для которых оно открывает. В контексте наследования, С++ предоставляет широкий спектр инструментов который открывает массу возможностей для программиста.
Учебник по наследованию Java: объясняется на примерах
Наследование – это процесс создания нового класса на основе функций другого существующего класса. I… С пометкой java, учебник, ооп.
- Автор записи
Наследование – это процесс создания нового класса на основе функций другого существующего класса. Он широко используется в Java, Python и других объектно-ориентированных языках для повышения возможности повторного использования кода и упрощения логики программы в категориальных и иерархических отношениях.
Однако каждый язык имеет свой собственный уникальный способ реализации наследования, который может затруднить переключение.
Сегодня мы проведем ускоренный курс по использованию наследования в программировании на Java и покажем вам, как реализовать основные инструменты наследования, такие как приведение типов, переопределение методов и окончательные сущности.
Вот что мы рассмотрим сегодня:
- Что такое наследование?
- Наследование в Java
- Примеры наследования Java
- Продвинутые концепции для изучения следующий
Внедряйте Java в два раза быстрее
Получите практическую практику с нашим лучшим контентом Java, адаптированным к текущему уровню квалификации разработчиков.
Java для программистов
Что такое наследование?
Наследование – это механизм, который позволяет одному классу наследовать свойства или поведение от другого класса. Несколько классов могут наследоваться от одного и того же родительского класса, образуя древовидную иерархическую структуру. Наследующие классы могут добавлять функции, помимо унаследованных от родительского класса, для обеспечения уникального поведения.
Наследование имеет важное значение для продвинутого Объектно-ориентированное программирование (ООП), поскольку оно позволяет повторно использовать функции одного класса в вашей программе без репликации кода.
Наследование часто используется для представления категорий (родительских классов) и подкатегорий (подклассов). Родительский класс задает функции, присутствующие во всех объектах, независимо от подкатегории, в то время как каждый подкласс представляет меньшую, более конкретную категорию.
Например, вы могли бы создать класс Автомобиль который указывает колеса и подкласс Седан , который включает в себя атрибут двери . Поток отношений наследования часто отражает логические отношения, аналогичные квадратам и прямоугольникам; в этом случае все седаны являются автомобилями, но не все автомобили являются седанами.
Наследование имеет три основных преимущества:
- Возможность повторного использования: Наследование позволяет повторно использовать функции существующего класса неограниченное количество раз в любом классе, который наследует этот класс. Вы можете сохранить согласованную функциональность для всех объектов одного типа, не переписывая код.
- Структура кода: Наследование обеспечивает четкую, понятную логическую структуру для вашей программы. Это позволяет разработчикам понимать ваш код как набор связанных, но уникальных категорий, а не просто как блок кода.
- Скрытие данных: Базовый класс может быть настроен на сохранение некоторых данных в тайне, чтобы они не могли быть изменены производным классом. Это пример инкапсуляции, когда доступ к данным ограничен только теми классами, которые нуждаются в них для своей роли.
Наследование в Java
Каждый язык программирования имеет несколько иную терминологию для наследования. В Java родительский класс называется суперклассом , а класс-наследник называется подклассом . Разработчики могут также называть суперклассы базовыми или родительскими классами, а подклассы производными или дочерними классами.
Подклассы связаны с суперклассами с помощью ключевого слова extends при их определении. Подклассы могут определять новые локальные методы или поля для использования или могут использовать ключевое слово super для вызова унаследованных методов или суперконструктора.
Когда использовать супер ключевое слово
супер – это, по сути, кнопка “предыдущее значение”, вызываемая из дочернего класса, которая позволяет вам читать и получать доступ к функциям родительского класса независимо от их значения в текущем дочернем классе.
Ключевое слово супер используется для:
- Доступ к полям родительского класса : super.var считывает значение var , заданное в родительском классе, в то время как var самостоятельно считывает измененное значение из дочернего класса.
- Вызов метода родительского класса : super.method() позволяет дочернему классу получить доступ к реализации родительского класса method() . Это требуется только в том случае, если дочерний класс также имеет метод с тем же именем.
- Использование конструкторов : Это позволяет создавать новые экземпляры родительского класса из дочернего класса.
Напомним, что конструкторы в Java – это специальные методы, используемые для инициализации объектов. Вызов суперконструктора создает новый объект, для которого требуются все поля, определенные в конструкторе родительского класса.
Затем вы можете добавить дополнительные поля в другие инструкции, чтобы сделать дочерний экземпляр более конкретным, чем родительский. По сути, это позволяет вам использовать конструктор родительского класса в качестве шаблона для конструктора дочернего класса.
Типы наследования
В Java доступно несколько типов наследования:
Одиночное наследование – это когда один подкласс наследуется от суперкласса, образуя один уровень наследования.
Многоуровневое наследование – это когда суперкласс наследуется промежуточным классом, который затем наследуется производным классом, образуя 3 или более уровней наследования.
Иерархическое наследование – это когда один суперкласс служит основой для нескольких конкретных подклассов. Это наиболее распространенная форма наследования.
Существуют также два других типа наследования, которые доступны только в Java благодаря сочетанию наследования классов и интерфейсов.
Множественное наследование , когда один подкласс наследуется от нескольких родительских классов.
Гибридное наследование , смесь двух или более из вышеперечисленных видов наследования.
Java не поддерживает множественное наследование с классами, что означает, что оба этих типа наследования невозможны только с классами Java. Однако подкласс может наследовать более одного интерфейса (абстрактный класс). Таким образом, вы можете имитировать множественное наследование, если вы объедините использование интерфейсов и классов.
Примеры наследования Java
Чтобы помочь вам лучше понять наследование, давайте перейдем к некоторым примерам кода. Обратите внимание на синтаксические компоненты наследования, которые мы видели до сих пор, такие как супер и общие методы.
Чтобы объявить наследование в Java, мы просто добавляем extends [суперкласс] после идентификатора подклассов.
Вот пример класса Car , который наследуется от базового класса Vehicle с использованием частных строк и методов получения/установки для достижения инкапсуляции.
Это пример одинарного наследования, так как только один объект наследуется от родительского класса. В строке 37 вы можете видеть , что мы используем super для вызова конструктора суперкласса , который упрощает наш конструктор Car . Вы также можете увидеть, как Автомобиль имеет доступ к классу транспортного средства методу printDetails() в строке 42 .
печать сведений() может быть вызвана без супер потому что Автомобиль не имеет собственной реализации печать деталей() . Ключевое слово супер необходимо только тогда, когда программа должна решить, какая версия метода используется.
Приведение типов в Java
Java также позволяет ссылаться на подкласс как на экземпляр его суперкласса, по сути, рассматривая подкласс так, как если бы он принадлежал к типу суперкласса. Этот процесс известен как приведение типов . Это отличный способ создания модульного кода, так как вы можете написать код, который будет работать для любого подкласса одного и того же родителя. Например, вы можете ссылаться на переменную типа Автомобиль как на объект типа Транспортное средство .
Сначала мы создаем экземпляр Car , а затем присваиваем этот экземпляр переменной типа Vehicle . Теперь ссылка на переменную Vehicle указывает на экземпляр Car . Это позволяет вам обрабатывать любой подкласс Транспортного средства как то же самое Транспортное средство тип, даже если вы не знаете к какому подклассу Транспортного средства он относится. Существует два типа типизации: восходящая и нисходящая.
Повышение – это когда вы обращаетесь с дочерним классом так, как если бы он был экземпляром родительского класса, как в нашем предыдущем примере. Любые поля, уникальные для дочернего класса, будут скрыты, чтобы они соответствовали форме родительского класса.
Понижение – это когда вы обращаетесь с экземпляром родительского класса так, как если бы он был одним из его дочерних классов. В то время как любой подкласс может быть повышен, только объекты, которые изначально были типизированы для подкласса, могут быть понижены.
Другими словами, объект может быть понижен, если объект изначально принадлежал к типу подкласса, но позже был повышен до родительского класса.
Переданный объект по-прежнему сохраняет поля, которые у него были, и поэтому может быть добавлен обратно, чтобы снова сделать его допустимым объектом типа дочернего класса.
Однако объекты, которые изначально принадлежали родительскому классу, не имеют значений для каких-либо существенных полей, уникальных для дочернего класса. В результате он будет компилироваться, но выдаст ошибку во время выполнения.
Переопределение методов в Java
Иногда нам понадобится один из наших подклассов, чтобы изменить поведение унаследованного метода. Java позволяет нам делать это, переопределяя существующие методы путем создания новых методов с тем же именем. Это также позволяет нам предоставлять классовые реализации абстрактных методов из интерфейсов.
Переопределение методов является фундаментальным инструментом при реализации полиморфизма, принципа проектирования, который позволяет разным классам иметь уникальные реализации для одного и того же метода. Если мы разберем это слово, то “поли” означает “много”, а “морф” означает “форма”.
Проще говоря, полиморфизм означает наличие множества специфичных для класса форм процесса для выполнения одной и той же задачи.
Вот функции, которыми должна обладать программа, чтобы разрешить переопределение методов:
- Переопределение метода требует наследования, и должен быть хотя бы один производный класс.
- Производные классы должны иметь такое же объявление, т.е. модификатор доступа, имя, те же параметры и тот же тип возвращаемого метода, что и у базового класса.
- Метод в производном классе или классах должен иметь реализацию, отличную друг от друга.
- Метод в базовом классе должен быть переопределен в производном классе.
- Базовый класс/метод не должен быть объявлен как Конечный класс. Чтобы переопределить метод в Java, определите новый метод с тем же именем, что и метод, который вы хотите переопределить, и добавьте @Переопределить тег над ним.
Здесь вы можете увидеть пример того, как мы можем создать поведение, зависящее от класса, для одного и того же вызова метода. Наш вызов метода всегда get Area() однако реализация метода зависит от класса оцениваемой фигуры.
Преимущества переопределения методов заключаются в следующем:
- Каждый производный класс может предоставлять свои собственные конкретные реализации унаследованным методам, не изменяя методы родительского класса.
- Для любого метода дочерний класс может использовать реализацию в родительском классе или создать свою собственную реализацию. Этот вариант обеспечивает вам большую гибкость при разработке решений.
Последнее ключевое слово
В Java ключевое слово final может использоваться при объявлении переменной, класса или метода, чтобы сделать значение неизменным. Значение сущности определяется при инициализации и будет оставаться неизменным на протяжении всей программы. Попытка изменить значение чего-либо, объявленного как final , приведет к ошибке компилятора.
Точное поведение final зависит от типа сущности:
- финал Параметр не может быть изменен нигде в функции
- окончательный Метод не может быть переопределен или скрыт каким-либо подклассом
- окончательный Класс не может быть родительским классом для любого подкласса
Хотя значение сущностей final изменить нельзя, их можно использовать для установки значений переменных, не относящихся к final . Это свойство делает его полезным для решения проблем изменчивости данных, когда для функционирования нескольких разделов кода необходимо ссылаться на один и тот же объект.
Вы можете настроить разделы так, чтобы они ссылались на окончательную версию сущности, использовать ее для создания не- окончательной копии сущности, а затем манипулировать ею для любых операций. Использование final гарантирует, что исходная общая ссылка останется неизменной, чтобы каждая часть могла вести себя согласованно.
Продвинутые концепции для изучения следующий
Наследование является мощным инструментом в Java и имеет важное значение для понимания передовых конструкций ООП. Некоторые следующие концепции, которые следует изучить на вашем пути разработки Java, следующие:
- Абстракция и интерфейсы
- Агрегация
- Композиция
- Расширенные модификаторы доступа
Чтобы помочь вам понять эти и другие передовые концепции, мы создали Java для разработчиков Путь . Внутри вы найдете коллекцию нашего лучшего контента на Java по таким темам, как ООП, многопоточность, рекурсия и новые функции Java 8. Эти уроки выбраны из всей нашей библиотеки курсов, что позволяет вам учиться на наших лучших материалах для каждой концепции.
К концу у вас будут навыки и практический опыт, необходимые для успешного прохождения следующего собеседования на Java.