В классе наследнике определен статический метод такой же как в родительском
Перейти к содержимому

В классе наследнике определен статический метод такой же как в родительском

  • автор:

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

В случае наследования в Java для указания родительского класса используется ключевое слово extends, которое следует после имени дочернего, т. е. производного, класса. После extends пишется родительский класс. Другими словами, «дочерний класс расширяет родительский».

В Java нет множественного наследования от классов, но есть множественное наследование интерфейсов.

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

Причем конструктор без параметров обязательно должен быть в родительском классе, если в дочернем предполагаются только конструкторы с аргументами, а родительские конструкторы не вызываются явно. В этом случае, если в родительском классе будут только конструкторы с аргументами, то в дочернем будет возникать ошибка.

public class Main  public static void main(String[] args)  Child child = new Child(10); System.out.println(child.a); System.out.println(child.b); > >
class Parent  int a; Parent(int a)  this.a = 1; > >
class Child extends Parent  int b; Child(int b)  this.b = b; > >

В приведенном выше примере ошибка возникает на уровне конструктора класса Child. Перед тем как выполнится выражение this.b = b, будет неявно вызван конструктор Parent без параметров. Однако такого конструктора в родительском классе нет.

Если мы закомментируем или удалим конструктор Parent,

class Parent  int a; // Parent(int a) // this.a = 1; // > >

то ошибка в дочернем классе исчезнет. Дело в том, что в Java любой класс есть потомок базового класса Object, у которого всегда имеется пустой дефолтный конструктор. В данном случае, двигаясь по дереву наследования к корню, из Child будет вызываться конструктор базового для всех класса.

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

Заметим, что у объектов Child поле this.a существует, даже если ему не было присвоено значение. В данном случае значение будет нулевым.

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

import java.util.Random; class Parent  int a; Parent()  Random random = new Random(); this.a = random.nextInt(10); > >

Другой распространенный вариант – когда в дочернем классе явно вызывается конструктор родительского класса с помощью ключевого слова super. Причем вызывать всегда надо первой строчкой тела конструктора.

public class Main  public static void main(String[] args)  Child child = new Child(10, 20); System.out.println(child.a); System.out.println(child.b); > >
class Parent  int a; Parent(int a)  this.a = a; > >
class Child extends Parent  int b; Child(int a, int b)  super(a); this.b = b; > >

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

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

Также возможно сужение типа, когда объект, связанный с переменной родительского типа, присваивается переменной дочернего, то есть своего типа. В этих случаях, чтобы избежать ошибок, следует проверять тип объекта. Например, пусть класс B производный от класса A. Тогда:

A var = new B(); if (var instanceof B)  B var1 = (B) var; >

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

public class ParentMeth  public static void main(String[] args)  ChildMeth ch = new ChildMeth(); ch.PrintStr(); > public void PrintStr()  System.out.println("Parent class"); > > class ChildMeth extends ParentMeth  public void PrintStr()  super.PrintStr(); System.out.println("Child class"); > >
Parent class Child class

X Скрыть Наверх

Программирование на Java. Курс

Перегрузка и переопределение методов в Java: примеры

Метод в Java — это функция, которая определяет, что умеет делать объект этого класса. Одна из главных задач методов — выполнение действий над данными объекта. Они могут менять значение, преобразовывать данные, выводить их в консоль.

Методы можно перегружать и переопределять. Как это делать и в чём разница между этими двумя механизмами — разберёмся в этой статье.

Перегрузка метода

Перегрузка методов в Java — это использование одного имени метода с разными параметрами.

Чтобы разобраться с этим механизмом, начнём с простого — создадим помощника, который будет здороваться с пользователями.

public class Assistant  
public void sayHello(String name) System.out.println("Добрый день, " + name + "!");
>

public static void main(String[] args) Assistant assistant = new Assistant();
assistant.sayHello("Михаил");
>
>

В консоли будет выведена фраза «Добрый день, Михаил!».

Допустим, Михаил пришёл не один, а с другом Виталием. Сейчас метод реализован так, что помощник поприветствует только Михаила, а Виталия проигнорирует. Чтобы исправить это, реализуем в классе два метода. Имя у них будет одинаковое. Но параметры они принимают разные.

public class Assistant  
public void sayHello(String firstGuest) System.out.println("Добрый вечер, " + firstGuest + "!");
>

public void sayHello(String firstGuest, String secondGuest) System.out.println("Добрый день, " + firstGuest + " и " + secondGuest + "!");
>
public static void main(String[] args) Assistant assistant = new Assistant();
assistant.sayHello("Михаил", "Виталий");
>
>

Теперь в консоли отобразится фраза «Добрый день, Михаил и Виталий!».

Мы уже перегрузили sayHello(). Теперь программа стала более гибкой — помощник может приветствовать сразу двух гостей. Но что произойдёт, если придут трое, четверо или пятеро? Проверим:

public class Assistant  
public void sayHello(String firstGuest) System.out.println("Добрый вечер, " + firstGuest + "!");
>

public void sayHello(String firstGuest, String secondGuest) System.out.println("Добрый день, " + firstGuest + " и " + secondGuest + "!");
>
public static void main(String[] args) Assistant assistant = new Assistant();
assistant.sayHello("Михаил", "Виталий", "Марина");
>
>

В ответ получим ошибку, потому что sayHello() готов принимать только два аргумента. Решение в лоб — перегружать его дальше. Сделать так, чтобы .sayHello() принимал троих, четверых, пятерых и больше гостей. Но это не похоже на гибкую работу программы. Придётся постоянно дописывать код.

Более гибкое решение — передать в качестве параметра аргумент переменной длины (String… names). Это позволит sayHello() принимать любое количество строк. А чтобы выводить в консоль приветствие каждого гостя, используем цикл.

public class Assistant  
public void sayHello(String firstGuest) System.out.println("Добрый вечер, " + firstGuest + "!");
>

public void sayHello(String… names)
for (String name: names) System.out.println("Добрый вечер, " + name + "!");
>
>

public static void main(String[] args) Assistant assistant = new Assistant();
assistant.sayHello("Михаил", "Виталий", "Марина", "Андрей", "Анна");
>

В консоли отобразится приветствие каждого переданного гостя:

Добрый вечер, Михаил!
Добрый вечер, Виталий!
Добрый вечер, Марина!
Добрый вечер, Андрей!
Добрый вечер, Анна!

Порядок аргументов

В примере выше мы не думали о порядке аргументов, потому что все они были строками. Нет разницы, с кем здороваться сначала — с Михаилом или с Анной.

Но порядок аргументов имеет значение, если метод принимает, например, строку и число. Посмотрите:

public class User  
public static void sayYourAge(String greeting, int age) System.out.println(greeting + " " + age);
>

public static void main(String[] args) sayYourAge(20, "Мой возраст - "); //ошибка!
>
>

На этапе компиляции возникнет ошибка, потому что при определении sayYourAge() мы задали, что сначала должна быть строка, а затем — число, но аргументы передали в обратном порядке.

Чтобы исправить ошибку, достаточно передать аргументы в правильном порядке:

public class User  
public static void sayYourAge(String greeting, int age) System.out.println(greeting + " " + age);
>

public static void main(String[] args) sayYourAge("Мой возраст - ", 20);
>
>

Чтобы избежать ошибок, можно сделать перегрузку порядком параметров. Например, вот так:

public class User  
public static void sayYourAge(String greeting, int age) System.out.println(greeting + " " + age);
>
public static void sayYourAge(int age, String greeting) System.out.println(greeting + " " + age);
>

public static void main(String[] args) sayYourAge("Мой возраст - ", 20);
sayYourAge(20, "Мой возраст - ");
>
>

Теперь не имеет значения, в каком порядке передавать аргументы — оба варианты будут понятны программе.

Варианты перегрузки

