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

Даже если бы Санта-Клаус был настоящим (а он вполне реален — я дважды проверял!), он не смог бы наследоваться от класса Human.

Что такое наследование?

В ООП наследование — это способ, которым один класс наследует характеристики и поведение другого класса. Это позволяет нам создать иерархию классов и представить отношение «есть-а». Например, класс «Собака» может наследоваться от класса «Млекопитающее», потому что собака — это тип млекопитающего.

Почему Санта-Клаус не может наследовать от человека

Хотя на первый взгляд может показаться логичным сделать Санта-Клауса наследником человека, есть несколько причин, по которым это не очень хорошая идея.

Наследование обычно используется для представления отношения «есть-а», что означает, что подкласс (в данном случае Санта-Клаус) является более конкретным типом надкласса (Человек). Однако Санта-Клаус не является типом человека — у него есть уникальные характеристики и способности, которые не применимы ко всем людям. Например, у Деда Мороза есть список непослушных и славных детей, волшебные сани, которые могут облететь весь мир за одну ночь, и способность доставлять подарки всем хорошим мальчикам и девочкам на Земле.

Вы можете подумать, что вот как это работает: у вас есть более общая база с более конкретными вещами поверх нее. Но реальность и программирование работают по-разному. Например, если мы попытаемся сделать так, чтобы Санта-Клаус наследовал от Человека, мы могли бы получить такие методы, как «getAge()» или «getHeight()», которые не имеют смысла для персонажа, которому сотни лет и который может уменьшаться. или расти, чтобы пройти через дымоходы.

Лучший подход

Лучшим подходом было бы создание отдельного класса для Санта-Клауса и определение его характеристик и поведения в этом классе. Таким образом, мы можем точно представить его и избежать путаницы или ненужных проверок по всему коду. В конце концов, мы бы не хотели, чтобы какой-нибудь непослушный код проник в наш хороший список!

Квадрат и прямоугольник

Другой пример неуместного наследования — попытка сделать класс «Квадрат» наследником класса «Прямоугольник». Хотя в реальной жизни квадрат является типом прямоугольника, он имеет свои уникальные характеристики (например, все стороны имеют одинаковую длину), которые не являются общими для всех прямоугольников. В этом случае для целей программирования было бы лучше создать для Square отдельный класс и самостоятельно определить его характеристики и поведение.

Вот пример того, что может случиться, когда мы делаем Square унаследованным от Rectangle:

class Rectangle {
    constructor(public length: number, public width: number) {}

    getArea(): number {
        return this.length * this.width;
    }

    setLength(length: number): void {
        this.length = length;
    }
    setWidth(width: number): void {
        this.width = width;
    }
}

class Square extends Rectangle {
    constructor(sideLength: number) {
        super(sideLength, sideLength);
    }
}

const square = new Square(5);
console.log(square.getArea());  // 25

square.setLength(10);
console.log(square.getArea());  // 50

В этом коде у нас есть класс Rectangle с методом getArea() для вычисления площади прямоугольника, а также методы установки для изменения длины и ширины прямоугольника. У нас также есть класс Square, который наследуется от Rectangle, но переопределяет конструктор, чтобы установить длину и ширину равными (поскольку они равны в квадрате).

Сначала, когда мы создаем объект Square и вызываем метод getArea(), он работает, как и ожидалось, и возвращает правильное значение. Однако когда мы используем метод установки setLength() для изменения длины квадрата, это нарушает ожидаемое поведение квадрата.

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

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

class Square {
    constructor(public sideLength: number) {}

    getArea(): number {
        return Math.pow(this.sideLength, 2);
    }

    setSideLength(sideLength: number): void {
        this.sideLength = sideLength;
    }
}

const square = new Square(5);
console.log(square.getArea());  // 25

square.setSideLength(10);
console.log(square.getArea());  // 100

В этом исправленном коде у нас есть отдельный класс Square с собственным методом установки для изменения длины стороны квадрата. Когда мы используем этот метод установки для изменения длины стороны, он правильно обновляет все стороны квадрата и поддерживает ожидаемое поведение.

Кроме того, если бы мы попытались поддерживать квадрат как подкласс прямоугольника, нам пришлось бы включить в код множество условных операторов для учета квадрата или переопределить их в классе квадрата, и даже тогда мы все равно получили бы такие методы, как setLength() и setHeight().

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

Окончательно

В некоторых случаях, например, в случае с Санта-Клаусом и Квадратом, лучше игнорировать концепции из реальной жизни в пользу того, что будет лучше работать для концепций программирования.

Итак, используйте это время и поступайте как Санта, проверяя свой список классов и разделяя их на хорошие и непослушные классы, а затем проверяя дважды.

Счастливых праздников и удачного кодирования!

Фото на обложке Mike Arney на Unsplash