Что такое наследование в программировании
Перейти к содержимому

Что такое наследование в программировании

  • автор:

Наследование (программирование)

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

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

Типы наследования

Простое наследование

Класс, от которого произошло наследование, называется базовым или родительским (англ. base class ). Классы, которые произошли от базового, называются потомками, наследниками или производными классами (англ. derived class ).

В некоторых языках используются абстрактные классы. Абстрактный класс — это класс, содержащий хотя бы один абстрактный метод, он описан в программе, имеет поля, методы и не может использоваться для непосредственного создания объекта. То есть от абстрактного класса можно только наследовать. Объекты создаются только на основе производных классов, наследованных от абстрактного. Например, абстрактным классом может быть базовый класс «сотрудник вуза», от которого наследуются классы «аспирант», «профессор» и т. д. Так как производные классы имеют общие поля и функции (например, поле «год рождения»), то эти члены класса могут быть описаны в базовом классе. В программе создаются объекты на основе классов «аспирант», «профессор», но нет смысла создавать объект на основе класса «сотрудник вуза».

Множественное наследование

Основная статья: Множественное наследование

При множественном наследовании у класса может быть более одного предка. В этом случае класс наследует методы всех предков. Достоинства такого подхода в большей гибкости. Множественное наследование реализовано в C++. Из других языков, предоставляющих эту возможность, можно отметить Python и Эйфель. Множественное наследование поддерживается в языке UML.