Из примеров выше можно выделить три варианта перегрузки.

  1. По количеству параметров.
public class Calculator void calculate(int number1, int number2) < > 
void calculate(int number1, int number2, int number3) < >
>
  1. По типам параметров:
public class Calculator void calculate(int number1, int number2) < > 
void calculate(double number1, double number2) < >
>
  1. По порядку параметров:
public class Calculator void calculate(double number1, int number2) < > 
void calculate(int number1, double number2) < >
>

Напоследок повторим, что означает перегрузка метода в Java . Это механизм языка, который позволяет создавать несколько методов с одинаковым названием, но разными параметрами. Так можно делать не во всех языках.

Перегрузка — это часть полиморфизма, одной из ключевых составляющих объектно-ориентированного программирования. Главный плюс перегрузки в Java — можно использовать схожие методы с одинаковыми именами.

Переопределение метода

Переопределение метода в Java позволяет взять метод родительского класса и создать специфическую реализацию в классе-наследнике.

Проще понять на примере. Допустим, вы создаёте класс Animal с методом voice(). Он нужен для того, чтобы животное могло подать голос:

public class Animal  
public void voice()
System.out.println("Говори!");
>
>

И сразу возникает проблема — все животные издают разные звуки. Можно создать для каждого отдельный метод. Например, у кошки это будет voiceCat(), а у собаки — voiceDog(). Но представьте, сколько строк кода придётся написать, чтобы дать возможность всем животным подать голос?

Здесь на помощь и приходит механизм переопределения в Java. Он позволяет заменить реализацию в классе-наследнике. Посмотрим на примере кошки и собаки:

public class Cat extends Animal  
@Override
public void voice() System.out.println("Мяу!");
>
>

public class Dog extends Animal
@Override
public void voice() System.out.println("Гав!");
>
>

В выводе отобразится сначала «Мяу», а затем — «Гав». Чтобы добиться такого результата, нужно:

  1. В классе-наследнике создать метод с таким же именем, как в родительском классе.
  2. Добавить перед ним аннотацию @Override (с английского переводится как «переопределён»). Эта аннотация сообщит компилятору, что это не ошибка, вы намеренно переопределяете метод. Отметим, что наличие аннотации необязательно. Если в дочернем классе создать метод с такой же сигнатурой, как у родительского, метод все равно переопределится. Но рекомендуется ставить аннотацию всегда, так как это улучшает «читабельность» кода, а также при этом компилятор проверит на этапе сборки, что такой метод действительно есть в родительском классе.

Собственная реализация пишется для каждого класса-наследника. Если этого не сделать, то будет использована реализация родительского класса.

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

super.method();

Ограничения при переопределении

У переопределения методов класса в Java есть ряд ограничений.

  • Название метода должно быть таким же, как у метода родителя (то есть сигнатура метода должна быть одинаковой).
  • Аргументы должны оставаться такими же, как у метода родителя.
  • Тип возвращаемого значения должен быть таким же, как у метода родителя.
  • Модификатор доступа должен быть таким же, как у метода родителя.
  • Окончательные методы (final) нельзя переопределять. Это один из способов запрета переопределения — объявить метод с помощью ключевого слова final.
class Parent final void show() <>
>

class Child extends Parent void show() <>
>

Такой код вернёт ошибку, потому что в родительском классе использовано ключевое слово final.

  • Статические методы (static) нельзя переопределять. Если вы определите в классе-наследнике такую же сигнатуру метода, как в родительском классе, то выполните сокрытие. Подробнее об этом вы можете прочитать в документации .
  • Приватные методы (private) нельзя переопределять, так как они связываются на этапе компиляции, а не выполнения.
  • Нельзя сужать модификатор доступа — например, с public до private. Расширение уровня доступа возможно.
  • Нельзя менять тип возвращаемого значения, однако можно сузить возвращаемое значение, если они совместимы.

Переопределение класса в Java подчиняется указанным выше правилам, которые необходимо соблюдать.

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

Заключение

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

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

Урок 6. Принципы ООП. Классы, объекты, поля и методы. Уровни доступа.

Поговорим про основные принципы объектно-ориентированного программирования: абстракцию, инкапсуляцию, наследование и полиморфизм. Научимся создавать классы и объекты классов в Python. Рассмотрим, чем отличаются понятия поля, свойства, методы и атрибуты класса. Изучим особенности организации уровней доступа к атрибутам: Public, Protected и Private.

Logo Python Course Lesson 5

Курс «Программирование на Python»

Урок 6.
Принципы ООП. Классы, объекты, поля и методы. Уровни доступа.

Поговорим про основные принципы объектно-ориентированного программирования: абстракцию, инкапсуляцию, наследование и полиморфизм. Научимся создавать классы и объекты классов в Python. Рассмотрим, чем отличаются понятия поля, свойства, методы и атрибуты класса. Изучим особенности организации уровней доступа к атрибутам: Public, Protected и Private.

Предыдущий урок
Урок 5. Модули и пакеты в Python. Импорт. Виртуальная среда venv.
Решения к задачам по курсу «Программирование на Python»
ТЕОРЕТИЧЕСКИЙ БЛОК
В КОНЦЕ УРОКА ЕСТЬ ВИДЕО

One

Что такое ООП?

  1. Процедурное программирование
  2. Объектно-ориентированное программирование (оно же ООП)

По сути любая программа представляет собой совокупность данных и операций по их обработке. Но что важнее, сами данные или операции над ними? В языках, в основе работы которых лежит принцип процедурного программирования ( Basic , C , Pascal , Go ), главным является код для обработки данных. При этом сами данные имеют второстепенное значение.

Smartiqa Процедурное программирование. Работа с данными.

Процедурное программирование. Работа с данными.

В чем суть процедурного подхода? Процедурное программирование – это написание функций и их последовательный вызов в некоторой главной( main ) функции.

Для каких проектов подходит процедурное программирование? Идеальные условия для применения данного подхода — простые программы, где весь функционал можно реализовать несколькими десятками процедур/функций. Функции аккуратно вложены друг в друга и легко взаимодействуют посредством передачи данных из одной функции в другую.

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

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

Долгое время процедурный подход был довольно популярным и считался самым прогрессивным. Однако объемы кода программ росли, и ему на смену пришло объектно-ориентированное программирование. Вот как определяет данный подход Википедия:

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

В основе ООП лежит простая и элегантная идея, в соответствии с которой главное в программе — это данные. Именно они определяют, какие методы будут использоваться для их обработки. Т. е. данные первичны, код для обработки этих данных — вторичен.

Smartiqa Объектно-ориентированное программирование. Работа с данными.

Объектно-ориентированное программирование. Работа с данными.

  1. Программа разбивается на объекты. Каждый объект отвечает за собственные данные и их обработку. Как результат — код становится проще и читабельней.
  2. Уменьшается дупликация кода. Нужен новый объект, содержимое которого на 90% повторяет уже существующий? Давайте создадим новый класс и унаследуем эти 90% функционала от родительского класса!
  3. Упрощается и ускоряется процесс написания программ. Можно сначала создать высокоуровневую структуру классов и базовый функционал, а уже потом перейти к их подробной реализации.
  1. В процедурном подходе основой программы является функция. Функции вызывают друг друга и при необходимости передают данные. В программе функции живут отдельно, данные — отдельно.
  2. Основной недостаток процедурного подхода — сложность создания и поддержки больших программ. Наличие сотен функций в таких проектах очень часто приводит к ошибкам и спагетти-коду.
  3. В основе объектно-ориентированного программирования лежит понятие объекта. Объект совмещает в себе и функции и данные.
  4. Основное преимущество ООП перед процедурным программированием — изоляция кода на уровне классов, что позволяет писать более простой и лаконичный код.

Two

