Введение
Классы редко живут поодиночке. Стоит написать несколько, и обнаруживается, что половина
кода в них одинаковая: у сотрудника, клиента и курьера есть имя и телефон, у всех
документов – автор и дата создания, у всех фигур на холсте – координаты и метод
draw. Копировать это в каждый класс – значит потом искать по всему проекту, где ещё
лежит та же логика.
Отсюда естественное желание – выстроить иерархию: описать общее один раз, а различия
добавить сверху. В интернет-магазине это Товар, от которого идут Книга, Одежда и
Электроника: цена, название и скидка описаны в одном месте, а размер, автор и гарантия –
каждый у своего вида товара. Так и появляется наследование.
Определение: Наследование позволяет классу перенимать атрибуты и методы другого класса, создавая новые классы через абстрагирование общих свойств.
Класс, от которого наследуют, называют родителем (родительский, базовый класс), а тот,
который наследует, – ребёнком (дочерний класс, наследник). В примере с магазином
Товар – родитель, а Книга, Одежда и Электроника – его дети.
Автомобиль: ехать, открывать двери, включать фары.Грузовик: добавляет загрузку щебня, отцепку прицепа, но наследует всё отАвтомобиля.
Пример
using System;
public class Vehicle
{
public int Weight { get; set; }
public int Length { get; set; }
public bool IsHeadlightsOn { get; set; }
public Vehicle()
{
Weight = 100;
Length = 120;
IsHeadlightsOn = false;
}
public void TurnOnHeadlights()
{
IsHeadlightsOn = true;
}
}
public class Truck : Vehicle
{
public bool IsLoaded { get; set; }
public Truck()
{
IsLoaded = true;
}
public void Unload()
{
IsLoaded = false;
}
}
class Program
{
static void Main()
{
Truck car = new Truck();
// Метод описан в Vehicle, но грузовик тоже умеет включать фары
car.TurnOnHeadlights();
Console.WriteLine(car.IsHeadlightsOn);
// А это уже собственный метод грузовика
car.Unload();
Console.WriteLine(car.IsLoaded);
}
}
#include <iostream>
class Vehicle {
public:
int weight = 100;
int length = 120;
bool is_headlights_on = false;
void TurnOnHeadlights() { is_headlights_on = true; }
};
// Наследование в C++ бывает public, protected и private –
// public означает отношение «является»
class Truck : public Vehicle {
public:
bool is_loaded = true;
void Unload() { is_loaded = false; }
};
int main() {
Truck car;
// Метод описан в Vehicle, но грузовик тоже умеет включать фары
car.TurnOnHeadlights();
std::cout << car.is_headlights_on << std::endl;
// А это уже собственный метод грузовика
car.Unload();
std::cout << car.is_loaded << std::endl;
return 0;
}
package main
import "fmt"
// Наследования в Go нет – вместо него встраивание.
// Truck получает поля и методы Vehicle, но это композиция, а не иерархия.
type Vehicle struct {
Weight int
Length int
IsHeadlightsOn bool
}
func NewVehicle() Vehicle {
return Vehicle{Weight: 100, Length: 120}
}
func (v *Vehicle) TurnOnHeadlights() {
v.IsHeadlightsOn = true
}
type Truck struct {
Vehicle
IsLoaded bool
}
func NewTruck() *Truck {
return &Truck{Vehicle: NewVehicle(), IsLoaded: true}
}
func (t *Truck) Unload() {
t.IsLoaded = false
}
func main() {
car := NewTruck()
// Метод описан у Vehicle, но вызывается на грузовике: встраивание
// поднимает его наверх, как если бы он был объявлен у Truck
car.TurnOnHeadlights()
fmt.Println(car.IsHeadlightsOn)
// А это уже собственный метод грузовика
car.Unload()
fmt.Println(car.IsLoaded)
}
class Vehicle:
def __init__(self):
self.weight = 100
self.length = 120
self.is_headlights_on = False
def turn_on_headlights(self):
self.is_headlights_on = True
class Truck(Vehicle):
def __init__(self):
super().__init__()
self.is_loaded = True
def unload(self):
self.is_loaded = False
if __name__ == "__main__":
car = Truck()
# Метод описан в Vehicle, но грузовик тоже умеет включать фары
car.turn_on_headlights()
print(car.is_headlights_on)
# А это уже собственный метод грузовика
car.unload()
print(car.is_loaded)
class Vehicle {
weight = 100;
length = 120;
isHeadlightsOn = false;
turnOnHeadlights(): void {
this.isHeadlightsOn = true;
}
}
class Truck extends Vehicle {
isLoaded = true;
unload(): void {
this.isLoaded = false;
}
}
const car = new Truck();
// Метод описан в Vehicle, но грузовик тоже умеет включать фары
car.turnOnHeadlights();
console.log(car.isHeadlightsOn);
// А это уже собственный метод грузовика
car.unload();
console.log(car.isLoaded);
public class Vehicle {
private int weight;
private int length;
private boolean isHeadlightsOn;
public Vehicle() {
weight = 100;
length = 120;
isHeadlightsOn = false;
}
public boolean isHeadlightsOn() {
return isHeadlightsOn;
}
public void turnOnHeadlights() {
isHeadlightsOn = true;
}
}
public class Truck extends Vehicle {
private boolean isLoaded;
public Truck() {
isLoaded = true;
}
public boolean isLoaded() {
return isLoaded;
}
public void unload() {
isLoaded = false;
}
}
public class Program {
public static void main(String[] args) {
Truck car = new Truck();
// Метод описан в Vehicle, но грузовик тоже умеет включать фары
car.turnOnHeadlights();
System.out.println(car.isHeadlightsOn());
// А это уже собственный метод грузовика
car.unload();
System.out.println(car.isLoaded());
}
}
// В Kotlin классы закрыты для наследования по умолчанию:
// чтобы от Vehicle можно было наследоваться, нужен модификатор open.
open class Vehicle {
var weight = 100
var length = 120
var isHeadlightsOn = false
fun turnOnHeadlights() {
isHeadlightsOn = true
}
}
class Truck : Vehicle() {
var isLoaded = true
fun unload() {
isLoaded = false
}
}
fun main() {
val car = Truck()
// Метод описан в Vehicle, но грузовик тоже умеет включать фары
car.turnOnHeadlights()
println(car.isHeadlightsOn)
// А это уже собственный метод грузовика
car.unload()
println(car.isLoaded)
}
Что даёт наследование
Главная выгода наследования – однократное описание сложной логики. Написали алгоритм
шифрования один раз в базовом классе Device – и все наследники, от умных часов до
роутера, получают его готовым. Каждый добавляет своё: часы – измерение пульса, роутер –
раздачу сети, а шифрование у всех одно и то же, в одном месте.
Отсюда и практический эффект: исправление в базовом классе доходит до всех наследников сразу. Нашли ошибку в алгоритме – поправили её однажды, а не в пятнадцати гаджетах.
Звучит так, будто механизм безусловно хорош. Но посмотрите на пример выше ещё раз: в Go наследования нет вовсе – там встраивание, то есть композиция. И Go тут не исключение, языки помоложе от наследования классов отказываются один за другим. Почему настолько удобная на вид вещь оказалась настолько спорной – в следующем уроке.