Содержание
Введение в объектно-ориентированное программирование (ООП) на Python
Объектно-ориентированное программирование – это метод структурирования программ путем объединения связанных свойств и методов в отдельные объекты. В этом руководстве мы познакомимся с основами объектно-ориентированного программирования на языке Python. Материал будет полезен абсолютным новичкам в ООП на Python. Чтобы проверить свои знания в Python, вы можете пройти наш тест на знание языка.
Текст публикации представляет собой незначительно сокращенный перевод статьи Дэвида Амоса Object-Oriented Programming (OOP) in Python 3.
Интерактив
Текст адаптирован в виде блокнота Jupyter, который можно запустить в интерактивном виде онлайн в среде Colab. Другие адаптированные таким образом тексты доступны в GitHub-репозитории.
Объектно-ориентированное программирование (ООП) – это парадигма программирования, которая предоставляет средства структурирования программ таким образом, чтобы их свойства и поведение были объединены в отдельные объекты.
Например, объект может представлять человека свойствами «имя», «возраст», «адрес» и методами (поведением) «ходьба», «разговор», «дыхание» и «бег». Или электронное письмо описывается свойствами «список получателей», «тема» и «текст», а также методами «добавление вложений» и «отправка».
Иными словами, объектно-ориентированное программирование – это подход для моделирования вещей, а также отношений между вещами. ООП моделирует сущности реального мира в виде программных объектов, с которыми связаны некоторые данные и которые могут выполнять определенные функции.
Другой распространенной парадигмой программирования является процедурное программирование, которое структурирует программу подобно рецепту. Такая программа предоставляет набор шагов в виде функций и блоков кода, которые последовательно выполняются для выполнения задачи.
Примитивные структуры данных, такие как числа, строки и списки, предназначены для представления простых фрагментов информации: стоимость яблока, название стихотворения, список любимых цветов. Но бывает, что информация имеет более сложную структуру.
Допустим, вы хотите отслеживать работу сотрудников. Необходимо хранить основную информацию о каждом сотруднике: Ф.И.О., возраст, должность, год начала работы. Один из способов это сделать – представить каждого сотрудника в виде списка:
kirk = ["James Kirk", 34, "Captain", 2265] spock = ["Spock", 35, "Science Officer", 2254] mccoy = ["Leonard McCoy", "Chief Medical Officer", 2266]
У этого подхода есть ряд проблем.
Во-первых, ухудшается читаемость кода. Чтобы понять, что kirk[0]
ссылается на имя сотрудника, нужно перемотать код к объявлению списка.
Во-вторых, возрастает вероятность ошибки. В приведенном коде в списке mccoy
не указан возраст, поэтому mccoy[1]
вместо возраста вернет "Chief Medical Officer"
.
Отличный способ сделать такой тип кода более удобным – использовать классы.
Классы и экземпляры
Итак, для создания пользовательских структур данных используются классы. Классы определяют функции, называемые методами класса. Методы описывают поведение – те действия, которые объект, созданный с помощью класса, может выполнять с данными.
В этом туториале в качестве примера мы создадим класс Dog
, который будет хранить информацию о характеристиках собак.
Нужно понимать, что класс – это только план того, как что-то должно быть определено. Сам класс не содержит никаких данных. Класс Dog
указывает, что для описания собаки необходимы кличка и возраст, но он не содержит ни клички, ни возраста какой-либо конкретной собаки.
Если класс является планом, то экземпляр – это объект, который построен по этому плану. Он содержит реальные данные, это настоящая собака. Например, 🐕 Майлз, которому недавно исполнилось четыре года.
Другая аналогия: класс – это бланк анкеты. Экземпляр – анкета, которую заполнили 📝. Подобно тому как люди заполняют одну и ту же форму своей уникальной информацией, так из одного класса может быть создано множество экземпляров. Обычно бланк анкеты сам по себе не нужен, он используется лишь для удобства оформления информации.
Как определить класс
Все определения классов начинаются с ключевого слова class
, за которым следует имя класса и двоеточие. Весь следующий после двоеточия код составляет тело класса:
class Dog: pass
Здесь тело класса Dog
пока состоит из одного оператора – ключевого слова-заполнителя pass
. Заполнитель позволяет запустить этот код без вызова исключений.
Примечание
Имена классов Python принято записывать в нотации CamelCase
.
Определим свойства, которые должны иметь все объекты Dog
. Для простоты будем описывать собак с помощью клички и возраста.
Свойства, которые должны иметь все объекты класса Dog
, определяются в специальном методе с именем __init__()
. Каждый раз, когда создается новый объект Dog
, __init __()
присваивает свойствам объекта значения. То есть __init__()
инициализирует каждый новый экземпляр класса.
Методу __init__()
можно передать любое количество параметров, но первым параметром всегда является автоматически создаваемая переменная с именем self
. Переменная self
ссылается на только что созданный экземпляр класса, за счет чего метод __init__()
сразу может определить новые атрибуты.
Обновим класс Dog
с помощью метода __init__()
, который создает атрибуты name
и age
:
class Dog: def __init__(self, name, age): self.name = name self.age = age
В теле __init__()
две инструкции, задействующие переменную self
:
self.name = name
создает атрибут с именемname
и присваивает ему значение параметраname
.self.age
=age
создает атрибутage
и присваивает ему значение параметраage
.
Атрибуты, созданные в __init__()
называются атрибутами экземпляра. Значение атрибута экземпляра зависит от конкретного экземпляра класса. Все объекты Dog
имеют имя и возраст, но значения атрибутов name
и age
будут различаться в зависимости от экземпляра Dog.
С другой стороны, можно создать атрибуты класса – атрибуты, которые имеют одинаковое значение для всех экземпляров класса. Вы можете определить атрибут класса, присвоив значение имени переменной вне __init__()
:
class Dog: # Атрибут класса species = "Canis familiaris" def __init__(self, name, age): self.name = name self.age = age
Атрибуты класса определяются после имени класса. Им всегда должно быть присвоено начальное значение. Используйте атрибуты класса для определения свойств, которые должны иметь одинаковое значение для каждого экземпляра класса, а атрибуты экземпляров – для тех данных, которые отличают один экземпляр от другого.
Теперь, когда у нас есть класс Dog
, создадим нескольких собак! 🐶
Временно воспользуемся простейшим описанием класса, с которого мы начали:
class Dog: pass
Создание нового экземпляра класса похоже на вызов функции:
>>> Dog() <__main__. Dog at 0x7f6854738150>
В памяти компьютера по указанному после at
адресу был создан новый объект типа __main__.Dog
.
Важно, что следующий экземпляр Dog
будет создан уже по другому адресу. Это совершенно новый экземпляр и он полностью уникален:
>>> Dog() <__main__.Dog at 0x7f6854625cd0>
>>> a = Dog() >>> b = Dog() >>> a == b False
Хотя a
и b
являются экземплярами класса Dog, они представляют собой два разных объекта.
Теперь возьмем последнюю рассмотренную нами структуру класса:
class Dog: species = "Canis familiaris" def __init__(self, name, age): self.name = name self.age = age
Для создания экземпляров объектов класса необходимо указать кличку и возраст собаки. Если мы этого не сделаем, то Python вызовет исключение TypeError
:
>>> Dog() [...] TypeError: __init__() missing 2 required positional arguments: 'name' and 'age'
Чтобы передать аргументы, помещаем значения в скобки после имени класса:
buddy = Dog("Buddy", 9) miles = Dog("Miles", 4)
Но ведь в описании класса __init__()
перечислены три параметра – почему в этом примере передаются только два аргумента?
При создании экземпляра Python сам передает новый экземпляр в виде параметра self
в метод __init__()
. Так что нам нужно беспокоиться только об аргументах name
и age
.
После того как экземпляры созданы, записанные данные доступны в виде атрибутов экземпляра:
>>> buddy. name 'Buddy' >>> buddy.age 9 >>> miles.name 'Miles' >>> miles.age 4 >>> buddy.species 'Canis familiaris' >>> miles.species 'Canis familiaris'
Одним из важных преимуществ использования классов для организации данных является то, что экземпляры гарантированно имеют ожидаемые атрибуты. У всех экземпляров Dog гарантировано есть атрибуты species
, name
и age
.
Значения атрибутов могут изменяться динамически:
>>> buddy.age = 10 >>> buddy.age 10 >>> miles.species = "Felis silvestris" >>> miles.species 'Felis silvestris'
Экземпляры не зависят друг от друга. Изменение атрибута класса у одного экземпляра не меняет его у остальных экземпляров:
>>> buddy.species 'Canis familiaris'
Методы экземпляра – это определенные внутри класса функции, которые могут вызываться из экземпляра этого класса. Так же, как и у метода __init__()
, первым параметром метода экземпляра всегда является self
:
class Dog: species = "Canis familiaris" def __init__(self, name, age): self.name = name self.age = age # Метод экземпляра def description(self): return f"{self.name} is {self.age} years old" # Другой метод экземпляра def speak(self, sound): return f"{self.name} says {sound}"
Мы добавили два метода экземпляра, возвращающих строковые значения. Метод description
возвращает строку с описанием собаки, метод speak
принимает аргумент sound
:
>>> miles = Dog("Miles", 4) >>> miles.description() 'Miles is 4 years old' >>> miles.speak("Woof Woof") 'Miles says Woof Woof' >>> miles.speak("Bow Wow") 'Miles says Bow Wow'
В приведенном примере description()
возвращает строку, содержащую информацию об экземпляре. При написании собственных классов такие методы, описывающие экземпляры, и правда полезны. Однако description()
– не самый элегантный способ это сделать.
К примеру, когда вы создаете объект списка, вы можете использовать для отображения функцию print()
:
>>> names = ["Fletcher", "David", "Dan"] >>> print(names) ['Fletcher', 'David', 'Dan']
Посмотрим, что произойдет, когда мы попробуем применить print()
к объекту miles
:
>>> print(miles) <__main__.Dog object at 0x7f6854623690>
В большинстве практических приложений информация о расположении объекта в памяти не очень полезна. Поведение объекта при взаимодействии с функцией print()
можно изменить, определив специальный метод __str__()
:
class Dog: species = "Canis familiaris" def __init__(self, name, age): self. name = name self.age = age def __str__(self): return f"{self.name} is {self.age} years old" def speak(self, sound): return f"{self.name} says {sound}"
>>> miles = Dog("Miles", 4) >>> print(miles) Miles is 4 years old
Двойные символы подчеркивания в таких методах, как __init__()
и __str__()
указывают на то, что они имеют предопределенное поведение. Есть множество более сложных методов, которые вы можете использовать для настройки классов в Python, но это тема отдельной публикации.
Наследование – это процесс, при котором один класс принимает атрибуты и методы другого. Вновь созданные классы называются дочерними классами, а классы, от которых происходят дочерние классы, называются родительскими. Дочерние классы могут переопределять или расширять атрибуты и методы родительских классов.
Пример: место для выгула собак
Представьте, что вы в парке, где разрешено гулять с собаками. В парке много собак разных пород, и все они ведут себя по-разному. Предположим, что вы хотите смоделировать парк собак с классами Python. Класс Dog
, который мы написали в предыдущем разделе, может различать собак по имени и возрасту, но не по породе.
Мы можем изменить класс Dog
, добавив атрибут breed
(англ. порода):
class Dog: species = "Canis familiaris" def __init__(self, name, age, breed): self.name = name self.age = age self.breed = breed def __str__(self): return f"{self.name} is {self.age} years old" def speak(self, sound): return f"{self.name} says {sound}"
Смоделируем несколько псов разных пород:
miles = Dog("Miles", 4, "Jack Russell Terrier") buddy = Dog("Buddy", 9, "Dachshund") jack = Dog("Jack", 3, "Bulldog") jim = Dog("Jim", 5, "Bulldog")
У каждой породы собак поведение несколько отличаются. Например, разные породы по-разному лают: одни говорят «гав», другие делают «вуф». Используя только класс Dog
, мы были бы должны указывать строку для аргумента sound метода speak()
каждый раз, когда вызываем его в экземпляре Dog
:
>>> buddy.speak("Yap") 'Buddy says Yap' >>> jim.speak("Woof") 'Jim says Woof' >>> jack.speak("Woof") 'Jack says Woof'
Передавать строку в каждый вызов метод speak()
неудобно. Более того, строка, соответствующая звуку, который издает экземпляр, в идеале должна определяться атрибутом breed
.
Один из вариантов упростить взаимодействие с классом Dog
– создать дочерний класс для каждой породы. Это позволит расширить функциональные возможности наследующих дочерних классов. В том числе можно будет указать аргумент по умолчанию для speak
.
Создадим дочерние классы для каждой из перечисленных пород. Так как порода теперь будет определяться дочерним классом, её нет смысла указывать в родительском классе:
class Dog: species = "Canis familiaris" def __init__(self, name, age): self.name = name self.age = age def __str__(self): return f"{self.name} is {self.age} years old" def speak(self, sound): return f"{self.name} says {sound}"
Связь между родительским и дочерним классом определяется тем, что наследуемый класс (Dog
) передается в качестве аргумента, принимаемого дочерним классом:
class JackRussellTerrier(Dog): pass class Dachshund(Dog): pass class Bulldog(Dog): pass
Дочерние классы действуют так же, как родительский класс:
miles = JackRussellTerrier("Miles", 4) buddy = Dachshund("Buddy", 9) jack = Bulldog("Jack", 3) jim = Bulldog("Jim", 5)
Экземпляры дочерних классов наследуют все атрибуты и методы родительского класса:
>>> miles. species 'Canis familiaris' >>> buddy.name 'Buddy' >>> print(jack) Jack is 3 years old >>> jim.speak("Woof") 'Jim says Woof'
Чтобы определить, к какому классу принадлежит определенный объект, используйте встроенную функцию type()
:
>>> type(miles) __main__.JackRussellTerrier
Чтобы определить, является ли miles
экземпляром класса Dog
, используем встроенную функцию isinstance()
:
>>> isinstance(miles, Dog) True
Объекты miles
, buddy
, jack
и jim
являются экземплярами Dog, но miles
не является экземпляром Bulldog
, а jack
не является экземпляром Dachshund
:
>>> isinstance(miles, Bulldog) False >>> isinstance(jack, Dachshund) False
Все объекты дочернего класса являются экземплярами родительского класса, но не других дочерних классов.
Теперь дадим нашим собакам немного полаять.
Расширяем функциональность родительского класса
Что мы хотим сделать: переопределить в дочерних классах пород метод speak()
. Чтобы переопределить метод, определенный в родительском классе, достаточно создать метод с тем же названием в дочернем классе:
class JackRussellTerrier(Dog): def speak(self, sound="Arf"): return f"{self.name} says {sound}"
Мы переопределили метод speak
, добавив для породы JackRussellTerrier
значение по умолчанию.
>>> miles = JackRussellTerrier("Miles", 4) >>> miles.speak() 'Miles says Arf'
Мы по-прежнему можем передать какой-то иной звук:
>>> miles.speak("Grrr") 'Miles says Grrr'
Изменения в родительском классе автоматически распространяются на дочерние классы. Если только изменяемый атрибут или метод не был переопределен в дочернем классе.
class Dog: species = "Canis familiaris" def __init__(self, name, age): self.name = name self.age = age def __str__(self): return f"{self.name} is {self.age} years old" def speak(self, sound): return f"{self.name} barks {sound}"
>>> miles = JackRussellTerrier("Miles", 4) >>> miles.speak() 'Miles says Arf'
Иногда бывает необходимо учесть и поведение родительского класса, и дочернего, например, вызвать аналогичный метод родительского класса, но модифицировать его поведение. Для вызова методов родительского класса есть специальная функция super
:
class JackRussellTerrier(Dog): def speak(self, sound="Arf"): return super().speak(sound)
>>> miles = JackRussellTerrier("Miles", 4) >>> miles.speak() 'Miles barks Arf'
Здесь при вызове super(). speak(sound)
внутри класса JackRussellTerrier
, Python ищет родительский класс Dog
(на это указывает функция super()
), и вызывает его метод speak()
с переданной переменной sound
. Именно поэтому выводится глагол barks
, а не says
, но с нужным нам звуком Arf
, который определен в дочернем классе.
Обратите внимание
В приведенных примерах иерархия классов очень проста. КлассJackRussellTerrier
имеет единственный родительский классDog
. В реальных примерах иерархия классов может быть довольно сложной.Функцияsuper()
делает гораздо больше, чем просто ищет в родительском классе метод или атрибут. В поисках искомого метода или атрибута функция проходит по всей иерархии классов. Поэтому без должной осторожности использованиеsuper()
может привести к неожиданным результатам.
Итак, в этом руководстве мы разобрали базовые понятия объектно-ориентированного программирования (ООП) в Python. Мы узнали:
- в чём отличия классов и экземпляров;
- как определить класс;
- как инициализировать экземпляр класса;
- как определить методы класса и методы экземпляра;
- как одни классы наследуются от других.
Больше полезной информации вы можете получить на нашем телеграм-канале «Библиотека питониста».
***
Как научиться программировать на Python максимально быстро и качественно?
В условиях повышенной конкуренции среди джунов, пойти учиться на курсы с преподавателями — самый прагматичный вариант, который позволит быстро и качественно освоить базовые навыки программирования и положить 5 проектов в портфолио. Преподаватель прокомментирует домашние задания, поделится полезными советами, когда надо подбодрит или даст «волшебного» пинка.
На курсе «Основы программирования на Python» с преподавателем вы научитесь:
- работать в двух интегрированных средах разработки — PyCharm и Jupyter Notebook;
- парсить веб-страницы;
- создавать ботов для Telegram и Instagram;
- работать с данными для различных материалов и дальнейшего анализа;
- тестировать код.
Плюс положите 5 проектов в портфолио.
Интересно, хочу посмотреть
🔝 Топ 46 интервью вопросов по ООП с ответами в 2022 году
1. Что такое ООП?
Объектно-ориентированное программирование или ООП – это методология программирования, которая представляет программное обеспечение в виде набора объектов. Каждый объект – это не что иное, как экземпляр класса.
2. Разница между процедурным программированием и ООП?
Процедурное программирование:
- Основано на функциях.
- Определяет данные для всей программы.
- В нем нет возможности повторного использования кода.
- Следует концепции нисходящего программирования.
- Природа языка сложна.
- Трудно изменять, расширять и поддерживать код.
Объектно-ориентированное программирование:
- Основано на реальных объектах.
- Инкапсулирует данные.
- Обеспечивает больше возможностей для повторного использования кода.
- Следует парадигме программирования «снизу вверх».
- Он менее сложен по своей природе, поэтому его легче модифицировать, расширять и поддерживать.
3. Зачем использовать ООП?
ООП позволяет повторно использовать код. Данные и код связаны вместе с помощью инкапсуляции. ООП имеет возможности для сокрытия данных, поэтому частные данные могут храниться и сохранять конфиденциальность. Задачи могут быть разделены на различные части, что упрощает их решение. Концепция полиморфизма обеспечивает гибкость, так как одна сущность может иметь несколько форм.
4. Каковы основные концепции ООП?
Основными концепциями ООП являются:
- Наследование
- Инкапсуляция
- Полиморфизм
- Абстракция
5.
Что такое инкапсуляция?
Инкапсуляция является частью концепции ООП. Она относится к объединению данных с методами, которые работают с этими данными. Это также помогает ограничить любой прямой доступ к некоторым компонентам объекта.
6. Что такое абстракция?
Абстракция – это концепция ООП для построения структуры объектов реального мира. Она «показывает» только существенные атрибуты и «прячет» ненужную информацию от посторонних глаз. Основная цель абстракции – скрыть ненужные детали от пользователей. Это одна из самых важных концепций ООП.
7. Что такое перегрузка методов?
Существует концепция, согласно которой два или более метода могут иметь одинаковое имя. Но они должны иметь разные параметры, разное количество параметров, разные типы параметров или и то, и другое. Такие методы известны как перегруженные методы, и эта особенность называется перегрузкой методов.
8. Что такое переопределение методов?
Переопределение методов – это концепция объектно-ориентированного программирования. Это особенность языка, которая позволяет подклассу или дочернему классу предоставлять конкретную реализацию метода, который уже предоставлен одним из его суперклассов или родительских классов.
9. Типы наследования в ООП
- Гибридное наследование
- Множественное наследование
- Одноуровневое наследование
- Многоуровневое наследование
- Иерархическое наследование
10. Что такое объект?
Объект – это экземпляр класса, а также он обладает собственной индивидуальностью и поведением.
11. Что такое метод?
Метод – это описание набора инструкций, который также называется процедурой.
12. Что такое класс?
Класс – это тип данных, определяемый пользователем, который содержит переменные, свойства и методы. Он также помогает найти свойства объекта.
13. Что такое конструкторы?
Конструктор имеет то же имя, что и класс. Конструктор также является особым видом метода. Он используется для инициализации объектов класса.
14. Типы конструкторов
Типы конструкторов зависят от языка:
- Частный конструктор.
- Конструктор по умолчанию.
- Конструктор копирования.
- Статический конструктор.
- Параметризованный конструктор.
15. В чем разница между классом и структурой?
Класс – это определенный пользователем чертеж, на основе которого создаются объекты. Он состоит из методов (набор инструкций), которые выполняются над объектами.
Структура – это определенная пользователем коллекция переменных. Структуры также являются различными типами данных.
16. Что такое модификаторы доступа?
Модификаторы доступа или спецификаторы доступа – это ключевые слова в объектно-ориентированных языках. Они помогают установить доступность классов, методов и других членов.
17. Какие языки относятся к концепции ООП?
Simula известен как первый объектно-ориентированный язык программирования, наиболее популярными языками ООП являются:
- Java.
- JavaScript.
- Python.
- C++.
- Visual Basic . NET.
- Ruby.
- Scala.
- PHP.
18. Что такое наследование?
Если вы порождаете класс от другого класса, это называется наследованием.
Дочерний класс наследует все публичные и защищенные свойства и методы от родительского класса. Дочерний класс также может иметь свои собственные свойства и методы. Наследуемый класс определяется с помощью ключевого слова extends.
Множественное наследование:
- Если класс наследует более одного базового класса.
- Пример: класс, объясняющий ребенка. Этот класс –
ребенок
– наследует от двух базовых классов, которыми являютсямать
иотец
.
Многоуровневое наследование:
- Если класс наследуется от другого класса, который сам является подклассом какого–либо другого базового класса.
- Пример: класс под названием спортивный велосипед, который наследуется от базового класса велосипед. В свою очередь, класс
велосипед
наследуется от другого классатранспортное средство
.
19. Что такое гибридное наследование?
Сочетание множественного и многоуровневого наследования известно как гибридное наследование.
20. Что такое иерархическое наследование?
Когда один базовый класс имеет более одного подкласса. Например, класс фруктов может иметь в качестве подклассов «яблоко», «манго» и т. д.
21. Каковы ограничения наследования?
Эти ограничения увеличивают время выполнения и трудозатраты и требуют переходов от одного класса к другому. Родительский класс и дочерний класс всегда тесно связаны. Внесение изменений в программу потребует изменений как в родительском, так и в дочернем классе. Наследование требует тщательной реализации, иначе это приведет к неправильным результатам.
22. Что такое суперкласс?
Суперкласс или базовый класс – это класс, который работает как родитель для некоторых других классов.
Например, класс транспортного средства является суперклассом класса велосипеда.
23. Что такое подкласс?
Подкласс – это класс, который наследуется от другого класса. Например, класс велосипед
является подклассом или производным от класса транспортное средство
.
24. Что такое полиморфизм?
Полиморфизм является одним из наиболее используемых и основных понятий в языках ООП. Полиморфизм – концепция, согласно которой различные классы могут использоваться с одним и тем же интерфейсом. Каждый из этих классов может иметь свою собственную реализацию интерфейса.
25. Что такое статический полиморфизм?
Статический полиморфизм или статическое связывание – это один из видов полиморфизма, который возникает во время компиляции. Примером полиморфизма во время компиляции является: перегрузка методов.
26. Что такое динамический полиморфизм?
Динамический полиморфизм, динамическое связывание или полиморфизм во время выполнения – это также часть полиморфизма, который в основном реализуется во время выполнения программы. Пример полиморфизма во время выполнения программы: переопределение метода.
27. Что такое перегрузка операторов?
Перегрузка операторов используется для реализации операторов, использующих определенные пользователем типы, на основе передаваемых вместе с ними аргументов.
28. Проведите различие между перегрузкой и переопределением.
При перегрузке два или более методов имеют одинаковое имя, но у них разные параметры или сигнатура.
Переопределение – дочерний класс наследует методы с теми же параметрами/подписью, которые присутствуют в базовом классе.
29. Зачем нужна инкапсуляция?
Инкапсуляция позволяет скрыть данные и обернуть данные и код, который работает над ними, в единое целое.
30. В чем разница между модификаторами доступа public, private и protected?
Модификатор | Доступность из самого класса | Доступность из производного класса | Доступность из любого места программы |
Public | Да | Да | Да |
Private | Да | Нет | Нет |
Protected | Да | Да | Нет |
31.
Что такое абстракция данных?
Абстракция данных – одна из самых важных особенностей ООП. Она позволяет отображать только важную информацию. Она помогает скрыть детали реализации.
Например, при использовании мобильного телефона вы знаете, как можно отправить сообщение или позвонить кому–то, но не знаете, как это происходит на самом деле.
Это и есть абстракция данных, поскольку детали реализации скрыты от пользователя.
32. Как достичь абстракции данных?
Абстракция данных может быть достигнута двумя способами:
- Абстрактный класс.
- Абстрактный метод.
33. Что такое абстрактный класс?
Абстрактный класс – это класс, который состоит из абстрактных методов.
Что же такое абстрактный метод?
Эти методы в основном объявлены, но не определены, и если эти методы должны быть использованы позже в каком–то подклассе, то эти методы должны быть определены исключительно в подклассе.
34. Проведите различие между абстракцией данных и инкапсуляцией
Абстракция:
- Решает проблему на уровне проектирования.
- Помогает скрыть детали реализации.
Инкапсуляция:
- Решает проблему на уровне реализации.
- Обертывает код и данные в единое целое и помогает скрыть их от посторонних глаз.
35. Что такое виртуальные функции?
Виртуальные функции – это часть функций, которые присутствуют в родительском классе, и они переопределяются подклассом.
Эти функции помогают достичь полиморфизма во время выполнения.
36. Что такое деструктор?
Деструктор – это метод, который вызывается автоматически при уничтожении объекта.
Деструктор также восстанавливает пространство кучи, которое было выделено для уничтоженного объекта. Он также начинает закрывать файлы и соединения базы данных объекта и т. д.
37. Что такое конструктор копирования?
Конструктор копирования в основном создает объекты путем копирования переменных из другого объекта того же класса. Основной задачей конструктора копирования является создание нового объекта из существующего.
38. Для чего используется Finalize?
Finalize используется для освобождения неуправляемых ресурсов, а также для очистки перед сборкой мусора (GC). Он выполняет задачи управления памятью.
39. Что такое сборка мусора (GC)?
Сборка мусора – это часть автоматического управления памятью. Сборщик мусора помогает освободить места, занятые объектами. Эти места перестают существовать.
40. Что такое конечная переменная?
Конечная переменная не изменяется и всегда ссылается на один и тот же объект благодаря свойству необратимости.
41. Что такое исключение?
Исключение – это вид сообщения, которое перерывает и появляется, когда возникает проблема с нормальным выполнением программы. Исключения выдают ошибку и передают эту ошибку обработчику исключений для ее устранения. Состояние программы сохраняется, как только возникает исключение.
42. Что такое обработка исключений?
Обработка исключений в объектно-ориентированном программировании является наиболее важной концепцией. Она используется для управления ошибками. Обработчик исключений помогает выбрасывать ошибки, а затем перехватывать их для дальнейшего решения.
43. В чем разница между ошибкой и исключением?
Ошибка: в основном относится к проблеме, и эти проблемы не должны возникать в приложениях.
Исключение: в основном это условие, которое приложение может попытаться выполнить.
44. Что такое блок try/catch?
Блок try/catch
помогает обрабатывать исключения. Блок try
объясняет набор утверждений, в которых может возникнуть ошибка. Блок catch
в основном перехватывает исключение.
45. Что такое блок finally?
Блок finally
выполняется, когда блок try
завершается, а также выполняется даже в случае возникновения неожиданного исключения. Блок finally
обычно содержит какую–то важную часть программы.
46. Можете ли вы вызвать метод базового класса, не создавая его экземпляр?
Да, вы можете вызывать базовый класс без его инстанцирования, но есть некоторые условия, которые необходимо соблюдать:
- Метод должен быть статическим.
- Базовый класс наследуется каким-либо другим подклассом.
***
Больше полезной информации вы можете найти на нашем телеграм-канале «Библиотека программиста».
Интересно, перейти к каналу
Материалы по теме
- 🤹 Классы на прототипах: как работает ООП в JavaScript
- 🤹 Введение в объектно-ориентированное программирование (ООП) на Python
- 🤹 Паттерны ООП простыми словами: паттерны поведения
404: Страница не найдена
Архитектура приложения
Страница, которую вы пытались открыть по этому адресу, похоже, не существует. Обычно это результат плохой или устаревшей ссылки. Мы приносим свои извинения за доставленные неудобства.
Что я могу сделать сейчас?
Если вы впервые посещаете TechTarget, добро пожаловать! Извините за обстоятельства, при которых мы встречаемся. Вот куда вы можете пойти отсюда:
Поиск
- Узнайте последние новости.
- Наша домашняя страница содержит последнюю информацию об архитектуре приложений.
- Наша страница «О нас» содержит дополнительную информацию о сайте, на котором вы находитесь, «Архитектура приложений».
- Если вам нужно, свяжитесь с нами, мы будем рады услышать от вас.
Просмотр по категории
Качество ПО
-
Перспективы двух выпускников учебного лагеря Nucamp по кодированию неясныУчебный лагерь по кодированию может дать толчок карьере в области технологий, но только для тех, кто готов пройти лишнюю милю. Вот как два неразработчика. ..
-
10 основных навыков Скрам-мастераСкрам-мастера помогают разработчикам Agile, владельцам продуктов и другим членам команды выполнять задачи, начиная от планирования спринта и заканчивая лидерством на службе…
-
Salesforce DevOps Center поставляется для обуздания приложений с низким кодомЦентр Salesforce DevOps Center развернут с функциями, предназначенными для обновления пользовательских приложений с низким и профессиональным кодом на его платформе CRM…
Облачные вычисления
-
6 разработчиков вариантов PaaS с открытым исходным кодом, о которых следует знать в 2023 годуPaaS с открытым исходным кодом — хороший вариант для разработчиков, которым нужен контроль над хостингом приложений и упрощенное развертывание приложений, но не…
-
10 лучших провайдеров PaaS 2023 года и что они вам предлагаютPaaS — хороший вариант для разработчиков, которым нужен контроль над хостингом приложений и упрощенное развертывание приложений, но не все PaaS . ..
-
Интерпретация и применение рекомендаций AWS Compute OptimizerТрудно найти правильный баланс между производительностью, доступностью и стоимостью. Узнайте, как включить и применить AWS Compute…
TheServerSide.com
-
Скрам против Канбана: в чем разница?Когда вы сравниваете Scrum и Kanban, вы понимаете, что у них столько же общего, сколько и различий. Здесь мы поможем вам выбрать …
-
Различия между Java и TypeScript должны знать разработчикиВы знаете Java? Вы пытаетесь изучить TypeScript? Вот пять различий между TypeScript и Java, которые сделают …
-
Владелец продукта и менеджер продукта: в чем разница?Работа менеджера по продукту в компании сильно отличается от роли владельца продукта в команде Scrum. Узнать ключ…
Концепции ООП, которые вам нужно знать
Концепции OOPS, которые вам нужно знать
Инструмент
Веб-сайт tomassetti. me изменился: теперь он является частью strumenta.com. Вы по-прежнему будете находить все новости в обычном качестве, но в новом оформлении.
O bject O ориентированный P программирование (OOPS или OOPS для объектно-ориентированной системы программирования) является сегодня наиболее широко используемой парадигмой программирования. Хотя большинство популярных языков программирования являются мультипарадигмальными, поддержка объектно-ориентированного программирования имеет основополагающее значение для крупных проектов. Конечно, ООП имеет свою долю критиков. В настоящее время функциональное программирование кажется новым модным подходом. Тем не менее, критика в основном связана с неправильным использованием ООП.
Это означает, что если вы учитесь, чтобы стать лучшим программистом, крайне важно иметь хорошее представление об основных концепциях объектно-ориентированного программирования и о том, как они работают. Может быть, вы опытный программист, но начали сразу с практики, без какой-либо теоретической подготовки. Или вы просто забыли обновить свои знания во время работы. Вас могут удивить вещи, которых вы на самом деле не знаете. Очевидно, это может случиться с самыми лучшими из нас.
Итак, мы постараемся сохранить правильное сочетание теории и практики, приведя большое количество примеров. Наши примеры будут основаны на представлении командного вида спорта: наша сфера будет посвящена игрокам, тренерам и другим сотрудникам. Как вы это представляете? Мы собираемся увидеть это сейчас.
- Полиморфизм
- Делегирование и открытая рекурсия
- Симптомы плохого дизайна
- Гризость
- Храдость
- Принципы
9008
- Принципы
9008
- Принципы
9008
- .
- Сегрегация интерфейса
- Инверсия зависимостей
- Выводы
Класс
Каждый игрок — это отдельный человек, но всех их объединяет кое-что общее: они могут выполнять одни и те же действия, такие как бег или передача, и у них есть определенные черты , как число и позиция. То же самое можно сказать и о тренерах и остальном персонале. Каждый из них будет иметь разные значения, но все они следуют одной и той же модели.
Класс — это модель, чертеж, шаблон, описывающий некоторые функции.
Точнее, класс представляет данные, обычно с переменными, называемыми полями , и поведением, представленным функциями, обычно называемыми методами . Например, класс Игрок
может иметь:
- поле с именем
Роль
для представления его роли или положения на фактическом поле игры; - другое поле
Имя
, чтобы представить его имя; - как поведение, у него может быть метод
Pass(Player)
, который заставит его передать мяч другому игроку.
Давайте рассмотрим пример в псевдокоде. class Player{ Text Name Text Role Pass (Player teamMate) { // тело метода }}
Object
Если класс является моделью, то каковы настоящие игроки? Они называются объектами или экземплярами класса. В предыдущем примере аргумент teamMate был объектом. Чтобы создать объект из класса, вы инстанцируете или создаете объект.
Например, объект класса Игрок
Джон имеет Имя
«Джонни Джон» и Роль
«Атакующий». Player John = new PlayerJohn.Name = «Johnny John» John.Role = «Attacker»
Черный ящик
Черный ящик — это то, что вы можете наблюдать за входом и выходом, но вы игнорируете то, как оно работает: вы не можете смотреть внутри него. Это хорошо, потому что вы не зависите от того, что находится внутри коробки. И вам все равно, если однажды кто-то изменит то, что находится внутри коробки, если она все еще ведет себя так же. Снаружи ничего не меняется. Это принцип, который применяется в ООП, и это хорошо.
Проще говоря: если вы просто знаете, что что-то должно делать, но не знаете, как оно это делает, то вы не сможете это испортить.
Идея состоит в том, чтобы делегировать все, что необходимо для выполнения чего-то важного, в определенный раздел кода. Чтобы вы могли менять его независимо от любого другого, не рискуя сломать что-то еще.
Например, представьте, что тренер создал некую стратегию: ему не нужно объяснять игрокам, как пасовать или как бежать. Это просто нужно сказать им, что они должны делать. Сами игроки должны знать, как на самом деле делать эти вещи. Тренеру не нужно знать, как бить по мячу, или что-либо из практических деталей паса.
Мы хотим добиться такой же организации в нашем программировании. И мы можем сделать это с помощью Abstraction и Encapsulation .
Давайте начнем с общего псевдокода для объяснения этих концепций. class Coach { TellHimToRun(Player dude) { dude.Run() }}class Player{ // класс BodyPart не отображается BodyPart Legs Run() { if Legs.IsOk() // бегите else // делайте что-нибудь веселое bad }}
Абстракция
Абстракция относится к сокрытию деталей реализации снаружи класса.
Например, объект OldMan класса Coach
вызывает метод Run()
класса John, объект класса Player
. Ему не нужно знать, что должен сделать Джон , чтобы действительно запуститься. Просто нужно знать, что у объекта класса Player
есть метод Run()
.
Инкапсуляция
Инкапсуляция включает два понятия: ограничение доступа к некоторым полям и методам класса из внешнего мира и связывание вместе связанных данных и методов.
Например, для имитации возможности бега класс Player
имеет метод с именем Run()
, а также поле с именем Legs
. Это поле отображает состояние ног игрока: насколько они устали и насколько они здоровы, то есть травмированы ли они. Внешнему миру не нужно знать, что Игрок
имеет Ноги
и как они работают.
Итак, класс Игрок
скрывает поле Ноги
из внешнего мира. Поскольку для выполнения Run()
нужно просто воздействовать на ноги
, тем самым гарантируется, что отдельный объект может быть полностью автономен от внешнего вмешательства. Это полезно, если вы позже захотите что-то изменить без вмешательства других частей кода. Например, если вы хотите добавить в симуляцию эффекты разной обуви на ногах. Вам просто нужно изменить класс Player
, и ничего больше.
Наследование
Для решения проблемы обычно приходится создавать классы, которые каким-то образом связаны между собой. У них есть общие черты или даже поведение. Поскольку вы хотите избежать повторения, вы хотите собрать все эти общие функции в общий класс. Вы хотите избежать повторения как потому, что повторять код раздражает, так и потому, что повторение приводит к ошибкам. Легко изменить только одну версию повторяющегося кода и оставить другую в старом состоянии.
Обычно этот общий класс называется родительским, суперклассом или базовым классом. Как только вы создали этот класс, другие классы могут объявить себя подобными ему или наследовать от него. Это называется наследованием.
Конечным результатом является то, что каждый из классов, наследуемых от родительского класса , также может иметь методы и поля родительского класса в дополнение к своим собственным.
Например, вы заметили, что классы Player
, Coach
и Staff
имеют имя и зарплату, поэтому вы создаете родительский класс с именем Person
и делаете их его наследниками. Обратите внимание, что вы продолжаете создавать только объект класса 9.0143 Player , Coach
и т. д. вам не нужно явно создавать объект класса Person
. Вы можете сделать это, но вам это не нужно, если вы хотите только Player
или Coach
. class Person{ Integer Salary Text Name} class Coach : parent Person{ AskForARaise() { if Salary < 1000 // запросить больше денег } }
В некоторых языках вы можете явно запретить создание класса Person
, пометив его как реферат 9 класс0140 . В таких случаях класс, экземпляр которого вы можете фактически создать, называется конкретным классом .
Большинство объектно-ориентированных языков поддерживают наследование, некоторые также поддерживают множественное наследование : класс может наследоваться от нескольких классов. Это не всегда возможно, потому что создает проблемы и добавляет сложности. Типичная проблема заключается в том, чтобы решить, что делать, когда два разных родительских класса имеют метод с одинаковой сигнатурой.
В просторечии наследование определяет отношение между двумя классами это (-тип-из)-a . В нашем примере Player
является (-a-type-of)-a Person
.
Интерфейс
Интерфейс, также известный как протокол, является альтернативой наследованию для двух несвязанных классов для связи друг с другом. Интерфейс определяет методы и (часто, но не всегда) значения. Каждый класс, реализующий интерфейс, должен предоставлять конкретный метод для каждого метода интерфейса.
Например, вы хотите имитировать изгнание или увольнение. Поскольку с поля могут быть удалены только игроки и тренеры, вы не можете сделать его методом родительского класса, который представляет людей. Так вы создаете интерфейс Ejectable
с методом Ejection()
и заставить Player
и Coach
реализовать его. interface Ejectable{ Ejection()}class Player : реализовать Ejectable{ Ejection() { // вылететь с криком с поля }}class Coach : реализовать Ejectable{ Ejection() { // взять свой мобильный телефон, чтобы поговорить с помощником }}
Не существует стандартного способа описания отношений, устанавливаемых интерфейсом, но вы можете представить его как вести себя как
. В нашем примере Плеер
вести себя как Извлекаемый
.
Ассоциация, агрегация и композиция
Наследование и интерфейс применяются к классам , но есть возможные способы связать два или более различных объектов. Эти способы можно рассматривать в порядке слабости связи: ассоциация, агрегация и композиция.
Ассоциация
Ассоциация просто описывает любой вид рабочих отношений. Эти два объекта являются экземплярами совершенно не связанных между собой классов, и ни один из объектов не управляет жизненным циклом другого. Они просто сотрудничают для достижения своих целей.
Представьте, что вы хотите добавить эффект аудитории на игроков, в реальной жизни аудитория состоит из людей, но в нашей симуляции они не являются детьми класса Person
. Вы просто хотите сделать так, чтобы если объект HomePeople класса Audience
аплодировал, то John играл лучше. Таким образом, HomePeople может влиять на поведение John, , но ни HomePeople , ни John не могут контролировать жизненный цикл другого. class Audience{ Boolean Cheering Cheer() { Cheering = true } StopCheer() { Cheering = false }}Audience HomePeople = new Audienceclass Player { ListenToThePeople() { if HomePeople.Cheering = true // улучшить способность }}
Агрегация
Агрегация описывает отношения, в которых один объект принадлежит другому объекту, но они все еще потенциально независимы. Первый объект не контролирует жизненный цикл второго.
В командных видах спорта все объекты класса Игрок
принадлежат объекту Команда
, но они не умирают только потому, что их уволили. Они могут временно остаться без работы или сменить Team
.
Этот тип отношений обычно описывается как имеет-a ( или является частью) , или, наоборот, принадлежит . В нашем примере объект Победители из Команда
имеет Джон из Игрок
или, наоборот, Джон принадлежит Победителям . class Team{ Player[50] TeamMembers Fire(Игрок-чувак) { TeamMembers.Remove(чувак) } Hire(Игрок-чувак) { TeamMembers.Add(чувак) } } Team Winners = новый TeamTeam Mediocre = новый TeamPlayer John = новый PlayerWinners.Hire (Джон)// проходит времяПобедители.Огонь(Джон)Посредственно.Наем(Джон)
Композиция
Композиция описывает отношения, в которых один объект полностью контролирует другой объект, не имеющий независимого жизненного цикла.
Представьте, что мы хотим добавить в нашу симуляцию стадионы или арены. Мы решили, что объект Арена
не может существовать вне Команды , они принадлежат Команде
, которая решает их судьбу. Конечно, в реальной жизни арена не исчезает волшебным образом, как только команда решает ее закрыть. Но поскольку мы хотим моделировать только командные виды спорта, для наших целей он выходит из игры, как только перестает принадлежать команде.
Композиции описываются точно так же, как агрегаты (т. е. has-a ( или is-part-of) ), поэтому следите за тем, чтобы не перепутать их.
class Arena{ Text Name Integer Capacity}class Team{ Arena HouseOfTheTeam EvaluateArena() { // если арена слишком мала для нашей лиги HouseOfTheTeam.Destroy() // создаем новую арену HouseOfTheTeam = new Arena }}
Полиморфизм
Полиморфизм в контексте ООП означает, что вы можете вызывать одну и ту же операцию над объектами разных классов, и все они будут выполнять ее по-своему. Это другое, и его не следует путать с концепцией программирования, независимой от ООП: функция (метод) перегрузка . Перегрузка применяется к функциям и позволяет вам определять функции с одним и тем же именем, которые работают с разными и несвязанными типами. Например, вы можете создать два метода add()
: один, который может складывать целые числа, и другой, который складывает действительные числа.
Полиморфизм в классе обычно означает, что метод может работать с разными объектами связанных классов. Он может вести себя по-разному на разных объектах, но не обязан. На самом базовом уровне объект дочерний класс , который наследуется от родителя, может использоваться, когда вы можете использовать объект родительского класса. Например, вы можете создать функцию Interview(Person)
, которая имитирует интервью Player
, Coach
или Staff
. Независимо от фактического объекта. Очевидно, что для того, чтобы это работало, Interview
может воздействовать только на поля, которые присутствуют в классе Person
.
На практике это означает, что объект дочернего класса также является объектом родительского класса.
В некоторых случаях дочерний класс может переопределить одноименный метод родительского класса, это называется переопределением . В этой ситуации всякий раз, когда этот метод вызывается, фактически выполняемым методом является метод дочернего класса. Это особенно полезно, когда вам нужно, чтобы все дочерние классы имели определенное поведение, но нет возможности определить общий. Например, вы хотите, чтобы все объекты класса Person
ушли на пенсию, но Player
должен уйти на пенсию после определенного возраста, а Coach
или Staff
нет.
Делегирование и открытая рекурсия
В объектно-ориентированном программировании делегирование относится к оценке члена одного объекта (получателя) в контексте другого, исходного объекта (отправителя) — из Википедии
Концепция используется широко используется в особом стиле объектно-ориентированного программирования, называемом программированием на основе прототипов. Одним из широко распространенных языков, использующих его, является JavaScript. Основная идея состоит в том, чтобы не иметь классов, а только объекты. Таким образом, вы не создаете экземпляры объектов из класса, а клонируете их из общего класса, а затем модифицируете его прототип в соответствии со своими потребностями.
Несмотря на то, что он лежит в основе самого известного языка программирования, он малоизвестен. Действительно, даже JavaScript в основном используется в обычном стиле объектно-ориентированного программирования. Хотя это может показаться тайным знанием, важно знать его, потому что оно широко используется со специальной переменной или ключевым словом, называемым this или self .
Большинство объектно-ориентированных языков программирования поддерживают его, и он позволяет ссылаться на конкретный объект (не класс), который будет создан, изнутри метода определен в классе или в любом дочернем классе . Тот факт, что при запуске кода этот
будет ссылаться на конкретный объект, позволяет открыть рекурсию . Это означает, что базовый класс может определить метод, который использует this
для ссылки на один из своих методов, который фактический объект будет использовать для ссылки на дочерний метод с той же сигнатурой.
Звучит сложно, но это не так. Представьте себе следующий псевдокод. class Person{ Integer Age IsOld() { if this.Age > 60 return true else return false } DoesHaveToRetire() { // делегирование произойдет здесь во время выполнения, если this.IsOld() return true else return false }}class Player : parent Person{ IsOld() { if this.Age > 34 return true else return false }}Player JohnJohn.Age = 35// здесь происходит открытая рекурсия// ответ trueJohn.DoesHaveToRetire()
В последней строке происходит открытая рекурсия, потому что метод DoesHaveToRetire
— это метод, определенный в родительском классе, который использует this.IsOld()
(в строке 16). Но метод IsOld
, который фактически вызывается во время выполнения, определен в дочернем классе Player
.
Это тоже делегирование, потому что в строке 16 этот
оценивается в контексте объекта Джон, оценивается как объект Класс Player
, а не оригинал этого класса John , как объект класса Person
. Потому что помните, что John является одновременно объектом класса Player
и его родительского класса Person
.
Симптомы плохого дизайна
До сих пор мы говорили об основах. Полезно уметь плодотворно применять эти знания. Во-первых, нам нужно посмотреть на симптомы плохого дизайна, чтобы вы могли обнаружить их в своем коде.
Жесткость
Программное обеспечение сложно изменить даже в мелочах. Каждая модификация требует каскадных изменений, применение которых занимает недели. Сами разработчики понятия не имеют, что произойдет и что придется изменить, когда им нужно будет сделать X или Y. Это приводит к нежеланию и страху меняться как у разработчиков, так и у руководства. И это постепенно делает код очень сложным в обслуживании.
Хрупкость
Программное обеспечение ломается неожиданным образом при каждом изменении. Это проблема, связанная с жесткостью, но она отличается тем, что не существует последовательности модификаций, которая продолжает удлиняться с каждым часом. Вроде бы все работает, но когда вы думаете, что готовы отправить код, тест или, что еще хуже, заказчик говорит вам, что что-то еще не работает. Обновка работает, а другая сломана. Каждое исправление — это фактически две новые проблемы. Это приводит к экзистенциальному страху у разработчика, который чувствует, что потерял контроль над программным обеспечением.
Непереносимость
Каждый модуль работает, но только в той осторожной ситуации, в которой он был помещен. Вы не можете повторно использовать код в другом проекте, потому что слишком много мелочей, которые вам придется изменить. Программа, похоже, работает по темной магии. Дизайна нет, только хаки. Каждый раз, когда вы что-то изменяете, вы знаете, что делать, но это всегда ужасная вещь, которая заставляет вас бояться, что она вернется, чтобы укусить вас. И это будет. Помимо стыда, который вы действительно должны чувствовать, это затрудняет повторное использование кода. Вместо этого каждый раз, когда вам нужно повторно использовать код, вы будете воссоздавать немного другую версию, которая делает почти то же самое.
Принципы хорошего дизайна
Знать, как выглядит проблема, недостаточно. Нам также необходимо знать практические принципы проектирования, чтобы в первую очередь избежать создания плохого дизайна. Эти общеизвестные принципы являются плодом многолетнего опыта и известны под своим аббревиатурой: SOLID.
Единая ответственность
У класса никогда не должно быть более одной причины для изменения 1
Обычно причина изменения модифицируется в «ответственности», отсюда и название принципа, но это исходная формулировка . В этом контексте ответственность или причина для изменения зависит от конкретного проекта и его требований. Это, очевидно, не означает, что класс должен иметь только один метод. Это означает, что когда вы описываете, что он делает, вы говорите, что он делает только это. Если вы нарушаете этот принцип, различные обязанности становятся связанными, и вам, возможно, придется изменить один и тот же класс или несколько классов по разным причинам.
Вернемся к нашему примеру. Вам нужно вести счет игры. У вас может возникнуть соблазн использовать класс Player
, в конце концов, игроки делают подсчет очков. Но если вы сделаете это, каждый раз, когда вам нужно будет узнать счет, вам также потребуется взаимодействовать с игроком. А что бы вы сделали, если бы вам нужно аннулировать балл? Держите одну работу для каждого класса.
Открытый/Закрытый
Модуль должен быть открытым для расширения, но закрытым для модификации 2
В данном контексте модуль означает класс или группу классов, которые отвечают за одну задачу программного обеспечения. Этот принцип означает, что вы должны иметь возможность добавлять поддержку новых элементов без изменения кода самого модуля. Например, вы должны иметь возможность добавить новый тип игрока (например, вратаря) без изменения класса Player
.
Это позволяет разработчику поддерживать новые вещи, которые выполняют те же функции, что и те, что у вас уже есть, без необходимости делать для этого «особый случай».
Liskov Substitution
Подклассы должны использоваться как их базовые классы. 2
Обратите внимание, что это не исходная формулировка, потому что она слишком математическая. Это настолько важный принцип, что он был включен в разработку самого объектно-ориентированного языка. Но сам язык гарантирует лишь часть принципа, формальную часть. Технически вы всегда можете использовать объект подкласса, как если бы он был объектом базового класса, но для нас важно также практическое использование.
Это означает, что вы не должны существенно изменять поведение подкласса, даже если вы переопределяете метод. Например, если вы делаете игру про гоночную машину, вы не можете создать подкласс для машины, которая движется под водой. Если вы сделаете это, метод Move() подкласса будет вести себя иначе, чем базовый класс. А через несколько месяцев появится странная ошибка, когда случайные автомобили будут использовать Гольфстрим как шоссе.
Разделение интерфейсов
Многие клиентские интерфейсы лучше, чем один интерфейс общего назначения 2
В этом контексте «специфический для клиента» означает специфичный для каждого типа клиента, а не для каждого класса клиентов. Этот принцип говорит о том, что вы не должны реализовывать один общий интерфейс для клиентов, которые действительно делают очень разные вещи. Это потому, что это связывает каждый тип клиента друг с другом. Если вы измените один тип клиента, вам придется изменить общий интерфейс и, возможно, вам также придется изменить другие клиенты.
Вы можете распознать нарушение этого принципа, если у вас есть несколько методов на интерфейсе, специфичных для одного клиента. Это может быть как в том смысле, что они делают обычные вещи другим, специфическим способом, так и в том, что они делают то, что нужно только одному клиенту.
При практическом использовании это, вероятно, труднее соблюдать. Тем более, что в начале легко упустить из виду, что на самом деле разные требования. Например, представьте, что вам приходится иметь дело с подключениями: кабельным, мобильным и т. д. Они все одинаковые, не так ли?
Ну, в теории они ведут себя одинаково, но на практике могут отличаться. Мобильные соединения обычно дороже и имеют строгие ограничения на размер ежемесячно передаваемых данных. Так что мегабайт для мобильного соединения дороже, чем для кабельного. Как следствие, вы можете реже проверять данные или использовать SendBasicData()
вместо SendData()
… Если у вас еще нет опыта в конкретной области, над которой вы работаете, вы можете продолжать заставлять себя следовать этому принципу.
Инверсия зависимостей
Зависит от абстракций. Не зависеть от конкреций 2
Здесь должно быть ясно, что под «абстракцией» подразумеваются интерфейсы и абстрактные классы . И должно быть также ясно, почему это правда. Абстракция по определению имеет дело с общим случаем. Используя абстракцию, вы упрощаете добавление новых функций или поддержку новых элементов, таких как другие базы данных или другие программы.
Конечно, вы не всегда должны использовать интерфейсы для всего, чтобы два класса не касались друг друга. Но если есть необходимость использовать абстракцию , вы также не можете допустить утечки абстракции. Вы не можете требовать, чтобы клиент интерфейса знал, что он должен использовать интерфейс определенным образом. Если вы обнаружите, что проверяете, действительно ли интерфейс представляет определенный класс, а затем делаете Y вместо X, у вас есть проблема.
Резюме
Мы рассмотрели основные концепции ООП, которые вам необходимо знать, чтобы стать хорошим программистом. Мы рассмотрели фундаментальные принципы проектирования, которыми вы должны руководствоваться при разработке программного обеспечения. Если вы только начинаете, это может показаться немного абстрактным, но, как и все специализированные знания, они необходимы для хорошего общения с вашими коллегами.