Объекты и классы в ООП

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

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

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

  1. Класс описывает множество объектов, имеющих общую структуру и обладающих одинаковым поведением. Класс — это шаблон кода, по которому создаются объекты. Т. е. сам по себе класс ничего не делает, но с его помощью можно создать объект и уже его использовать в работе.
  2. Данные внутри класса делятся на свойства и методы. Свойства класса (они же поля) — это характеристики объекта класса.
  3. Методы класса — это функции, с помощью которых можно оперировать данными класса.
  4. Объект — это конкретный представитель класса.
  5. Объект класса и экземпляр класса — это одно и то же.

Грубо говоря
Класс = Свойства + Методы

  1. Цвет
  2. Объем двигателя
  3. Мощность
  4. Тип коробки передач
  1. Ехать
  2. Остановиться
  3. Заправиться
  4. Поставить на сигнализацию
  5. Включить дворники

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

Smartiqa Объектно-ориентированное программирование. Класс Автомобиль.

ООП. Класс Автомобиль.

Но в чем разница? Значения свойств будут различаться. Одна машина красная, другая — зеленая. У одной объем двигателя 1968 см3 и коробка-робот, а у другой — 1395 см3 и ездить владельцу придется на механике.

Вывод: Объекты класса на выходе похожие и одновременно разные. Различаются, как правило, свойства. Методы остаются одинаковыми.

  1. Свойства: Цвет=»Белый», Объем двигателя=»1984 см3″, Мощность=»180 л.с.», Тип коробки передач=»Робот»
  2. Методы: Ехать, Остановиться, Заправиться, Поставить на сигнализацию, Включить дворники

Three

Принципы ООП: абстракция, инкапсуляция, наследование, полиморфизм

Как мы уже сказали, на текущий момент ООП является самой востребованной и распространенной парадигмой программирования. Концепция ООП строится на основе 4 принципов, которые мы предлагаем вам кратко рассмотреть.

Принцип 1. Абстракция

Дадим определение:

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

  1. Выделить главные и наиболее значимые свойства предмета.
  2. Отбросить второстепенные характеристики.

Зачем нужна абстракция? Если мыслить масштабно — то она позволяет бороться со сложностью реального мира. Мы отбрасываем все лишнее, чтобы оно нам не мешало, и концентрируемся только на важных чертах объекта.

Smartiqa Объектно-ориентированное программирование. Абстракция.

ООП. Абстракция.

Принцип 2. Инкапсуляция

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

Инкапсуляция — принцип ООП, согласно которому сложность реализации программного компонента должна быть спрятана за его интерфейсом.

  1. Отсутствует доступ к внутреннему устройству программного компонента.
  2. Взаимодействие компонента с внешним миром осуществляется посредством интерфейса, который включает публичные методы и поля.
  1. Инкапсуляция упрощает процесс разработки, т. к. позволяет нам не вникать в тонкости реализации того или иного объекта.
  2. Повышается надежность программ за счет того, что при внесении изменений в один из компонентов, остальные части программы остаются неизменными.
  3. Становится более легким обмен компонентами между программами.

Smartiqa Объектно-ориентированное программирование. Инкапсуляция.

ООП. Инкапсуляция.

Принцип 3. Наследование

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

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

  1. Класс-потомок = Свойства и методы родителя + Собственные свойства и методы.
  2. Класс-потомок автоматически наследует от родительского класса все поля и методы.
  3. Класс-потомок может дополняться новыми свойствами.
  4. Класс-потомок может дополняться новыми методами, а также заменять(переопределять) унаследованные методы. Переопределить родительский метод — это как? Это значит, внутри класса потомка есть метод, который совпадает по названию с методом родительского класса, но функционал у него новый — соответствующий потребностям класса-потомка.
  1. Дает возможность использовать код повторно. Классы-потомки берут общий функционал у родительского класса.
  2. Способствует быстрой разработке нового ПО на основе уже существующих открытых классов.
  3. Наследование позволяет делать процесс написания кода более простым.

СВОЙСТВА

1) Тип фундамента
2) Материал крыши
3) Количество окон
4) Количество дверей
МЕТОДЫ

1) Построить
2) Отремонтировать
3) Заселить
4) Снести

А если мы захотим создать объект Частный дом? Данный объект по-прежнему будет являться домом, а значит будет обладать свойствами и методами класса Дом. Например, в нем так же есть окна и двери, и такой дом по-прежнему можно построить или отремонтировать. Однако при этом у него также будут собственные свойства и методы, ведь именно они отличают новый класс от его родителя. Новый класс Частный дом может выглядеть следующим образом:

СВОЙСТВА

1) Тип фундамента (УНАСЛЕДОВАНО)
2) Материал крыши (УНАСЛЕДОВАНО)
3) Количество окон (УНАСЛЕДОВАНО)
4) Количество дверей (УНАСЛЕДОВАНО)
5) Количество комнат
6) Тип отопления
7) Наличие огорода

МЕТОДЫ

1) Построить (УНАСЛЕДОВАНО)
2) Отремонтировать (УНАСЛЕДОВАНО)
3) Заселить (УНАСЛЕДОВАНО)
4) Снести (УНАСЛЕДОВАНО)
5) Изменить фасад
6) Утеплить
7) Сделать пристройку

С такой же легкостью мы можем создать еще один класс-потомок — Многоэтажный дом. Его свойства и методы могут выглядеть так:

СВОЙСТВА

1) Тип фундамента (УНАСЛЕДОВАНО)
2) Материал крыши (УНАСЛЕДОВАНО)
3) Количество окон (УНАСЛЕДОВАНО)
4) Количество дверей (УНАСЛЕДОВАНО)
5) Количество квартир
6) Количество подъездов
7) Наличие коммерческой недвижимости

МЕТОДЫ

1) Построить (УНАСЛЕДОВАНО)
2) Отремонтировать (УНАСЛЕДОВАНО)
3) Заселить (УНАСЛЕДОВАНО)
4) Снести (УНАСЛЕДОВАНО)
5) Выбрать управляющую компанию
6) Организовать собрание жильцов
7) Нанять дворника

Т. е. если подытожить, наследование позволяет нам использовать функционал уже существующих классов для создания новых. Просто представьте, что будет, если возможность наследования исчезнет. В этом случае пришлось бы копировать свойства и методы класса-родителя в класс-потомок. А если наследников 2? 5? 20? Придется копировать 20 раз. А что, если с течением времени вам потребуется внести изменение в код базового класса? Будете менять код во всех 20ти потомках? А что, если изменений тоже не одно, а например, 100? Думаю, что теперь вы убеждены, что наследование не зря является базовым принципом объектно-ориентированного программирования.

Python. ООП Наследование

Принцип 4. Полиморфизм

В данном случае глубоко вдаваться в подробности не будем(так как по-хорошему это тема для целой статьи), но суть данного принципа постараемся передать.

Полиморфизм — это поддержка нескольких реализаций на основе общего интерфейса.

Другими словами, полиморфизм позволяет перегружать одноименные методы родительского класса в классах-потомках.

Также для понимания работы этого принципа важным является понятие абстрактного метода:

Абстрактный метод (он же виртуальный метод) — это метод класса, реализация для которого отсутствует.

  1. В родительском классе(в нашем случае — класс Дом) создают пустой метод(например, метод Построить() ) и делают его абстрактным.
  2. В классах-потомках создают одноименные методы, но уже с соответствующей реализацией. И это логично, ведь например, процесс постройки Частного и Многоквартирного дома отличается кардинально. К примеру, для строительства Многоквартирного дома необходимо задействовать башенный кран, а Частный дом можно построить и собственными силами. При этом данный процесс все равно остается процессом строительства.
  3. В итоге получаем метод с одним и тем же именем, который встречается во всех классах. В родительском — имеем только интерфейс, реализация отсутствует. В классах-потомках — имеем и интерфейс и реализацию. Причем в отличие от родительского класса реализация в потомках уже становится обязательной.
  4. Теперь мы можем увидеть полиморфизм во всей его красе. Даже не зная, с объектом какого из классов-потомков мы работаем, нам достаточно просто вызвать метод Построить(). А уже в момент исполнения программы, когда класс объекта станет известен, будет вызвана необходимая реализация метода Построить().

Как итог — за одинаковым названием могут скрываться методы с совершенно разным функционалом, который в каждом конкретном случае соответствует нуждам класса, к которому он относится.

Smartiqa Полиморфизм

Four

Классы и объекты в Python

Теперь давайте посмотрим, как реализуется ООП в рамках языка программирования Python. Синтаксис для создания класса выглядит следующим образом:

 class :

А вот так компактно смотрится пример объявления класса с минимально возможным функционалом:

 class Car: pass 

Как мы видим, для задания класса используется инструкция class , далее следует имя класса Car и двоеточие. После них идет тело класса, которое в нашем случае представлено оператором pass . Данный оператор сам по себе ничего не делает — фактически это просто заглушка.

Чтобы создать объект класса, нужно воспользоваться следующим синтаксисом:

И в качестве примера создадим объект класса Car :

 car_object = Car() 

Five

Атрибуты класса в Python

Давайте договоримся, что атрибутом класса/объекта мы будем называть любой элемент класса/объекта (переменную, метод, подкласс), на который мы можем сослаться через символ точки. Т. е. вот так: MyClass. или my_object. .

  1. Встроенные(служебные) атрибуты
  2. Пользовательские атрибуты

Python. ООП Атрибуты класса

1. Встроенные атрибуты

Называть данную группу атрибутов встроенными — это своего рода условность, и сейчас мы объясним почему. Суть в том, что на самом деле все классы в Python (начиная с 3-й версии) имеют один общий родительский класс — object . Это значит, что когда вы создаете новый класс, вы неявно наследуете его от класса object , и потому свежесозданный класс наследует его атрибуты. Именно их мы и называем встроенными(служебными). Вот некоторые из них(заметьте, что в списке есть как поля, так и методы):

Атрибут;Назначение;Тип

__new__(cls[, . ]);Конструктор. Создает экземпляр(объект) класса. Сам класс передается в качестве аргумента.;Функция __init__(self[, . ]);Инициализатор. Принимает свежесозданный объект класса из конструктора.;Функция __del__(self);Деструктор. Вызывается при удалении объекта сборщиком мусора;Функция __str__(self);Возвращает строковое представление объекта.;Функция __hash__(self);Возвращает хэш-сумму объекта.;Функция __setattr__(self, attr, val);Создает новый атрибут для объекта класса с именем attr и значением val;Функция __doc__;Документация класса.;Строка (тип str) __dict__;Словарь, в котором хранится пространство имен класса;Словарь (тип dict)

Это важно
В теории ООП конструктор класса — это специальный блок инструкций, который вызывается при создании объекта. При работе с питоном может возникнуть мнение, что метод __init__(self) — это и есть конструктор, но это не совсем так. На самом деле, при создании объекта в Python вызывается метод __new__ (cls, *args, **kwargs) и именно он является конструктором класса.

Также обратите внимание, что __new__() — это метод класса, поэтому его первый параметр cls — ссылка на текущий класс. В свою очередь, метод __init__() является так называемым инициализатором класса. Именно этот метод первый принимает созданный конструктором объект. Как вы уже, наверное, не раз замечали, метод __init__() часто переопределяется внутри класса самим программистом. Это позволяет со всем удобством задавать параметры будущего объекта при его создании.

2. Пользовательские атрибуты

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

Список атрибутов класса / объекта можно получить с помощью команды dir() . Если взять самый простой класс:

 class Phone: pass 

То мы получим вот такой список атрибутов:

 ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__'] 

Как видим, в нем есть только встроенные атрибуты, которые наш класс по-умолчанию унаследовал от базового класса object . А теперь добавим ему функционала:

 class Phone: color = 'Grey' def turn_on(self): pass def call(self): pass 

И теперь посмотрим, как изменился список атрибутов класса:

 ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'color', 'call', 'turn_on'] 

Несложно заметить, что в конце списка добавились три новых пользовательских атрибута: переменная color и методы turn_on() и call() .

  1. Атрибутами называем совокупность полей и методов класса / объекта.
  2. Атрибуты делятся на встроенные и пользовательские.
  3. Все классы в Python имеют общий родительский класс — он называется object .
  4. Класс object предоставляет всем своим потомкам набор служебных атрибутов (как переменных (например, __dict__ и __doc__ ), так и методов (например, __str__ ) ).
  5. Многие из служебных атрибутов можно переопределить внутри своего класса.
  6. Поля и методы, которые описываются программистом в теле класса, являются пользовательскими и добавляются в общий список атрибутов наряду со встроенными атрибутами.

Six

Поля (свойства) класса в Python

  1. Статические поля
  2. Динамические поля

Python. ООП Поля класса

1. Статические поля (они же переменные или свойства класса)

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

 class Phone: # Статические поля (переменные класса) default_color = 'Grey' default_model = ‘C385’ def turn_on(self): pass def call(self): pass 

Вот так выглядит список атрибутов класса после его создания:

 ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'call', 'default_color', 'default_model', 'turn_on'] 

Кроме того, у нас есть возможность получить или изменить такое свойство, просто обратившись к самому классу по его имени(экземпляр класса при этом создавать не нужно).

Python — Интерактивный режим

 >>> Phone.default_color 'Grey' # Изменяем цвет телефона по умолчанию с серого на черный >>> Phone.default_color = 'Black' >>> Phone.default_color 'Black' 

2. Динамические поля (переменные или свойства экземпляра класса)

Это переменные, которые создаются на уровне экземпляра класса. Нет экземпляра — нет его переменных. Для создания динамического свойства необходимо обратиться к self внутри метода:

 class Phone: # Статические поля (переменные класса) default_color = 'Grey' default_model = 'C385' def __init__(self, color, model): # Динамические поля (переменные объекта) self.color = color self.model = model 

Python — Интерактивный режим

 # Создадим экземпляр класса Phone - телефон красного цвета модели 'I495’ >>> my_phone_red = Phone('Red', 'I495') # Полный список атрибутов созданного экземпляра: >>> dir(my_phone_red) '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'color', 'default_color', 'default_model', 'model'] # Прочитаем статические поля объекта >>> my_phone_red.default_color 'Grey' >>> my_phone_red.default_model 'C385' # Прочитаем динамические поля объекта >>> my_phone_red.color 'Red' >>> my_phone_red.model 'I495' # Создадим еще один экземпляр класса Phone - такой же телефон, но другого цвета >>> my_phone_blue = Phone('Blue', 'I495') # Прочитаем динамические поля объекта >>> my_phone_blue.color 'Blue' >>> my_phone_blue.model 'I495' 

Что такое self в Python?
Служебное слово self — это ссылка на текущий экземпляр класса. Как правило, эта ссылка передается в качестве первого параметра метода Python:

class Apple:

____# Создаем объект с общим количеством яблок 12
____def __init__(self):
________self.whole_amount = 12

____# Съедаем часть яблок для текущего объекта
____def eat(self, number):
________self.whole_amount -= number

Стоит обратить внимание, что на самом деле слово self не является зарезервированным. Просто существует некоторое соглашение, по которому первый параметр метода именуется self и передает ссылку на текущий объект, для которого этот метода был вызван. Хотите назвать первый параметр метода по-другому — пожалуйста.

В других языках программирования(например, Java или C++) аналогом этого ключа является служебное слово this .

Обратите внимание, что объект класса сочетает в себе как статические атрибуты(уровня класса), так и динамические(собственные — уровня объекта).

  1. Для создания статической переменной достаточно объявления класса, причем данная переменная создается непосредственно в теле класса.
  2. Динамические переменные создаются только в рамках работы c экземпляром класса.
  3. Чтоб создать переменную экземпляра, необходимо воспользоваться конструкцией self. внутри одного из методов.
  4. Экземпляр класса сочетает в себе совокупность как статических (уровня класса), так и динамических (уровня самого экземпляра) полей.
  5. Значения динамических переменных для разных объектов класса могут (и чаще всего так и делают) различаться.

Seven

Методы (функции) класса в Python

  1. Методы экземпляра класса (они же обычные методы)
  2. Статические методы
  3. Методы класса

Python. ООП Методы класса

1. Методы экземпляра класса (Обычные методы)

Это группа методов, которые становятся доступны только после создания экземпляра класса, то есть чтобы вызвать такой метод, надо обратиться к экземпляру. Как следствие — первым параметром такого метода является слово self . И как мы уже обсудили выше, с помощью данного параметра в метод передается ссылка на объект класса, для которого он был вызван. Теперь пример:

 class Phone: def __init__(self, color, model): self.color = color self.model = model # Обычный метод # Первый параметр метода - self def check_sim(self, mobile_operator): if self.model == 'I785' and mobile_operator == 'MTS': print('Your mobile operator is MTS') 

Python — Интерактивный режим

 # Импортируем наш класс для работы с ним >>> from phone import Phone # Создаем экземпляр класса >>> my_phone = Phone('red', 'I785') # Обращаемся к методу check_sim() через объект my_phone >>> my_phone.check_sim('MTS') Your mobile operator is MTS 

Стоит заметить, что, как правило, данная группа методов является самой многочисленной и часто используемой в сравнении со статическими методами и методами класса.

2. Статические методы

Статические методы — это обычные функции, которые помещены в класс для удобства и тем самым располагаются в области видимости этого класса. Чаще всего это какой-то вспомогательный код.

Важная особенность заключается в том, что данные методы можно вызывать посредством обращения к имени класса, создавать объект класса при этом не обязательно. Именно поэтому в таких методах не используется self — этому методу не важна информация об объектах класса.

Чтобы создать статический метод в Python, необходимо воспользоваться специальным декоратором — @staticmethod . Выглядит это следующим образом:

 class Phone: # Статический метод справочного характера # Возвращает хэш по номеру модели # self внутри метода отсутствует @staticmethod def model_hash(model): if model == 'I785': return 34565 elif model == 'K498': return 45567 else: return None # Обычный метод def check_sim(self, mobile_operator): pass 

Python — Интерактивный режим

 >>> from phone import Phone # Вызываем статический метод model_hash, просто обращаясь к имени класса # Объект класса Phone при этом создавать не надо >>> Phone.model_hash('I785') 34565 

3. Методы класса

Методы класса являются чем-то средним между обычными методами (привязаны к объекту) и статическими методами (привязаны только к области видимости). Как легко догадаться из названия, такие методы тесно связаны с классом, в котором они определены.

Обратите внимание, что такие методы могут менять состояние самого класса, что в свою очередь отражается на ВСЕХ экземплярах данного класса. Правда при этом менять конкретный объект класса они не могут (этим занимаются методы экземпляра класса).

Чтобы создать метод класса, необходимо воспользоваться соответствующим декоратором — @classmethod . При этом в качестве первого параметра такого метода передается служебное слово cls , которое в отличие от self является ссылкой на сам класс (а не на объект). Рассмотрим пример:

 class Phone: def __init__(self, color, model, os): self.color = color self.model = model self.os = os # Метод класса # Принимает 1) ссылку на класс Phone и 2) цвет в качестве параметров # Создает специфический объект класса Phone(особенность объекта в том, что это игрушечный телефон) # При этом вызывается инициализатор класса Phone # которому в качестве аргументов мы передаем цвет и модель, # соответствующую созданию игрушечного телефона @classmethod def toy_phone(cls, color): toy_phone = cls(color, 'ToyPhone', None) return toy_phone # Статический метод @staticmethod def model_hash(model): pass # Обычный метод def check_sim(self, mobile_operator): pass 

Python — Интерактивный режим

 >>> from phone import Phone # Создаем объект игрушечный телефон # Обращаемся к методу класса toy_phone через имя класса и точку >>> my_toy_phone = Phone.toy_phone('Red') >>> my_toy_phone
  1. Необходимо создать специфичный объект текущего класса
  2. Нужно реализовать фабричный паттерн — создаём объекты различных унаследованных классов прямо внутри метода

eight

Уровни доступа атрибутов в Python

Вам наверняка известно, что в классических языках программирования (таких как C++ и Java) доступ к ресурсам класса реализуется с помощью служебных слов public , private и protected :

  1. Private. Приватные члены класса недоступны извне — с ними можно работать только внутри класса.
  2. Public. Публичные методы наоборот — открыты для работы снаружи и, как правило, объявляются публичными сразу по-умолчанию.
  3. Protected. Доступ к защищенным ресурсам класса возможен только внутри этого класса и также внутри унаследованных от него классов (иными словами, внутри классов-потомков). Больше никто доступа к ним не имеет
  1. Если переменная/метод начинается с одного нижнего подчеркивания ( _protected_example ), то она/он считается защищенным ( protected ).
  2. Если переменная/метод начинается с двух нижних подчеркиваний ( __private_example ), то она/он считается приватным ( private ).

Python. ООП Уровни доступа атрибутов

Все члены класса в Python являются публичными по умолчанию. Любой член класса может быть доступен за пределами самого класса. Вот так выглядит создание и работа с публичными ( public ) методами в Python:

 class Phone: def __init__(self, color): # Объявляем публичное поле color self.color = color 
 >>> from phone import Phone # Создаем экземпляр класса Phone >>> phone = Phone('Grey') # Обращаемся к свойству color >>> phone.color 'Grey' # Изменяем свойство color >>> phone.color = 'Red' >>> phone.color 'Red' 

Как видите, никаких проблем. Идем дальше. Как мы уже сказали, в соотвествии с соглашением чтобы сделать атрибут класса защищенным ( protected ), необходимо добавить к имени символ подчеркивания _ . Как, например, здесь:

 class Phone: def __init__(self, color): # Объявляем защищенное поле _color self._color = color 

Однако по факту в Python такой атрибут все равно будет доступен снаружи класса. Вы все еще можете выполнить операции, которые мы рассмотрели выше:

 # Создаем экземпляр класса Phone >>> phone = Phone('Grey') # Обращаемся к защищенному свойству _color >>> phone._color 'Grey' # Изменяем защищенное свойство _color >>> phone._color = 'Red' >>> phone._color 'Red' 

Иными словами, это больше вопрос ответственности программиста — он не должен работать с атрибутами, имена которых начинаются с нижнего подчёркивания _ , снаружи класса.

Аналогично, два нижних подчеркивания __ в названии свойства/метода делают его приватным ( private ). Здесь уже интереснее — получить доступ к таким атрибутам напрямую нельзя (но если очень хочется, то все равно можно — об этом чуть ниже):

 class Phone: def __init__(self, color): # Объявляем приватное поле __color self.__color = color 
 >>> from phone import Phone >>> phone = Phone('Grey') # Пытаемся обратиться к приватному свойству и получаем ошибку >>> phone.__color Traceback (most recent call last): File "", line 1, in AttributeError: 'Phone' object has no attribute '__color' 

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

 # Получаем список атрибутов класса, в котором находим новое имя свойства: '_Phone__color' >>> dir(phone) ['_Phone__color', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__'] # Обращаемся к защищенному свойству по новому имени >>> phone._Phone__color 'Grey' # Меняем значение защищенного свойства >>> phone._Phone__color = 'Blue' >>> phone._Phone__color 'Blue' 
  1. Существует три уровня доступа к свойствам/методам класса: public, protected, private
  2. Физически данный механизм ограничения доступа к атрибутам класса в Python реализован слабо, что от части может противоречить одному из главных принципов ООП — инкапсуляции.
  3. Однако существует некоторое соглашение, по которому в Python задать уровень доступа к свойству/методу класса можно с помощью добавления к имени одного ( protected ) или двух ( private ) подчеркиваний. Ответственность за соблюдение данного соглашения ложится на плечи программистов.

eight

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

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

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

 class (): 

Теперь давайте рассмотрим пример применения механизма наследования в действии. Перед нами класс Phone (Телефон), у которого есть одно свойство is_on и три метода:

 # Родительский класс class Phone: # Инициализатор def __init__(self): self.is_on = False # Включаем телефон def turn_on(self): self.is_on = True # Если телефон включен, делаем звонок def call(self): if self.is_on: print('Making call. ') 

В результате объект такого класса получит следующий набор атрибутов:

 >>> my_phone = Phone() >>> dir(my_phone) ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'call', 'is_on', 'turn_on'] 

Среди данной совокупности атрибутов нас больше всего интересуют пользовательские свойства и методы: ‘__init__’ , ‘call’ , ‘is_on’ , ‘turn_on’

А теперь предположим, что мы захотели создать новый класс — MobilePhone (Мобильный телефон). Хоть этот класс и новый, но это по-прежнему телефон, а значить — его все так же можно включить и по нему можно позвонить. А раз так, то нам нет смысла реализовывать этот функционал заново, а можно просто унаследовать его от класса Phone . Выглядит это так:

 class Phone: def __init__(self): self.is_on = False def turn_on(self): self.is_on = True def call(self): if self.is_on: print('Making call. ') # Унаследованный класс class MobilePhone(Phone): # Добавляем новое свойство battery def __init__(self): super().__init__() self.battery = 0 # Заряжаем телефон на величину переданного значения def charge(self, num): self.battery = num print(f'Charging battery up to . %') 

Как вы видите, в новом классе добавились свойство battery и метод charge() . При этом мы помним, что это класс-потомок Phone , а значит от унаследовал и его функционал тоже. Создадим объект нового класса и посмотрим список его атрибутов:

 >>> my_mobile_phone = MobilePhone() >>> dir(my_mobile_phone) ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'battery', 'call', 'charge', 'is_on', 'turn_on'] 

Теперь мы видим, что пользовательские атрибуты состоят из унаследованных ( ‘is_on’ , ‘call’ , ‘turn_on’ ) и новых ( ‘__init__’ , ‘battery’ , ‘charge’ ). Все они теперь принадлежат классу MobilePhone . Пример использования:

 # Импортируем оба класса >>> from phone import Phone, MobilePhone # Создаем объект класса MobilePhone >>> my_mobile_phone = MobilePhone() # Включаем телефон и делаем звонок >>> my_mobile_phone.turn_on() >>> my_mobile_phone.call() Making call. # Заряжаем мобильный телефон >>> my_mobile_phone.charge(76) Charging battery up to . 76% 

Что такое super?
Как вы могли заметить, в инициализаторе (метод __init__ ) наследуемого класса вызывается метод super() . Что это за метод и зачем он нужен?

Главная задача этого метода — дать возможность наследнику обратиться к родительскому классу. В классе родителе Phone есть свой инициализатор, и когда в потомке MobilePhone мы так же создаем инициализатор (а он нам действительно нужен, так как внутри него мы хотим объявить новое свойство) — мы его перегружаем. Иными словами, мы заменяем родительский метод __init__() собственным одноименным методом. Это чревато тем, что родительский метод просто в принципе не будет вызван, и мы потеряем его функционал в классе наследнике. В конкретном случае, потеряем свойство is_on .

  1. Внутри инициализатора класса-наследника вызвать инициализатор родителя (для этого вызываем метод super().__init__() )
  2. А затем просто добавить новый функционал

def __init__(self):
____super().__init__()
____self.battery = 0

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

eight

Полиморфизм в Python

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

 # Родительский класс class Phone: def __init__(self): self.is_on = False def turn_on(self): pass def call(self): pass # Метод, который выводит короткую сводку по классу Phone def info(self): print(f'Class name: ') print(f'If phone is ON: ') # Унаследованный класс class MobilePhone(Phone): def __init__(self): super().__init__() self.battery = 0 # Такой же метод, который выводит короткую сводку по классу MobilePhone # Обратите внимание, что названия у методов совпадают - оба метода называются info() # Однако их содержимое различается def info(self): print(f'Class name: ') print(f'If mobile phone is ON: ') print(f'Battery level: ') # Демонстрационная функция # Создаем список из классов # В цикле перебираем список и для каждого элемента списка(а элемент - это класс) # Создаем объект и вызываем метод info() # Главная особенность: запись object.info() не дает информацию об объекте, для которого будет вызван метод info() # Это может быть объект класса Phone, а может - объект класса MobilePhone # И только в момент исполнения кода становится ясно, для какого именно объекта нужно вызывать метод info() def show_polymorphism(): for item in [Phone, MobilePhone]: print('-------') object = item() object.info() 

В классе наследнике определен статический метод такой же как в родительском

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

Замечание: Конструкторы, определённые в классах-родителях, не вызываются автоматически, если дочерний класс определяет собственный конструктор. Чтобы вызвать конструктор, объявленный в родительском классе, требуется вызвать parent::__construct() внутри конструктора дочернего класса. Если в дочернем классе не определён конструктор, то он может быть унаследован от родительского класса как обычный метод (если он не был определён как приватный).

Пример #1 Конструкторы при наследовании

class BaseClass function __construct () print «Конструктор класса BaseClass\n» ;
>
>

class SubClass extends BaseClass function __construct () parent :: __construct ();
print «Конструктор класса SubClass\n» ;
>
>

class OtherSubClass extends BaseClass // наследует конструктор BaseClass
>

// Конструктор класса BaseClass
$obj = new BaseClass ();

// Конструктор класса BaseClass
// Конструктор класса SubClass
$obj = new SubClass ();

// Конструктор класса BaseClass
$obj = new OtherSubClass ();
?>

В отличие от других методов, __construct() освобождается от обычных правил совместимости сигнатуры при наследовании.

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

Пример #2 Использование аргументов в конструкторах

class Point protected int $x ;
protected int $y ;

public function __construct ( int $x , int $y = 0 ) $this -> x = $x ;
$this -> y = $y ;
>
>

// Передаём оба параметра.
$p1 = new Point ( 4 , 5 );
// Передаём только обязательные параметры. Для $y используется значение по умолчанию 0.
$p2 = new Point ( 4 );
// Вызываем с именованными параметрами (начиная с PHP 8.0):
$p3 = new Point ( y : 5 , x : 4 );
?>

Если у класса нет конструктора, или его конструктор не имеет обязательных параметров, скобки после имени класса можно не писать.

Конструкторы в старом стиле

До PHP 8.0.0, классы в глобальном пространстве имён будут интерпретировать метод, названный так же, как класс, как конструктор старого стиля. Этот синтаксис считается устаревшим и будет вызывать ошибку уровня E_DEPRECATED , но всё равно эти методы будут вызываться в качестве конструктора. Если в классе присутствуют и __construct(), и метод с именем класса, то в качестве конструктора будет вызван __construct().

Для классов, находящихся в собственном пространстве имён и для всех классов, начиная с PHP 8.0.0, метод, названный по имени класса, будет игнорироваться.

В новом коде всегда используйте __construct().

Определение свойств объекта в конструкторе

Начиная с PHP 8.0.0, параметры конструктора можно использовать для задания соответствующих свойств объекта. Это довольно распространённая практика — присваивать свойствам объекта параметры, переданные в конструктор, не производя никаких дополнительных преобразований. Определение свойств класса в конструкторе позволяет значительно сократить количество шаблонного кода для такого случая. Пример выше можно будет переписать следующим образом:

