Course program← Объектно ориентированное программирование
1История ООП
2Объект и Класс
3Базовые концепции ООП
4Принцип проектирования GRASP
5Принцип проектирования SOLID
6Паттерны GOF

Inheritance

This lesson was translated automatically – it may contain errors.

Introduction

Classes rarely live alone. Write a few of them and you find that half of their code is the same: an employee, a customer and a courier all have a name and a phone number; every document has an author and a creation date; every shape on a canvas has coordinates and a draw method. Copying that into each class means later hunting through the whole project for the other places the same logic sits in.

Hence the natural wish to build a hierarchy: describe the common part once and add the differences on top. In an online shop that is Product, with Book, Clothing and Electronics below it: price, name and discount are described in one place, while size, author and warranty belong to their own kind of product. That is where inheritance comes from.

Definition: inheritance lets a class take over the attributes and methods of another class, building new classes by abstracting out the common properties.

The class being inherited from is called the parent (the parent or base class), and the one that inherits is the child (the child class, the subclass). In the shop example Product is the parent, and Book, Clothing and Electronics are its children.

  • Vehicle: drive, open the doors, turn on the headlights.
  • Truck: adds loading gravel and unhitching a trailer, but inherits everything from Vehicle.

An example

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();

        // The method is declared in Vehicle, but the truck can turn on the headlights too
        car.TurnOnHeadlights();
        Console.WriteLine(car.IsHeadlightsOn);

        // And this one is the truck's own method
        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; }
};

// Inheritance in C++ comes as public, protected and private –
// public is the one that means «is a»
class Truck : public Vehicle {
public:
    bool is_loaded = true;

    void Unload() { is_loaded = false; }
};

int main() {
    Truck car;

    // The method is declared in Vehicle, but the truck can turn on the headlights too
    car.TurnOnHeadlights();
    std::cout << car.is_headlights_on << std::endl;

    // And this one is the truck's own method
    car.Unload();
    std::cout << car.is_loaded << std::endl;

    return 0;
}
package main

import "fmt"

// Go has no inheritance – it has embedding instead.
// Truck gets the fields and methods of Vehicle, but that is composition, not a hierarchy.
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()

	// The method belongs to Vehicle but is called on the truck: embedding
	// lifts it up as if it were declared on Truck
	car.TurnOnHeadlights()
	fmt.Println(car.IsHeadlightsOn)

	// And this one is the truck's own method
	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()

    # The method is declared in Vehicle, but the truck can turn on the headlights too
    car.turn_on_headlights()
    print(car.is_headlights_on)

    # And this one is the truck's own method
    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();

// The method is declared in Vehicle, but the truck can turn on the headlights too
car.turnOnHeadlights();
console.log(car.isHeadlightsOn);

// And this one is the truck's own method
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();

        // The method is declared in Vehicle, but the truck can turn on the headlights too
        car.turnOnHeadlights();
        System.out.println(car.isHeadlightsOn());

        // And this one is the truck's own method
        car.unload();
        System.out.println(car.isLoaded());
    }
}
// In Kotlin classes are closed for inheritance by default:
// Vehicle needs the open modifier before anything can inherit from it.
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()

    // The method is declared in Vehicle, but the truck can turn on the headlights too
    car.turnOnHeadlights()
    println(car.isHeadlightsOn)

    // And this one is the truck's own method
    car.unload()
    println(car.isLoaded)
}

What inheritance gives you

The main benefit of inheritance is describing complex logic once. Write an encryption algorithm once in the base class Device and every subclass, from a smart watch to a router, gets it ready-made. Each adds its own part: the watch measures the pulse, the router shares the network, and the encryption is one and the same for all of them, in one place.

Hence the practical effect: a fix in the base class reaches every subclass at once. Found a bug in the algorithm – you fix it once instead of in fifteen gadgets.

It sounds as though the mechanism is good without reservation. But look at the example above once more: Go has no inheritance at all – it has embedding, which is composition. And Go is no exception here; younger languages drop class inheritance one after another. Why something that looks so convenient turned out to be so contested is the subject of the next lesson.