0 added
0 removed
Original
2026-01-01
Modified
2026-02-21
1
<p><a>#статьи</a></p>
1
<p><a>#статьи</a></p>
2
<ul><li>6 дек 2019</li>
2
<ul><li>6 дек 2019</li>
3
<li>0</li>
3
<li>0</li>
4
</ul><p>Вы всё время пользуетесь результатами наследования, даже если не знаете этого. Рассказываем, как меньше дублировать код и что общего у всех классов.</p>
4
</ul><p>Вы всё время пользуетесь результатами наследования, даже если не знаете этого. Рассказываем, как меньше дублировать код и что общего у всех классов.</p>
5
<p>vlada_maestro / shutterstock</p>
5
<p>vlada_maestro / shutterstock</p>
6
<p>Пишет о программировании, в свободное время создаёт игры. Мечтает открыть свою студию и выпускать ламповые RPG.</p>
6
<p>Пишет о программировании, в свободное время создаёт игры. Мечтает открыть свою студию и выпускать ламповые RPG.</p>
7
<p><strong>Наследование в объектно-ориентированном программировании -</strong>это концепция, согласно которой одни классы, называемые родительскими, могут лежать в основе других - дочерних. При этом, дочерние классы перенимают свойства и поведение своего родителя.</p>
7
<p><strong>Наследование в объектно-ориентированном программировании -</strong>это концепция, согласно которой одни классы, называемые родительскими, могут лежать в основе других - дочерних. При этом, дочерние классы перенимают свойства и поведение своего родителя.</p>
8
<p>Все живые существа наследуют черты своих родителей: цвет глаз или волос, форму лица, телосложение и т.д. При этом, какие-нибудь свойства, например, темперамент или физические качества, обязательно отличаются от таковых у родителей - иначе мы были бы копиями своих предков.</p>
8
<p>Все живые существа наследуют черты своих родителей: цвет глаз или волос, форму лица, телосложение и т.д. При этом, какие-нибудь свойства, например, темперамент или физические качества, обязательно отличаются от таковых у родителей - иначе мы были бы копиями своих предков.</p>
9
<p>Другой, более технический пример - телефон и смартфон. Хотя у смартфона намного больше возможностей, чем у обыкновенной "звонилки", одну из них он точно унаследовал от телефона. И по Nokia 3310, и по IPhone 14, и по латвийскому VEF ТА-68 можно звонить другу и обсуждать новые эпизоды "Игры престолов".</p>
9
<p>Другой, более технический пример - телефон и смартфон. Хотя у смартфона намного больше возможностей, чем у обыкновенной "звонилки", одну из них он точно унаследовал от телефона. И по Nokia 3310, и по IPhone 14, и по латвийскому VEF ТА-68 можно звонить другу и обсуждать новые эпизоды "Игры престолов".</p>
10
<p>Более ста лет с момента изобретения телефоны были проводными, а затем инженеры сделали их мобильными, то есть наделили новыми свойствами. Так на основе базового "Телефона" появился дочерний "Мобильный телефон". Потом кто-то засунул туда календарь, будильник, тетрис и интернет, или, как сказали бы программисты, добавил новых методов. Так появился класс "Смартфон", который лежит в основе большинства современных мобилок.</p>
10
<p>Более ста лет с момента изобретения телефоны были проводными, а затем инженеры сделали их мобильными, то есть наделили новыми свойствами. Так на основе базового "Телефона" появился дочерний "Мобильный телефон". Потом кто-то засунул туда календарь, будильник, тетрис и интернет, или, как сказали бы программисты, добавил новых методов. Так появился класс "Смартфон", который лежит в основе большинства современных мобилок.</p>
11
<p>В программирование действуют по тому же принципу: мы создаем новые классы на основе родительских и наделяем их оригинальными свойствами и поведением.</p>
11
<p>В программирование действуют по тому же принципу: мы создаем новые классы на основе родительских и наделяем их оригинальными свойствами и поведением.</p>
12
<h2><strong>Как наследовать класс</strong></h2>
12
<h2><strong>Как наследовать класс</strong></h2>
13
<p>Для начала создадим класс, от которого будем наследовать. Обычно его называют базовым или родительским:</p>
13
<p>Для начала создадим класс, от которого будем наследовать. Обычно его называют базовым или родительским:</p>
14
class Vehicle { public string name; public int speed; public int x; public int y; public void Move(Direction d) { switch(d) { case Direction.Forward: y += speed; break; case Direction.Backward: y -= speed; break; case Direction.Left: x -= speed; break; case Direction.Right: x += speed; break; } } }<p>Этот класс<em>(Vehicle)</em>представляет собой транспортное средство, но пока у него есть только слишком общие свойства (название, координаты и скорость) и поведение (перемещение). Нам может понадобиться реализовать класс, который тоже относится к транспортным средствам, но более конкретным. Например, это будет автомобиль<em>(Car).</em></p>
14
class Vehicle { public string name; public int speed; public int x; public int y; public void Move(Direction d) { switch(d) { case Direction.Forward: y += speed; break; case Direction.Backward: y -= speed; break; case Direction.Left: x -= speed; break; case Direction.Right: x += speed; break; } } }<p>Этот класс<em>(Vehicle)</em>представляет собой транспортное средство, но пока у него есть только слишком общие свойства (название, координаты и скорость) и поведение (перемещение). Нам может понадобиться реализовать класс, который тоже относится к транспортным средствам, но более конкретным. Например, это будет автомобиль<em>(Car).</em></p>
15
<p>Если мы хотим, чтобы класс<em>Car</em>наследовал поля и методы класса<em>Vehicle</em>, то при его объявлении после названия нужно поставить двоеточие и имя родительского класса:</p>
15
<p>Если мы хотим, чтобы класс<em>Car</em>наследовал поля и методы класса<em>Vehicle</em>, то при его объявлении после названия нужно поставить двоеточие и имя родительского класса:</p>
16
<p>Теперь объекты класса<em>Car</em>обладают всеми полями и методами класса<em>Vehicle</em>:</p>
16
<p>Теперь объекты класса<em>Car</em>обладают всеми полями и методами класса<em>Vehicle</em>:</p>
17
Vehicle a = new Vehicle() //Создание экземпляра класса Vehicle { name = "Motorcycle", speed = 2, x = 5, y = 10 }; Car b = new Car() //Создание экземпляра класса Car { //Используем те же поля name = "Car", speed = 3, x = 15, y = 40 }; //Используем одинаковые методы для обоих классов a.Move(Direction.Forward); b.Move(Direction.Left); a = b; //Мы можем привести дочерний класс к типу родительского //При этом стоит учитывать, что функционал и поля дочернего класса перестанут работать для этого объекта<p><strong>Внимание!</strong>Наследовать можно только от одного класса.</p>
17
Vehicle a = new Vehicle() //Создание экземпляра класса Vehicle { name = "Motorcycle", speed = 2, x = 5, y = 10 }; Car b = new Car() //Создание экземпляра класса Car { //Используем те же поля name = "Car", speed = 3, x = 15, y = 40 }; //Используем одинаковые методы для обоих классов a.Move(Direction.Forward); b.Move(Direction.Left); a = b; //Мы можем привести дочерний класс к типу родительского //При этом стоит учитывать, что функционал и поля дочернего класса перестанут работать для этого объекта<p><strong>Внимание!</strong>Наследовать можно только от одного класса.</p>
18
<p>Чтобы добавить в дочерний класс новое поле или метод, нужно просто объявить их:</p>
18
<p>Чтобы добавить в дочерний класс новое поле или метод, нужно просто объявить их:</p>
19
class Car : Vehicle { public int horsePower = 1000; public void Beep() { Console.WriteLine("Beep!"); } }<p>Теперь объекты этого класса могут использовать как метод<em>Move (),</em>так и метод<em>Beep ()</em>. То же самое касается и полей.</p>
19
class Car : Vehicle { public int horsePower = 1000; public void Beep() { Console.WriteLine("Beep!"); } }<p>Теперь объекты этого класса могут использовать как метод<em>Move (),</em>так и метод<em>Beep ()</em>. То же самое касается и полей.</p>
20
<p>Допустим, у родительского класса есть конструктор, который принимает один аргумент:</p>
20
<p>Допустим, у родительского класса есть конструктор, который принимает один аргумент:</p>
21
public Vehicle(string name) { this.name = name; }<p>Все дочерние классы должны вызывать его в своих конструкторах, передавая аргумент того же типа. Для этого используется ключевое слово<em>base:</em></p>
21
public Vehicle(string name) { this.name = name; }<p>Все дочерние классы должны вызывать его в своих конструкторах, передавая аргумент того же типа. Для этого используется ключевое слово<em>base:</em></p>
22
public Car(string name, int horsePower) :base(name) { this.horsePower = horsePower; }<p>В скобках после<em>base</em>указывается аргумент, который нужно передать в родительский класс. При этом повторно описывать логику присваивания<em>name</em>не нужно.</p>
22
public Car(string name, int horsePower) :base(name) { this.horsePower = horsePower; }<p>В скобках после<em>base</em>указывается аргумент, который нужно передать в родительский класс. При этом повторно описывать логику присваивания<em>name</em>не нужно.</p>
23
<p>Если вы не хотите ничего вызывать, то просто создайте в наследуемом классе пустой конструктор.</p>
23
<p>Если вы не хотите ничего вызывать, то просто создайте в наследуемом классе пустой конструктор.</p>
24
<p>Часто бывает нужно, чтобы какой-то метод в дочернем классе работал немного иначе, чем в родительском. Например, в методе<em>Move ()</em>для класса<em>Car</em>можно прописать условие, которое будет проверять, не кончилось ли топливо. Точно так же может появиться необходимость переопределить свойство.</p>
24
<p>Часто бывает нужно, чтобы какой-то метод в дочернем классе работал немного иначе, чем в родительском. Например, в методе<em>Move ()</em>для класса<em>Car</em>можно прописать условие, которое будет проверять, не кончилось ли топливо. Точно так же может появиться необходимость переопределить свойство.</p>
25
<p>Методы и свойства, которые можно переопределить, называются виртуальными. В родительском классе для них указывается модификатор<em>virtual:</em></p>
25
<p>Методы и свойства, которые можно переопределить, называются виртуальными. В родительском классе для них указывается модификатор<em>virtual:</em></p>
26
public virtual void GetInfo() { Console.WriteLine($"Name: {name}\nSpeed: {speed}"); }<p>А в дочернем для переопределения используется модификатор<em>override:</em></p>
26
public virtual void GetInfo() { Console.WriteLine($"Name: {name}\nSpeed: {speed}"); }<p>А в дочернем для переопределения используется модификатор<em>override:</em></p>
27
public override void GetInfo() { Console.WriteLine($"Name: {name}\nSpeed: {speed}\n Horse power: {horsePower}"); }<p>Таким образом можно определить разную логику для разных классов. Это тоже можно считать<a>полиморфизмом</a>.</p>
27
public override void GetInfo() { Console.WriteLine($"Name: {name}\nSpeed: {speed}\n Horse power: {horsePower}"); }<p>Таким образом можно определить разную логику для разных классов. Это тоже можно считать<a>полиморфизмом</a>.</p>
28
<p>Несмотря на то что наследовать можно только от одного класса, существует также и класс Object, который является родительским для всех остальных. У него есть четыре метода:</p>
28
<p>Несмотря на то что наследовать можно только от одного класса, существует также и класс Object, который является родительским для всех остальных. У него есть четыре метода:</p>
29
<ul><li><em><strong>Equals ()</strong></em> - проверяет, равен ли текущий объект тому, что был передан в аргументе.</li>
29
<ul><li><em><strong>Equals ()</strong></em> - проверяет, равен ли текущий объект тому, что был передан в аргументе.</li>
30
<li><em><strong>ToString ()</strong></em><strong></strong>- преобразует объект в строку.</li>
30
<li><em><strong>ToString ()</strong></em><strong></strong>- преобразует объект в строку.</li>
31
<li><em><strong>GetHashCode ()</strong></em> - получает числовой хеш объекта. Этот метод редко используется, потому что может возвращать одинаковый хеш для разных объектов.</li>
31
<li><em><strong>GetHashCode ()</strong></em> - получает числовой хеш объекта. Этот метод редко используется, потому что может возвращать одинаковый хеш для разных объектов.</li>
32
<li><em><strong>GetType ()</strong></em><strong></strong>- получает тип объекта.</li>
32
<li><em><strong>GetType ()</strong></em><strong></strong>- получает тип объекта.</li>
33
</ul><p>Любой из них также может быть переопределён или перегружен. Например, метод<em>Equals ()</em>можно использовать, чтобы он проверял, равны ли поля объектов:</p>
33
</ul><p>Любой из них также может быть переопределён или перегружен. Например, метод<em>Equals ()</em>можно использовать, чтобы он проверял, равны ли поля объектов:</p>
34
public bool Equals(Car obj) { bool areEqual = false; if(obj.name == this.name && obj.horsePower == this.horsePower) { areEqual = true; } return areEqual; }<p>В данном случае это именно перегрузка, потому что ни один из вариантов метода<em>Equals ()</em>не принимал объект класса<em>Car</em>. Отсюда следует, что переопределить можно только метод с такими же принимаемыми аргументами.</p>
34
public bool Equals(Car obj) { bool areEqual = false; if(obj.name == this.name && obj.horsePower == this.horsePower) { areEqual = true; } return areEqual; }<p>В данном случае это именно перегрузка, потому что ни один из вариантов метода<em>Equals ()</em>не принимал объект класса<em>Car</em>. Отсюда следует, что переопределить можно только метод с такими же принимаемыми аргументами.</p>
35
<p>Есть несколько особенностей, которые нужно знать при работе с наследованием:</p>
35
<p>Есть несколько особенностей, которые нужно знать при работе с наследованием:</p>
36
<ul><li>Наследовать можно только от класса, уровень доступа которого выше дочернего или равен ему. То есть публичный класс не может наследоваться от приватного.</li>
36
<ul><li>Наследовать можно только от класса, уровень доступа которого выше дочернего или равен ему. То есть публичный класс не может наследоваться от приватного.</li>
37
<li>Дочерний класс не может обращаться к приватным полям и методам родительского. Поэтому нужно либо определять логику приватных компонентов в базовом классе, либо создавать публичные свойства и методы, которые будут своего рода посредниками.</li>
37
<li>Дочерний класс не может обращаться к приватным полям и методам родительского. Поэтому нужно либо определять логику приватных компонентов в базовом классе, либо создавать публичные свойства и методы, которые будут своего рода посредниками.</li>
38
<li>У дочернего класса может быть только один родительский, но у родительского может быть несколько дочерних.</li>
38
<li>У дочернего класса может быть только один родительский, но у родительского может быть несколько дочерних.</li>
39
<li>Нельзя наследовать от класса с модификатором<em>static</em>.</li>
39
<li>Нельзя наследовать от класса с модификатором<em>static</em>.</li>
40
<li>Можно наследовать от класса, который наследует от другого класса. Но с этим лучше не злоупотреблять, потому что можно быстро запутаться в их взаимосвязях.</li>
40
<li>Можно наследовать от класса, который наследует от другого класса. Но с этим лучше не злоупотреблять, потому что можно быстро запутаться в их взаимосвязях.</li>
41
</ul><p>Чтобы лучше это усвоить, стоит попробовать поработать с каждой особенностью на практике и немного поэкспериментировать.</p>
41
</ul><p>Чтобы лучше это усвоить, стоит попробовать поработать с каждой особенностью на практике и немного поэкспериментировать.</p>
42
<p>Создайте несколько классов персонажей: например, воин, лучник и маг.</p>
42
<p>Создайте несколько классов персонажей: например, воин, лучник и маг.</p>
43
<p>Каждый из них должен быть родительским для нескольких других классов допустим, воин будет базовым классом для рыцаря и берсеркера.</p>
43
<p>Каждый из них должен быть родительским для нескольких других классов допустим, воин будет базовым классом для рыцаря и берсеркера.</p>
44
<p>У всех персонажей должен быть метод<em>Attack ()</em>, при вызове которого у разных персонажей будут выводиться различные сообщения. Например, если атаковать будет маг, то мы должны увидеть сообщение, что он запустил огненный шар.</p>
44
<p>У всех персонажей должен быть метод<em>Attack ()</em>, при вызове которого у разных персонажей будут выводиться различные сообщения. Например, если атаковать будет маг, то мы должны увидеть сообщение, что он запустил огненный шар.</p>
45
<p>С помощью наследования можно создавать множество полезных классов с общим поведением и свойствами, при этом не дублируя код. Однако это ещё не всё, что можно использовать, - в следующей статье вы узнаете про интерфейсы и абстрактные классы.</p>
45
<p>С помощью наследования можно создавать множество полезных классов с общим поведением и свойствами, при этом не дублируя код. Однако это ещё не всё, что можно использовать, - в следующей статье вы узнаете про интерфейсы и абстрактные классы.</p>
46
<a><b>Бесплатный курс по Python ➞</b>Мини-курс для новичков и для опытных кодеров. 4 крутых проекта в портфолио, живое общение со спикером. Кликните и узнайте, чему можно научиться на курсе. Смотреть программу</a>
46
<a><b>Бесплатный курс по Python ➞</b>Мини-курс для новичков и для опытных кодеров. 4 крутых проекта в портфолио, живое общение со спикером. Кликните и узнайте, чему можно научиться на курсе. Смотреть программу</a>