Пример #3 Использование определения свойств в конструкторе

class Point public function __construct (protected int $x , protected int $y = 0 ) >
>

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

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

Замечание:

Свойства объектов не могут быть типа callable в связи с неоднозначностью которую они представляют для движка PHP. Соответственно и свойства определяемые в конструкторе также не могут быть типа callable . Любые другие декларации типов допустимы.

Замечание:

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

New в инициализации класса

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

Замечание:

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

Пример #4 Пример использования new в инициализации класса

// Всё допустимо:
static $x = new Foo ;
const C = new Foo ;

function test ( $param = new Foo ) <>

#[ AnAttribute (new Foo )]
class Test public function __construct (
public $prop = new Foo ,
) <>
>
// Всё не допустимо (ошибка во времени компиляции):
function test (
$a = new ( CLASS_NAME_CONSTANT )(), // динамическое имя класса
$b = new class <>, // анонимный класс
$c = new A (. []), // распаковка аргументов
$d = new B ( $abc ), // неподдерживаемое постоянное выражение
) <>
?>

Статические методы создания объекта

PHP поддерживает только один конструктор для класса. Однако в некоторых случаях есть необходимость создавать объект разными путями в зависимости от разных входных данных. Рекомендуемый способ — использовать статические методы как обёртки над конструктором.

Пример #5 Использование статических методов для создания объектов

private ? int $id ;
private ? string $name ;

private function __construct (? int $id = null , ? string $name = null ) $this -> id = $id ;
$this -> name = $name ;
>

public static function fromBasicData ( int $id , string $name ): static $new = new static( $id , $name );
return $new ;
>

public static function fromJson ( string $json ): static $data = json_decode ( $json );
return new static( $data [ ‘id’ ], $data [ ‘name’ ]);
>

public static function fromXml ( string $xml ): static // Пользовательская логика.
$data = convert_xml_to_array ( $xml );
$new = new static();
$new -> id = $data [ ‘id’ ];
$new -> name = $data [ ‘name’ ];
return $new ;
>
>

$p1 = Product :: fromBasicData ( 5 , ‘Widget’ );
$p2 = Product :: fromJson ( $some_json_string );
$p3 = Product :: fromXml ( $some_xml_string );

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

В примере выше три публичных статических метода демонстрируют различные способы создания экземпляра объекта.

  • fromBasicData() принимает явные параметры, создаёт экземпляр класса через конструктор и возвращает объект.
  • fromJson() принимает JSON строку, производит над ней некоторые преобразования, извлекает данные необходимые для создания объекта и, так же как и предыдущий метод, вызывает конструктор и возвращает созданный объект.
  • fromXml() принимает XML строку, извлекает нужные данные и, так как в конструкторе нет обязательных параметров, вызывает его без них. После этого, так как ему доступны скрытые свойства, он присваивает им значения напрямую. После чего возвращает готовый объект.

Во всех трёх случаях, ключевое слово static транслируется в имя класса, в котором этот код вызывается. В нашем случае Product .

Деструкторы

__destruct (): void

PHP предоставляет концепцию деструктора, аналогичную с той, которая применяется в других ОО-языках, таких как C++. Деструктор будет вызван при освобождении всех ссылок на определённый объект или при завершении скрипта (порядок выполнения деструкторов не гарантируется).

Пример #6 Пример использования деструктора

class MyDestructableClass
function __construct () print «Конструктор\n» ;
>

function __destruct () print «Уничтожается » . __CLASS__ . «\n» ;
>
>

$obj = new MyDestructableClass ();

Как и в случае с конструкторами, деструкторы, объявленные в родительском классе, не будут вызываться автоматически. Для вызова деструктора родительского класса, требуется вызвать parent::__destruct() в теле деструктора дочернего класса. Подобно конструкторам, дочерний класс может унаследовать деструктор из родительского класса, если он не определён в нем.

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

Замечание:

Деструкторы, вызываемые при завершении скрипта, вызываются после отправки HTTP-заголовков. Рабочая директория во время фазы завершения скрипта может отличаться в некоторых SAPI (например, в Apache).

Замечание:

Попытка выбросить исключение из деструктора (вызываемого во время завершения скрипта) вызывает фатальную ошибку.

User Contributed Notes 15 notes

12 years ago

Be aware of potential memory leaks caused by circular references within objects. The PHP manual states «[t]he destructor method will be called as soon as all references to a particular object are removed» and this is precisely true: if two objects reference each other (or even if one object has a field that points to itself as in $this->foo = $this) then this reference will prevent the destructor being called even when there are no other references to the object at all. The programmer can no longer access the objects, but they still stay in memory.

Consider the following example:

header ( «Content-type: text/plain» );

/**
* An indentifier
* @var string
*/
private $name ;
/**
* A reference to another Foo object
* @var Foo
*/
private $link ;

public function __construct ( $name ) $this -> name = $name ;
>

public function setLink ( Foo $link ) $this -> link = $link ;
>

public function __destruct () echo ‘Destroying: ‘ , $this -> name , PHP_EOL ;
>
>

// create two Foo objects:
$foo = new Foo ( ‘Foo 1’ );
$bar = new Foo ( ‘Foo 2’ );

// make them point to each other
$foo -> setLink ( $bar );
$bar -> setLink ( $foo );

// destroy the global references to them
$foo = null ;
$bar = null ;

// we now have no way to access Foo 1 or Foo 2, so they OUGHT to be __destruct()ed
// but they are not, so we get a memory leak as they are still in memory.
//
// Uncomment the next line to see the difference when explicitly calling the GC:
// gc_collect_cycles();
//
// see also: http://www.php.net/manual/en/features.gc.php
//

// create two more Foo objects, but DO NOT set their internal Foo references
// so nothing except the vars $foo and $bar point to them:
$foo = new Foo ( ‘Foo 3’ );
$bar = new Foo ( ‘Foo 4’ );

// destroy the global references to them
$foo = null ;
$bar = null ;

// we now have no way to access Foo 3 or Foo 4 and as there are no more references
// to them anywhere, their __destruct() methods are automatically called here,
// BEFORE the next line is executed:

echo ‘End of script’ , PHP_EOL ;

Destroying: Foo 3
Destroying: Foo 4
End of script
Destroying: Foo 1
Destroying: Foo 2

But if we uncomment the gc_collect_cycles(); function call in the middle of the script, we get:

Destroying: Foo 2
Destroying: Foo 1
Destroying: Foo 3
Destroying: Foo 4
End of script

As may be desired.

NOTE: calling gc_collect_cycles() does have a speed overhead, so only use it if you feel you need to.

1 year ago

* I can’t edit my previous note to elaborate on modifiers. Please excuse me.*

If both parent and child classes have a method with the same name defined, and it is called in parent’s constructor, using `parent::__construct()` will call the method in the child.

class A public function __construct () $this -> method ();
>
public function method () echo ‘A’ . PHP_EOL ;
>
>
class B extends A public function __construct () parent :: __construct ();
>
>
class C extends A public function __construct () parent :: __construct ();
>
public function method () echo ‘C’ . PHP_EOL ;
>
>
$b = new B ; // A
$c = new C ; // C

?>

In this example both A::method and C::method are public.

You may change A::method to protected, and C::method to protected or public and it will still work the same.

If however you set A::method as private, it doesn’t matter whether C::method is private, protected or public. Both $b and $c will echo ‘A’.

6 years ago

The __destruct magic method must be public.

public function __destruct()
;
>

The method will automatically be called externally to the instance. Declaring __destruct as protected or private will result in a warning and the magic method will not be called.

Note: In PHP 5.3.10 i saw strange side effects while some Destructors were declared as protected.

15 years ago

It’s always the easy things that get you —

Being new to OOP, it took me quite a while to figure out that there are TWO underscores in front of the word __construct.

It is __construct
Not _construct

Extremely obvious once you figure it out, but it can be sooo frustrating until you do.

I spent quite a bit of needless time debugging working code.

I even thought about it a few times, thinking it looked a little long in the examples, but at the time that just seemed silly(always thinking «oh somebody would have made that clear if it weren’t just a regular underscore. «)

All the manuals I looked at, all the tuturials I read, all the examples I browsed through — not once did anybody mention this!

(please don’t tell me it’s explained somewhere on this page and I just missed it, you’ll only add to my pain.)

I hope this helps somebody else!

3 years ago

To better understand the __destrust method:

class A protected $id;

public function __construct($id)
$this->id = $id;
echo «construct id>\n»;
>

public function __destruct()
echo «destruct id>\n»;
>
>

The output content:

16 years ago

i have written a quick example about the order of destructors and shutdown functions in php 5.2.1:

class destruction var $name ;

function destruction ( $name ) $this -> name = $name ;
register_shutdown_function (array(& $this , «shutdown» ));
>

function shutdown () echo ‘shutdown: ‘ . $this -> name . «\n» ;
>

function __destruct () echo ‘destruct: ‘ . $this -> name . «\n» ;
>
>

$a = new destruction ( ‘a: global 1’ );

function test () $b = new destruction ( ‘b: func 1’ );
$c = new destruction ( ‘c: func 2’ );
>
test ();

$d = new destruction ( ‘d: global 2’ );

?>

this will output:
shutdown: a: global 1
shutdown: b: func 1
shutdown: c: func 2
shutdown: d: global 2
destruct: b: func 1
destruct: c: func 2
destruct: d: global 2
destruct: a: global 1

conclusions:
destructors are always called on script end.
destructors are called in order of their «context»: first functions, then global objects
objects in function context are deleted in order as they are set (older objects first).
objects in global context are deleted in reverse order (older objects last)

shutdown functions are called before the destructors.
shutdown functions are called in there «register» order. 😉

10 years ago

/**
* a funny example Mobile class
*
* @author Yousef Ismaeil Cliprz[At]gmail[Dot]com
*/

/**
* Some device properties
*
* @var string
* @access public
*/
public $deviceName , $deviceVersion , $deviceColor ;

/**
* Set some values for Mobile::properties
*
* @param string device name
* @param string device version
* @param string device color
*/
public function __construct ( $name , $version , $color ) $this -> deviceName = $name ;
$this -> deviceVersion = $version ;
$this -> deviceColor = $color ;
echo «The » . __CLASS__ . » class is stratup.

» ;
>

/**
* Some Output
*
* @access public
*/
public function printOut () echo ‘I have a ‘ . $this -> deviceName
. ‘ version ‘ . $this -> deviceVersion
. ‘ my device color is : ‘ . $this -> deviceColor ;
>

/**
* Umm only for example we will remove Mobile::$deviceName Hum not unset only to check how __destruct working
*
* @access public
*/
public function __destruct () $this -> deviceName = ‘Removed’ ;
echo ‘

Dumpping Mobile::deviceName to make sure its removed, Olay :’ ;
var_dump ( $this -> deviceName );
echo «
The » . __CLASS__ . » class is shutdown.» ;
>

// Oh ya instance
$mob = new Mobile ( ‘iPhone’ , ‘5’ , ‘Black’ );

// print output
$mob -> printOut ();

?>

The Mobile class is stratup.

I have a iPhone version 5 my device color is : Black

Dumpping Mobile::deviceName to make sure its removed, Olay :
string ‘Removed’ (length=7)

The Mobile class is shutdown.

11 years ago

As of PHP 5.3.10 destructors are not run on shutdown caused by fatal errors.

For example:
class Logger
protected $rows = array();

public function __destruct ()
$this -> save ();
>

public function log ( $row )
$this -> rows [] = $row ;
>

    ‘ ;
    foreach ( $this -> rows as $row )
    echo ‘
  • ‘ , $row , » ;
    >
    echo ‘

$logger = new Logger ;
$logger -> log ( ‘Before’ );

$logger -> log ( ‘After’ );
?>

Without the $nonset->foo(); line, Before and After will both be printed, but with the line neither will be printed.

One can however register the destructor or another method as a shutdown function:
class Logger
protected $rows = array();

public function __construct ()
register_shutdown_function (array( $this , ‘__destruct’ ));
>

public function __destruct ()
$this -> save ();
>

public function log ( $row )
$this -> rows [] = $row ;
>

    ‘ ;
    foreach ( $this -> rows as $row )
    echo ‘
  • ‘ , $row , » ;
    >
    echo ‘

$logger = new Logger ;
$logger -> log ( ‘Before’ );

$logger -> log ( ‘After’ );
?>
Now Before will be printed, but not After, so you can see that a shutdown occurred after Before.

13 years ago

Please be aware of when using __destruct() in which you are unsetting variables.

Consider the following code:
class my_class public $error_reporting = false ;

function __construct ( $error_reporting = false ) $this -> error_reporting = $error_reporting ;
>

function __destruct () if( $this -> error_reporting === true ) $this -> show_report ();
unset( $this -> error_reporting );
>
?>

The above will result in an error:
Notice: Undefined property: my_class::$error_reporting in my_class.php on line 10

It appears as though the variable will be unset BEFORE it actually can execute the if statement. Removing the unset will fix this. It’s not needed anyways as PHP will release everything anyways, but just in case you run across this, you know why 😉

2 months ago

There are other advantages to using static factory methods to wrap object construction instead of bare constructor calls.

As well as allowing for different methods to use in different scenarios, with more relevant names both for the methods and the parameters and without the constructor having to handle different sets of arguments of different types:

* You can do all your input validation before attempting to construct the object.
* The object itself can bypass that input validation when constructing new instances of its own class, since you can ensure that it knows what it’s doing.
* With input validation/preprocessing moved to the factory methods, the constructor itself can often be reduced to «set these properties to these arguments», meaning the constructor promotion syntax becomes more useful.
* Having been hidden away from users, the constructor’s signature can be a bit uglier without becoming a pain for them. Heh.
* Static methods can be lifted and passed around as first class closures, to be called in the normal fashion wherever functions can be called, without the special «new» syntax.
* The factory method need not return a new instance of that exact class. It could return a pre-existing instance that would do the same job as the new one would (especially useful in the case of immutable «value type» objects by reducing duplication); or a simpler or more specific subclass to do the job with less overhead than a more generic instance of the original class. Returning a subclass means LSP still holds.

17 years ago

Peter has suggested using static methods to compensate for unavailability of multiple constructors in PHP. This works fine for most purposes, but if you have a class hierarchy and want to delegate parts of initialization to the parent class, you can no longer use this scheme. It is because unlike constructors, in a static method you need to do the instantiation yourself. So if you call the parent static method, you will get an object of parent type which you can’t continue to initialize with derived class fields.

Imagine you have an Employee class and a derived HourlyEmployee class and you want to be able to construct these objects out of some XML input too.

class Employee public function __construct ( $inName ) $this -> name = $inName ;
>

public static function constructFromDom ( $inDom )
$name = $inDom -> name ;
return new Employee ( $name );
>

class HourlyEmployee extends Employee public function __construct ( $inName , $inHourlyRate ) parent :: __construct ( $inName );
$this -> hourlyRate = $inHourlyRate ;
>

public static function constructFromDom ( $inDom )
// can’t call parent::constructFromDom($inDom)
// need to do all the work here again
$name = $inDom -> name ; // increased coupling
$hourlyRate = $inDom -> hourlyrate ;
return new EmployeeHourly ( $name , $hourlyRate );
>

private $hourlyRate ;
>
?>

The only solution is to merge the two constructors in one by adding an optional $inDom parameter to every constructor.

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

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