Множественное наследование — потенциальный источник ошибок, которые могут возникнуть из-за наличия одинаковых имен методов в предках. В языках, которые позиционируются как наследники C++ (Java, C# и др.), от множественного наследования было решено отказаться в пользу интерфейсов. Практически всегда можно обойтись без использования данного механизма. Однако, если такая необходимость все-таки возникла, то, для разрешения конфликтов использования наследованных методов с одинаковыми именами, возможно, например, применить операцию расширения видимости — «::» — для вызова конкретного метода конкретного родителя.

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

Большинство современных объектно-ориентированных языков программирования (C#, Java, Delphi и др.) поддерживают возможность одновременно наследоваться от класса-предка и реализовать методы нескольких интерфейсов одним и тем же классом. Этот механизм позволяет во многом заменить множественное наследование — методы интерфейсов необходимо переопределять явно, что исключает ошибки при наследовании функциональности одинаковых методов различных классов-предков.

Единый базовый класс

В ряде языков программирования все классы явно или неявно наследуются от некого базового класса. Smalltalk был одним из первых языков, в которых использовалась эта концепция. К таким языкам относятся Objective-C ( NSObject ), Perl ( UNIVERSAL ), Eiffel ( ANY ), Java ( java.lang.Object ), C# ( System.Object ), Delphi ( TObject ).

Наследование в языках программирования

Visual Basic

Наследование в Visual Basic:

Class A 'базовый класс End Class Class B : Inherits A 'наследование от A End Class Noninheritable Class C 'Класс, который нельзя наследовать (final в Java) End Class MustInherit Class Z 'Класс, который обязательно наследовать (абстрактный класс) End Class

C++

class A //базовый класс >; class B : public A //public наследование >; class C : protected A //protected наследование >; class Z : private A //private наследование >; 

В C++ существует три типа наследования: public, protected, private. Спецификаторы доступа членов базового класса меняются в потомках следующим образом:

ANSI ISO IEC 14882 2003

Если класс объявлен как базовый для другого класса со спецификатором доступа public, тогда public члены базового класса доступны как public члены производного класса, protected члены базового класса доступны как protected члены производного класса.

Если класс объявлен как базовый для другого класса со спецификатором доступа protected, тогда public и protected члены базового класса доступны как protected члены производного класса.

Если класс объявлен как базовый для другого класса со спецификатором доступа private, тогда public и protected члены базового класса доступны как private члены производного класса.

\ANSI ISO IEC 14882 2003

Одним из основных преимуществ public-наследования является то, что указатель на классы-наследники может быть неявно преобразован в указатель на базовый класс, то есть для примера выше можно написать:

A* a = new B(); 

Эта интересная особенность открывает возможность динамической идентификации типа (RTTI).

Delphi (Object Pascal)

Для использования механизма наследования в Delphi необходимо в объявлении класса справа от слова class указать класс предок:

TAncestor = class private protected public // Виртуальная процедура procedure VirtualProcedure; virtual; abstract; procedure StaticProcedure; end; 
TDescendant = class(TAncestor) private protected public // Перекрытие виртуальной процедуры procedure VirtualProcedure; override; procedure StaticProcedure; end; 

Абсолютно все классы в Delphi являются потомками класса TObject . Если класс-предок не указан, то подразумевается, что новый класс является прямым потомком класса TObject .

Множественное наследование в Delphi частично поддерживается за счёт использования классов-помощников (Сlass Helpers).

Python

Python поддерживает как одиночное, так и множественное наследование. При доступе к атрибуту порядок просмотра производных классов называется порядком разрешения метода (англ. method resolution order ) [1] .

class Ancestor1(object): # Предок 1 def m1(self): pass class Ancestor2(object): # Предок 2 def m1(self): pass class Descendant(Ancestor1, Ancestor2): # Наследник def m2(self): pass d = Descendant() # инстанциация print d.__class__.__mro__ # порядок разрешения метода: 
(class '__main__.Descendant'>, class '__main__.Ancestor1'>, class '__main__.Ancestor2'>, type 'object'>) 

С версии Python 2.2 в языке сосуществуют «классические» классы и «новые» классы. Последние являются наследниками object . «Классические» классы будут поддерживаться вплоть до версии 2.6, но удалены из языка в Python версии 3.0.

Множественное наследование применяется в Python, в частности, для введения в основной класс классов-примесей (англ. mix-in ).

PHP

Для использования механизма наследования в PHP необходимо в объявлении класса после имени объявляемого класса-наследника указать слово extends и имя класса-предка:

class Descendant extends Ancestor  > 

В случае перекрытия классом-наследником свойств и методов предка, доступ к свойствам и методам предка можно получить с использованием ключевого слова parent :

class A  function example()  echo "Вызван метод A::example().
\n"
; > > class B extends A function example() echo "Вызван метод B::example().
\n"
; parent::example(); > >

Objective-C

@interface MyNumber : NSObject  int num; > - (int) num; - (void) setNum: (int) theNum; @end @implementation - (id) init  self = [super init]; return self; > - (int) num  return num; > - (void) setNum: (int) theNum  num = theNum; > @end 

Переопределенные методы не нужно объявлять в интерфейсе.

Java

Пример наследования от одного класса и двух интерфейсов:

public class A  > public interface I1  > public interface I2  > public class B extends A implements I1, I2  > 

Директива final в объявлении класса делает наследование от него невозможным.

C#

Пример наследования от одного класса и двух интерфейсов:

public class A  > public interface I1  > public interface I2  > public class B : A, I1, I2  > 

Наследование от типизированных классов можно осуществлять, указав фиксированный тип, либо путем переноса переменной типа в наследуемый класс:

public class AT>  > public class B : Aint>  > public class B2T> : AT>  > 

Допустимо также наследование вложенных классов от классов, их содержащих:

class A  public class B : A  > > 

Директива sealed в объявлении класса делает наследование от него невозможным. [2]

Ruby

class Parent def public_method "Public method" end private def private_method "Private method" end end class Children  Parent def public_method "Redefined public method" end def call_private_method "Ancestor's private method: " + private_method end end 

Класс Parent является предком для класса Children, у которого переопределен метод public_method.

children = Children.new children.public_method #=> "Redefined public method" children.call_private_method #=> "Ancestor's private method: Private method" 

Приватные методы предка можно вызывать из наследников.

JavaScript

var Parent = function( data )  this.data = data || false; this.public_method = function()  return 'Public Method'; > > var Child = function()  this.public_method = function()  return 'Redefined public method'; > this.getData = function()  return 'Data: ' + this.data; > > Child.prototype = new Parent('test'); var Test = new Child(); Test.getData(); // => "Data: test" Test.public_method(); // => "Redefined public method" Test.data; // => "test" 

Класс Parent является предком для класса Children, у которого переопределен метод public_method. В JavaScript используется прототипное наследование.

Конструкторы и деструкторы

В С++ конструкторы при наследовании вызываются последовательно от самого раннего предка до самого позднего потомка, а деструкторы наоборот — от самого позднего потомка до самого раннего предка.

class First  public: First()  cout  <">>First constructor"  ; > ~First()  cout  <">>First destructor"  ; > >; class Second: public First  public: Second()  cout  <">Second constructor"  ; > ~Second()  cout  <">Second destructor"  ; > >; class Third: public Second  public: Third()  cout  <"Third constructor"  ; > ~Third()  cout  <"Third destructor"  ; > >; // выполнение кода Third *th = new Third(); delete th; // результат вывода /* >>First constructor >Second constructor Third constructor Third destructor >Second destructor >>First destructor */ 

См. также

  • Виртуальное наследование
  • Хрупкий базовый класс

Примечания

  1. о порядке разрешения метода в Python
  2. C# Language Specification Version 4.0, Copyright © Microsoft Corporation 1999—2010

Ссылки

  • Multiple Inheritance
  • Проблемы множественного динамического приведения типов и их решения

Учебники. Программирование для начинающих.

Programm.ws — это сайт, на котором вы можете почитать литературу по языкам программирования , а так-же посмотреть примеры работающих программ на С++, ассемблере, паскале и много другого..

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

Программирование на языке С++

Часть 5. Наследование и шаблоны

Урок 26. Наследование

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

  • Ели ваши программы используют наследование, то для порождения нового класса необходим базовый класс, т.е. новый класс наследует элементы базового класса.
  • Для инициализации элементов производного класса ваша программа должна вызвать конструкторы базового и производного классов.
  • Используя оператор точку, программы могут легко обращаться к элементам базового и производного классов.
  • В дополнение к общим (public) (доступным всем) и частным (private) (доступным методам класса) элементам C++ предоставляет защищенные (protected) элементы, которые доступны базовому и производному классам.
  • Для разрешения конфликта имен между элементами базового и производного классов ваша программа может использовать оператор глобального разрешения, указывая перед ним имя базового или производного класса.

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

ПРОСТОЕ НАСЛЕДОВАНИЕ

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

class employee

<
public:
employee(char *, char *, float);
void show_employee(void);
private:
char name[64];
char position[64];
float salary;
>;

Далее предположим, что вашей программе требуется класс manager, который добавляет следующие элементы данных в класс employee:

float annual_bonus;
char company_car[64];
int stock_options;

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

Для определения этого класса вы должны указать ключевое слово class, имя manager, следующее за ним двоеточие и имя employee, как показано ниже:

Производный класс //——> class manager : public employee

// Здесь определяются элементы
>;

Ключевое слово public, которое предваряет имя класса employee, указывает, что общие (public) элементы класса employee также являются общими и в классе manager. Например, следующие операторы порождают класс manager.

class manager : public employee

<
public:
manager(char *, char *, char *, float, float, int);
void show_manager(void);
private:
float annual_bonus;
char company_car[64];
int stock_options;
>;

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

Следующая программа MGR_EMP.CPP иллюстрирует использование наследования в C++ , создавая класс manager из базового класса employee:

#include

#include

class employee

<
public:
employee(char *, char *, float);
void show_employee(void);
private:
char name [ 64 ];
char position[64];
float salary;
>;

employee::employee(char *name, char *position,float salary)

<
strcpy(employee::name, name);
strcpy(employee::position, position);
employee::salary = salary;
>

void employee::show_employee(void)

<
cout cout cout >

class manager : public employee

<
public:
manager(char *, char *, char *, float, float, int);
void show_manager(void);
private:
float annual_bonus;
char company_car[64];
int stock_options;
>;

manager::manager(char *name, char *position, char *company_car, float salary, float bonus, int stock_options) : employee(name, position, salary)

<
strcpy(manager::company_car, company_car) ;
manager::annual_bonus = bonus ;
manager::stock_options = stock_options;
>

void manager::show_manager(void)

<
show_employee();
cout cout cout >

void main(void)

<
employee worker(«Джон Дой», «Программист», 35000);
manager boss(«Джейн Дой», «Вице-президент «, «Lexus», 50000.0, 5000, 1000);
worker.show_employee() ;
boss.show_manager();
>

Как видите, программа определяет базовый класс employee, а затем определяет производный класс manager. Обратите внимание на конструктор manager. Когда вы порождаете класс из базового класса, конструктор производного класса должен вызвать конструктор базового класса. Чтобы вызвать конструктор базового класса, поместите двоеточие сразу же после конструктора производного класса, а затем укажите имя конструктора базового класса с требуемыми параметрами:

manager::manager(char *name, char *position, char *company_car, float salary, float bonus, int stock_options) :
employee(name, position, salary) //————————————— Конструктор базового класса

<
strcpy(manager::company_car, company_car);
manager::annual_bonus = bonus;
manager::stock_options = stock_options;
>

Также обратите внимание, что функция show_manager вызывает функцию show_employee, которая является элементом класса employee. Поскольку класс manager является производным класса employee, класс manager может обращаться к общим элементам класса employee, как если бы все эти элементы были определены внутри класса manager,

Представление о наследовании

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

Второй пример

Предположим, например, что вы используете следующий базовый класс book внутри существующей программы:

class book

<
public:
book (char *, char *, int);
void show_book(void);
private:
char title[64];
char author[б 4];
int pages;
>;

Далее предположим, что программе требуется создать класс library_card, который будет добавлять следующие элементы данных в класс book:

char catalog[64];
int checked_out; // 1, если проверена, иначе О

Ваша программа может использовать наследование, чтобы породить класс library _card из класса book, как показано ниже:

class library_card : public book

<
public:
library_card(char *, char *, int, char *, int);
void show_card(void);
private:
char catalog[64] ;
int checked_out;
>;

Следующая программа BOOKCARD.CPP порождает класс library_card из клacca book:

#include

#include

class book

<
public:
book(char *, char *, int);
void show_book(void);
private:
char title [64];
char author[64];
int pages;
>;

book::book(char •title, char *author, int pages)

<
strcpy(book::title, title);
strcpy(book::author, author);
book::pages = pages;
>

void book::show_book(void)

<
cout cout cout >

class library_card : public book

<
public:
library_card(char *, char *, int, char *, int);
void show_card(void) ;
private:
char catalog[64];
int checked_out;
>;

library_card::library_card(char *title, char *author, int pages, char *catalog, int checked_out) : book(title, author, pages)

<
strcpy(library_card::catalog, catalog) ;
library_card::checked_out = checked_out;
>

void 1ibrary_card::show_card(void)

<
show_book() ;
cout if (checked_out) cout else cout >

void main(void)

<
library_card card( «Учимся программировать на языке C++», «Jamsa», 272, «101СРР», 1);
card.show_card();
>

Как и ранее, обратите внимание, что конструктор library _card вызывает конструктор класса book для инициализации элементов класса book. Кроме того, обратите внимание на использование функции-элемента show_book класса book внутри функции show_card. Поскольку класс library_card наследует методы класса book, функция show_card может вызвать этот метод (show_book) без помощи оператора точки, как если бы этот метод был методом класса library _card.

ЧТО ТАКОЕ ЗАЩИЩЕННЫЕ ЭЛЕМЕНТЫ

При изучении определений базовых классов вы можете встретить элементы, объявленные как public, private и protected (общие, частные и защищенные). Как вы знаете, производный класс может обращаться к общим элементам базового класса, как будто они определены в производном классе. С другой стороны, производный класс не может обращаться к частным элементам базового класса напрямую. Вместо этого для обращения к таким элементам производный класс должен использовать интерфейсные функции. Защищенные элементы базового класса занимают промежуточное положение между частными и общими. Если элемент является защищенным, объекты производного класса могут обращаться к нему, как будто он является общим. Для оставшейся части вашей программы защищенные элементы являются как бы частными. Единственный способ, с помощью которого ваши программы могут обращаться к защищенным элементам, состоит в использовании интерфейсных функций. Следующее определение класса book использует метку protected, чтобы позволить классам, производным от класса book, обращаться к элементам title, author и pages напрямую, используя оператор точку:

class book

<
public:
book(char *, char *, int) ;
void show_book(void) ;
protected:
char title [64];
char author[64];
int pages;
>;

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

Защищенные элементы обеспечивают доступ и защиту

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

РАЗРЕШЕНИЕ КОНФЛИКТА ИМЕН

Если вы порождаете один класс из другого, возможны ситуации, когда имя элемента класса в производном классе является таким же, как имя элемента в базовом классе. Если возник такой конфликт, C++ всегда использует элементы производного класса внутри функций производного класса. Например, предположим, что классы book и library_card используют элемент price. В случае класса book элемент price соответствует продажной цене книги, например $22.95. В случае класса library’_card price может включать библиотечную скидку, например $18.50. Если в вашем исходном тексте не указано явно (с помощью оператора глобального разрешения), функции класса library_card будут использовать элементы производного класса

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Из этого урока вы узнали, что наследование в C++ позволяет вам строить /порождать) новый класс из существующего класса. Строя таким способом один класс из другого, вы уменьшаете объем программирования, что, в свою очередь, экономит ваше время. Из урока 27 вы узнаете, что C++ позволяет вам порождать класс из двух или нескольких базовых классов. Использование нескольких базовых классов для порождения класса представляет собой множественное наследование. До изучения урока 27 убедитесь, что освоили следующие основные концепции:

    1. Наследование представляет собой способность производить новый класс из существующего базового класса.
    2. Производный класс — это новый класс, а базовый класс — существующий класс.
    3. Когда вы порождаете один класс из другого (базового класса), производный класс наследует элементы базового класса.
    4. Для порождения класса из базового начинайте определение производного класса ключевым словом class, за которым следует имя класса, двоеточие и имя базового класса, например class dalmatian: dog.
    5. Когда вы порождаете класс из базового класса, производный класс может обращаться к общим элементам базового класса, как будто эти элементы определены внутри самого производного класса. Для доступа к частным данным базового класса производный класс должен использовать интерфейсные функции базового класса.
    6. Внутри конструктора производного класса ваша программа должна вызвать конструктор базового класса, указывая двоеточие, имя конструктора базового класса и соответствующие параметры сразу же после заголовка конструктора производного класса.
    7. Чтобы обеспечить производным классам прямой доступ к определенным элементам базового класса, в то же время защищая эти элементы от оставшейся части программы, C++ обеспечивает защищенные
    8. Если в производном и базовом классе есть элементы с одинаковым именем, то внутри функций производного класса C++ будет использовать элементы производного класса. Если функциям производного класса необходимо обратиться к элементу базового класса, вы должны использовать оператор глобального разрешения, например base class:: member.

    Основные принципы ООП: наследование в программировании

    О принципе наследования в ООП простыми словами. Объясняем механизм наследования ООП и преимущества метода на примере Java-кода.

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

    Преимущества принципа наследования

    Принцип наследования является одним из фундаментальных понятий объектно-ориентированного программирования и предоставляет несколько преимуществ, которые делают его важным и полезным инструментом при проектировании программных систем:

    1. Повторное использование кода. Наследование позволяет создавать иерархии классов, где общая функциональность реализуется в родительском классе, и все подклассы автоматически наследуют этот код. Это способствует повторному использованию кода, что уменьшает дублирование и облегчает его поддержку.
    2. Расширяемость. Принцип наследования позволяет создавать новые классы, расширяющие функциональность существующих классов. Подклассы могут добавлять новые свойства и методы, а также переопределять поведение унаследованных методов. Это делает код более гибким и позволяет легко вносить изменения.
    3. Упрощение кода. Использование наследования позволяет разбивать большие и сложные классы на более мелкие и управляемые части. Каждый подкласс специализируется на определенном аспекте функциональности, что упрощает понимание и поддержку кода.
    4. Полиморфизм. Наследование поддерживает концепцию полиморфизма, которая позволяет обращаться к объектам подклассов через ссылки на родительские классы. Это облегчает обработку групп объектов с различными типами, что упрощает написание общего и универсального кода.
    5. Абстракция. Наследование позволяет выделить общие характеристики объектов и создать абстрактные классы, которые определяют интерфейс для группы связанных классов. Абстрактные классы предоставляют общую сущность без необходимости определения всех деталей реализации.
    6. Структурирование кода. Наследование помогает упорядочить классы в логические иерархии, что улучшает структуру программы. Каждый класс наследует функциональность от одного или нескольких родительских классов, что улучшает организацию кода и делает его более понятным и легко поддерживаемым.

    В целом, принцип наследования позволяет создавать более гибкие, модульные и расширяемые программы, что упрощает разработку и сопровождение сложных проектов. Он способствует повторному использованию кода и помогает соблюдать принципы DRY (Don’t Repeat Yourself) и SOLID, что в свою очередь способствует созданию качественного и эффективного кода.

    Пример наследования в ООП

    Рассмотрим пример кода на Java:

    // Родительский класс (суперкласс) class Animal < String name; int age; Animal(String name, int age) < this.name = name; this.age = age; >void makeSound() < System.out.println("Some generic animal sound"); >> // Подкласс, наследующий свойства и методы от Animal class Dog extends Animal < String breed; Dog(String name, int age, String breed) < super(name, age); // Вызов конструктора суперкласса this.breed = breed; >@Override void makeSound() < // Переопределение метода makeSound() в подклассе Dog System.out.println("Woof!"); >void fetch() < System.out.println("Fetching the ball!"); >> // Подкласс, наследующий свойства и методы от Animal class Cat extends Animal < String furColor; Cat(String name, int age, String furColor) < super(name, age); // Вызов конструктора суперкласса this.furColor = furColor; >@Override void makeSound() < // Переопределение метода makeSound() в подклассе Cat System.out.println("Meow!"); >void purr() < System.out.println("Purring. "); >> public class Main < public static void main(String[] args) < Dog dog = new Dog("Buddy", 3, "Golden Retriever"); Cat cat = new Cat("Whiskers", 2, "Gray"); System.out.println("Dog: " + dog.name + ", Age: " + dog.age + ", Breed: " + dog.breed); dog.makeSound(); dog.fetch(); System.out.println("\nCat: " + cat.name + ", Age: " + cat.age + ", Fur Color: " + cat.furColor); cat.makeSound(); cat.purr(); >> 

    В этом примере у нас есть родительский класс Animal , который содержит общие свойства и методы для всех животных. Затем есть два подкласса Dog и Cat , которые наследуют свойства и методы от класса Animal . Каждый подкласс также имеет свои собственные уникальные свойства и методы. Обратите внимание на использование ключевого слова extends при объявлении подклассов.

    Из цикла ETL: настройка первого DAG

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

    ООП с примерами (часть 2)

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

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

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

    Инкапсуляция

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

    Теперь вернёмся в сегодняшний день к современным чудесам автопрома с коробкой-автоматом. На самом деле, по сути, ничего не изменилось. Бензонасос всё так же поставляет бензин в двигатель, дифференциалы обеспечивают поворот колёс на различающиеся углы, коленвал превращает поступательное движение поршня во вращательное движение колёс. Прогресс в другом. Сейчас все эти действия скрыты от пользователя и позволяют ему крутить руль и нажимать на педаль газа, не задумываясь, что в это время происходит с инжектором, дроссельной заслонкой и распредвалом. Именно сокрытие внутренних процессов, происходящих в автомобиле, позволяет эффективно его использовать даже тем, кто не является профессионалом-автомехаником с двадцатилетним стажем. Это сокрытие в ООП носит название инкапсуляции.

    Инкапсуляция – это свойство системы, позволяющее объединить данные и методы, работающие с ними, в классе и скрыть детали
    реализации от пользователя.

    Инкапсуляция неразрывно связана с понятием интерфейса класса. По сути, всё то, что не входит в интерфейс, инкапсулируется в классе.

    Абстракция

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

    Абстрагирование – это способ выделить набор значимых характеристик объекта, исключая из рассмотрения незначимые. Соответственно, абстракция – это набор всех таких характеристик.

    Если бы для моделирования поведения автомобиля приходилось учитывать химический состав краски кузова и удельную теплоёмкость лампочки подсветки номеров, мы никогда бы не узнали, что такое NFS.

    Полиморфизм

    Любое обучение вождению не имело бы смысла, если бы человек, научившийся водить, скажем, ВАЗ 2106 не мог потом водить ВАЗ 2110 или BMW X3. С другой стороны, трудно представить человека, который смог бы нормально управлять автомобилем, в котором педаль газа находится левее педали тормоза, а вместо руля – джойстик.

    Всё дело в том, что основные элементы управления автомобиля имеют одну и ту же конструкцию и принцип действия. Водитель точно знает, что для того, чтобы повернуть налево, он должен повернуть руль, независимо от того, есть там гидроусилитель или нет.
    Если человеку надо доехать с работы до дома, то он сядет за руль автомобиля и будет выполнять одни и те же действия, независимо от того, какой именно тип автомобиля он использует. По сути, можно сказать, что все автомобили имеют один и тот же интерфейс, а водитель, абстрагируясь от сущности автомобиля, работает именно с этим интерфейсом. Если водителю предстоит ехать по немецкому автобану, он, вероятно выберет быстрый автомобиль с низкой посадкой, а если предстоит возвращаться из отдалённого маральника в Горном Алтае после дождя, скорее всего, будет выбран УАЗ с армейскими мостами. Но, независимо от того, каким образом будет реализовываться движение и внутреннее функционирование машины, интерфейс останется прежним.

    Полиморфизм – это свойство системы использовать объекты с одинаковым интерфейсом без информации о типе и внутренней структуре объекта.

    Например, если вы читаете данные из файла, то, очевидно, в классе, реализующем файловый поток, будет присутствовать метод похожий на следующий: byte[] readBytes( int n );
    Предположим теперь, что вам необходимо считывать те же данные из сокета. В классе, реализующем сокет, также будет присутствовать метод readBytes. Достаточно заменить в вашей системе объект одного класса на объект другого класса, и результат будет достигнут.

    При этом логика системы может быть реализована независимо от того, будут ли данные прочитаны из файла или получены по сети. Таким образом, мы абстрагируемся от конкретной специализации получения данных и работаем на уровне интерфейса. Единственное требование при этом – чтобы каждый используемый объект имел метод readBytes.

    Наследование

    Представим себя, на минуту, инженерами автомобильного завода. Нашей задачей является разработка современного автомобиля. У нас уже есть предыдущая модель, которая отлично зарекомендовала себя в течение многолетнего использования. Всё бы хорошо, но времена и технологии меняются, а наш современный завод должен стремиться повышать удобство и комфорт выпускаемой продукции и соответствовать современным стандартам.

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

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

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

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

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *