что такое атрибуты класса
ООП: атрибуты и методы
Два главных слова любого программиста в ООП. Знай их, люби их, говори правильно.
Продолжаем цикл статей об основах объектно-ориентированного программирования. Сегодня говорим о двух важных словах в ООП: атрибутах и методах. Это основа лексикона ООП, поэтому нужно знать.
Краткое содержание предыдущих частей:
Теперь нырнём в атрибуты и методы.
Атрибуты
Атрибут — это переменная, связанная с объектом или классом. Грубо говоря, если я хочу, чтобы у объекта «Пользователь» появилась фамилия, я должен сделать пользователю атрибут «Фамилия».
Для программистов: у класса есть атрибуты, свойства и поля. В зависимости от языка программирования эти три параметра могут означать одно и то же, а могут различаться. В этой статье мы разбираем академический подход к структуре класса вида «атрибут — значение».
Возьмём в качестве примера метафору — производство телефонов. У нас есть класс «Смартфон» — некий абстрактный смартфон, по лекалам которого изготавливают конкретные объекты-смартфоны.
У класса «Смартфон» могут быть такие атрибуты:
Это у нас будут атрибуты класса «Смартфон». Они могут принимать конкретные значения: камеры могут быть разных моделей, память может быть 64 или 256 гигабайт, а батарейка — 2500 мАч или 3500 мАч.
Когда мы задаём атрибут для класса, мы как будто настраиваем производственную линию: «Тут у нас будет станок по установке камер, там — по вклеиванию батареи». Когда мы задали класс с определёнными атрибутами, все объекты, произведённые из этого класса, будут появляться на свет с этими атрибутами.
Методы
Методы — это то, как можно взаимодействовать с атрибутами, узнавать и менять их значения. Рассмотрим их на том же прошлом примере про класс мобильника. Вот какие действия можно совершать:
Получается, что методы отвечают за то, чтобы можно было взаимодействовать с классом. Чаще всего они отвечают за то, что можно сделать с атрибутами
Если посмотреть на список, можно заметить, что почти все методы доступны для выполнения извне — позвонить, сделать фото, посмотреть погоду и так далее. Это значит, что это открытые методы (public) — их может вызывать и работать с ними кто угодно: как пользователь, так и другие программы для своих нужд.
Но два других метода из списка — особенные: обработать HDR-фото и поймать сигнал сети. Их нельзя запустить напрямую, их вызывает операционная система, когда ей это нужно. Это значит, что это закрытые (private) методы, и они доступны только внутри самого класса. Если классу понадобится что-то обработать внутри себя, он ими воспользуется, а другие не смогут этого сделать.
Ещё есть защищённые (protected) методы. Их пока нет в наших примерах, но мы до них обязательно дойдём.
Атрибуты C#: обо всех аспектах
Здравствуй, читатель. В этой статье описаны атрибуты со всех сторон — начиная от спецификации, смысла и определения атрибутов, создания собственных и работе с ними, заканчивая добавлением атрибутов на рантайме и наиболее полезными и интересными существующими атрибутами. Если вам интересна тема атрибутов в C#, то добро пожаловать под кат.
Содержание
Введение
Как всегда, начнем с определений и спецификаций. Это поможет понять и осознать атрибуты на всех уровнях, что, в свою очередь, очень полезно для того, чтобы находить им правильные применения.
С синтаксической точки зрения (в метаданных) есть следующие атрибуты
Пример
Как видно StructLayoutAttribute имеет специальный синтаксис, так как в IL он представлен как «explicit». ObsoleteAttribute использует общий синтаксис — в IL начинается с «.custom». SecurityPermissionAttribute как атрибут безопасности превратился в «.permissionset assert».
Пользовательские атрибуты добавляют пользовательскую информацию к метаданным. Этот механизм может использоваться для хранения специфичной для приложения информации во время компиляции и для доступа к ней во время выполнения или для чтения и анализа другим инструментом. Хотя любой определенный пользователем тип может быть использован в качестве атрибута, для соответствия CLS необходимо, чтобы атрибуты наследовались от System.Attribute. CLI предопределяет некоторые атрибуты и использует их для управления поведением во время выполнения. Некоторые языки определяют атрибуты для представления возможностей языка, не представленных непосредственно в CTS.
Как уже упоминалось, атрибуты хранятся в метаданных, которые, в свою очередь, формируются на этапе компиляции, т.е. вносятся в PE файл (как правило *.dll). Таким образом, добавить во время выполнения атрибут можно только модифицируя исполняемый файл во время выполнения (но времена самоизменяющихся программ уже давно в прошлом). Отсюда следует, что на этапе выполнения их добавить нельзя, но это не совсем точно. В случае, если мы формируем свою сборку, определяем в ней типы, то мы можем на этапе выполнения создать новый тип и вешать атрибуты на него. Так что формально, мы все же можем добавлять атрибуты на этапе выполнения (пример будет в самом низу).
А теперь немного об ограничениях
Если по каким-то причинам в одной сборке существует 2 атрибута с именами Name и NameAtribute, то становится невозможным поставить первый из них. При использовании [Name] (т.е. без суффикса) компилятор говорит, что видит неопределенность. При использовании [NameAttribute] мы поставим NameAttribute, что логично. Для такой мистической ситуации с недостатком воображения при именовании существует специальный синтаксис. Чтобы поставить первую версию без суффикса можно указать знак собаки (т.е. [Name] — шутка, так не надо) перед именем атрибута [@Name].
Пользовательские атрибуты могут быть добавлены к чему угодно, кроме пользовательских атрибутов. Имеется в виду метаданные, т.е. если мы ставим в C# атрибут над классом атрибута, то в метаданных он будет относиться к классу. А вот добавить атрибут к «public» нельзя. Зато можно к сборкам, модулям, классам, типам значений, перечислениям (enum), конструкторам, методам, свойствам, полям, событиям, интерфейсам, параметрам, делегатам, возвращаемым значениям или обобщенным параметрам. В примере ниже приведены очевидные и не очень примеры того, как можно поставить атрибут на ту или иную конструкцию.
Атрибуты имеют 2 типа параметров — именованные и позиционные. К позиционным относятся параметры конструктора. К именованным — публичные свойства с доступным сеттером. При этом это не просто формальные названия, все параметры можно указывать при объявлении атрибута в скобках после его названия. Именованные не являются обязательными.
Допустимые параметры (обоих видов) для атрибута должны быть одного из перечисленных типов:
Существует два вида пользовательских атрибутов: подлинные пользовательские атрибуты (genuine custom attributes) и псевдо-пользовательские (pseudo-custom).
В коде они выглядят одинаково (указываются над конструкцией языка в квадратных скобках), но обрабатываются по-разному:
Большинство пользовательских атрибутов введены на уровне языка. Они хранятся и возвращаются рантаймом, при этом рантайм не знает ничего о значении этих атрибутов. Но все псевдо-пользовательские атрибуты плюс некоторые пользовательские атрибуты представляют особый интерес для компиляторов и для CLI. Таким образом мы переходим к следующему разделу.
Атрибуты с поддержкой рантайма
Данный раздел носит чисто информативный характер, если интереса к тому, что использует рантайм нет, то можно скролить к следующему разделу.
В таблице ниже перечислены псевдо-пользовательские атрибуты и специальные пользовательские атрибуты (CLI или компиляторы обрабатывают их специальным образом).
Псевдо-пользовательские атрибуты (их нельзя получить через рефлексию).
CLI Атрибуты:
Атрибут | Описание |
---|---|
AssemblyAlgorithmIDAttribute | Записывает идентификатор используемого алгоритма хеширования. Задает поле Assembly.HashAlgId |
AssemblyFlagsAttribute | Записывает флаги для соответствующей сборки. Задает поле Assembly.Flags |
DllImportAttribute | Предоставляет информацию о коде, реализованном в неуправляемой библиотеке. Устанавливает Method.Flags.PinvokeImpl бит соответствующего метода; добавляет новую запись в ImplMap (устанавливая значения MappingFlags, MemberForwarded, ImportName и ImportScope) |
StructLayoutAttribute | Позволяет явно задать способ размещения полей ссылочного или значимого типа. Устанавливает поле TypeDef.Flags.LayoutMask для типа. Также может устанавливать поля TypeDef.Flags.StringFormatMask, ClassLayout.PackingSize и ClassLayout.ClassSize |
FieldOffsetAttribute | Определяет смещение в байтах полей в ссылочном или значимом типе. Устанавливает значение FieldLayout.OffSet для соответствующего метода |
InAttribute | Показывает, что параметр передается как [in] аргумент. Устанавливает Param.Flags.In бит для соответствующего параметра |
OutAttribute | Показывает, что параметр передается как [out] аргумент. Устанавливает Param.Flags.Out бит для соответствующего параметра |
MarshalAsAttribute | Определяет способ маршалинга данных между управляемым и неуправляемым кодом. Устанавливает Field.Flags.HasFieldMarshal бит для поля (или Param.Flags.HasFieldMarshal бит для параметра); Добавляет запись в таблицу FieldMarshal (устанавливая значения Parent и NativeType) |
MethodImplAttribute | Определяет детали реализации метода. Устанавливает значение Method.ImplFlags для соответствующего метода |
CLS Атрибуты — языки должны поддерживать их:
Атрибут | Описание |
---|---|
AttributeUsageAttribute | Используется для указания, как атрибут может быть использован |
ObsoleteAttribute | Показывает, что элемент не должен использоваться |
CLSCompliantAttribute | Указывает, объявлен ли элемент как CLS-совместимый |
Атрибут | Описание |
---|---|
ThreadStaticAttribute | Предоставляет поля типа, относящиеся к потоку |
ConditionalAttribute | Помечает метод как вызываемый, опираясь на условие компиляции (указанное в /define). Если условие не соблюдено, то метод не вызовется (И не будет скомпилирован в IL). Может быть помечен только void метод. В противном случае возникнет ошибка компиляции |
DecimalConstantAttribute | Сохраняет значение константы типа decimal в метаданных |
DefaultMemberAttribute | Определяет член класса, который будет использоваться по умолчанию методом InvokeMember |
CompilationRelaxationsAttribute | Указывает, являются ли исключения из проверок инструкций строгими или смягченными. На текущий момент можно передать только параметр NoStringInterning, который помечает сборку как не требующую интернирования строковых литералов. Но этот механизм все еще может использоваться |
FlagsAttribute | Атрибут, указывающий, должен ли enum восприниматься как битовые флаги |
IndexerNameAttribute | Указывает имя, под которым индексатор будет известен в языках программирования, которые не поддерживают такую возможность напрямую |
ParamArrayAttribute | Показывает, что метод принимает переменное число параметров |
Полезные атрибуты
Неотъемлемой частью разработки программного продукта является отладка. И зачастую в большой и сложной системе требуется десятки и сотни раз запускать один и тот же метод и наблюдать за состоянием объектов. При этом на раз 20 уже начинает конкретно бесить необходимость разворачивать один объект вглубь раз 400, чтоб увидеть значение одной переменной и перезапустить метод заново.
Для более спокойной и быстрой отладки можно использовать атрибуты, модифицирующие поведение отладчика.
DebuggerDisplayAttribute указывает, как отображается тип или его член в окне переменных отладчика (ну и не только).
Единственным аргументом конструктора является строка с форматом отображения. То, что будет находиться между фигурными скобками, будет вычислено. Формат как у интерполированной строки, только без доллара. Нельзя использовать указатели в вычисляемом значении. Кстати, если у вас есть переопределенный ToString, то его значение будет показываться как если бы оно было в этом атрибуте. Если есть и ToString и атрибут, то значение берется из атрибута.
DebuggerBrowsableAttribute определяет способ отображения поля или свойства в окне переменных отладчика. Принимает DebuggerBrowsableState, который имеет 3 опции:
DebuggerTypeProxy — если объект просматривается в отладчике сотни раз в день, то можно заморочиться и потратить минуты 3 на создание прокси объекта, который отобразит исходный объект как надо. Обычно прокси объект для отображения — внутренний класс. Собственно, он и будет отображаться вместо целевого объекта.
Другие полезные атрибуты
ThreadStatic — атрибут позволяющий сделать статическую переменную своей для каждого потока. Для этого надо поставить атрибут над статическим полем. Стоит помнить важный нюанс — инициализация статическим конструктором будет выполнена только один раз, и переменная поменяется в том потоке, который выполнит статический конструктор. В остальных она останется дефолтной. (ЗЫ. Если вам необходимо такое поведение, советую взглянуть в сторону класса ThreadLocal).
InternalsVisibleTo — позволяет указать сборку, которой будут видны элементы, помеченные internal. Может показаться, что если какой-то сборке нужны определенные типы и их члены, можно просто пометить их public и не париться. Но хорошая архитектура подразумевает сокрытие деталей имплементации. Тем не менее они могут понадобиться для каких-нибудь инфраструктурных вещей, например, тестовых проектов. С помощью этого атрибута можно поддерживать и инкапсуляцию, и требуемый процент покрытия тестами.
HandleProcessCorruptedStateExceptions — позволяет отпугивать робких программистов и ловить исключения поврежденного состояния. По умолчанию для таких исключений CLR не выполняет отловки. В общем случае лучшим выходом будет как раз позволить приложению упасть. Это опасные исключения, показывающие, что память процесса повреждена, так что использование данного атрибута — идея очень плохая. Но возможно в некоторых случаях, для локальной разработки будет полезно на некоторое время поставить данный атрибут. Чтобы словить исключение поврежденного состояния, достаточно просто поставить этот атрибут над методом. И если уже дошло до использования этого атрибута, то рекомендуется (впрочем, как и всегда) отлавливать какое-то конкретное исключение.
DisablePrivateReflection — делает все приватные члены сборки недосягаемыми для рефлексии. Атрибут ставиться на сборку.
Определение своего атрибута
Не просто так этом раздел стоит последним. Ведь лучший способ понять, в каких случаях будет выгодно использовать атрибут — посмотреть на уже используемые. Сложно сказать формализованное правило, когда следует задуматься о собственном атрибуте. Зачастую их используют как дополнительную информацию о типе/его члене или другой конструкции языка, которая бывает общей у совершенно разных сущностей. Как пример — все атрибуты, используемые для сериализации/ОРМ/форматирования и тд. Из-за обширного применения данных механизмов к совершенно разным типам, зачастую не известным разработчикам соответствующего механизма, использование атрибутов — отличный способ дать пользователю возможность предоставлять декларативную информацию для данного механизма.
Использование своих атрибутов можно разделить на 2 части:
Создание атрибута и его использование
Для создания своего атрибута достаточно наследоваться от System.Attribute. При этом желательно придерживаться упомянутого стиля именования — заканчивать имя класса на Attribute. При этом никакой ошибки не будет, если опустить этот суффикс. Как упоминалось ранее, атрибуты могут иметь 2 типа параметров — позиционные и именованные. Логика их применения такая же, как и со свойствами и параметрами конструктора у класса — значения, необходимые для создания объекта, для которых не существует разумного «дефолта» выносятся в позиционные (т.е. конструктор). То, чему можно поставить какой-то разумный дефолт, который будет зачастую использован, лучше выделить в именованный (т.е. свойство).
Немаловажным в создании атрибута является ограничение мест его применения. Для этого используется AttributeUsageAttribute. Обязательным параметром (позиционным) является AttributeTarget, определяющий место использования атрибута (метод, сборка и тд). Необязательными (именованными) параметрами являются:
Метод (функция) — тоже информация и тоже может описывать конструкцию. И используя полиморфизм в атрибутах можно предоставить весьма мощный и удобный инструмент, где пользователь сможет влиять как на информацию, используемую вашим инструментом, так и на определенные этапы выполнения и обработки. При этом ему не надо будет плодить классы, внедрять зависимости, стоить фабрики и их интерфейсы, которые будут создавать эти классы. Достаточно будет создать единственный класс-наследник, который инкапсулирует в себе детали работы с элементом, к которому он относится. Но, как правило, достаточно обычного «РОСО» атрибута с парой свойств.
Получение и обработка атрибута
Обработка полученных атрибутов зависит от конкретного случая и может быть сделана абсолютно по-разному. Сложно привести полезные для этого функции и приемы.
Получение атрибутов во время выполнения осуществляется с помощью рефлексии. Для получения атрибута с определенного элемента существуют различные способы.
Но все берет начало от интерфейса ICustomAttributeProvider. Его реализуют такие типы как Assembly, MemberInfo, Module, ParameterInfo. В свою очередь наследниками MemberInfo являются Type, EventInfo, FieldInfo, MethodBase, PropertyInfo.
Интерфейс имеет лишь 3 функции, и они не очень удобные. Они работают с массивами (даже если мы знаем, что атрибут может быть лишь один) и не параметризованы типом (используют object). Поэтому напрямую к функциям этого интерфейса придется обращаться редко (не сказал никогда, потому что не хочу быть категоричным). Для удобства использования существует класс CustomAttributeExtensions, в котором собрано множество методов расширения для всевозможных типов, выполняющих несложные операции по приведению типов, выборке единственного значения и так далее, тем самым освобождая разработчика от данной необходимости. Также эти методы доступны как статические в классе Attribute с полезнейшей функцией игнорирования параметра inherit (для нонконформистов).
Основные используемые функции приведены ниже. Первый параметр, указывающий какой тип расширяет метод, я опустил. Также везде, где указан параметр bool inherit существует перегрузка без него (со значением по умолчанию true). Этот параметр указывает, нужно ли учитывать при выполнении метода атрибуты родительского класса или базового метода (если используется на переопределенном методе). В случае, если в атрибуте inherit = flase, то даже установка его в true не поможет учитывать атрибуты базового класса
Название метода | Описание |
---|---|
GetCustomAttributes (bool inherit) | получает перечисление атрибутов указанного типа. Если атрибут один, вернется перечисление из 1 элемента |
GetCustomAttribute (bool inherit) | возвращает единственный атрибут указанного типа. Если таких несколько, выбрасывает исключение System.Reflection.AmbiguousMatchException: Multiple custom attributes of the same type found |
GetCustomAttributes() | возвращает перечисление атрибутов всех типов |
GetCustomAttributesData() | возвращает перечисление CustomAttributeData, в котором есть свойства позволяющие получить конструктор, параметры (именованные и позиционные), аргументы конструктора |
IsDefined(Type attrType, bool inherit) | возвращает true, если атрибут объявлен над элементом, false если нет |
Для наглядности предлагаю взглянуть на небольшое демо работы всех упомянутых функций.
Ну и для академического интереса привожу пример определения атрибутов во время выполнения. Данный код не претендует на звание самого красивого и поддерживаемого.
Основы ООП в Python — классы, объекты, методы
О ОП — самая используемая парадигма программирования. Это одновременно и особый способ мышления, и отдельная методика. Её концепцию проще всего понимать на примерах из реальной жизни. И это неспроста. Объектно-ориентированное программирование помогает представлять содержимое программы наиболее естественным для нашего мира способом.
Главным понятием ООП является понятие программного объекта. Вообще говоря, большинство сущностей на планете Земля — это некие объекты. И с частью из них мы взаимодействуем при помощи программирования. Банковский счёт, персонаж компьютерной игры или анимированный виджет сайта — всё это легко представить в виде объектов. Можно сказать, что объектно-ориентированное программирование позволяет смоделировать реальный объект в виде программного.
Множество объектов со схожими свойствами формируются в классы. Идея класса также является одной из основополагающих концепций ООП. Со стороны программы, класс — это всего лишь тип данных, но для программиста это куда более глубокая абстрактная структура. Но перейдём уже к конкретике.
💁♂️ Итак, мы — разработчики игр. Наша студия трудится над новым автосимулятором. В игре будут представлены разные виды транспорта: легковые автомобили, гоночные, грузовые и пассажирские. Все их можно описать одним словом — автотранспорт. Сделав это, мы абстрагировались от деталей и, таким образом, определили класс. Объектом этого класса может быть, как Бьюик 1968-го года, так и грузовой Freightliner Columbia желтого цвета.
У класса есть свойства и функции (в ООП их называют методами).
Свойствами класса «автотранспорт» могут быть, например: год выпуска, вид и цвет. На уровне объектов это будет выглядеть так: Бьюик Электра — это объект класса «Автотранспорт» со следующими свойствами:
Можно сказать, что объект — это вполне конкретный экземпляр класса
Помимо физических атрибутов, которые описывают внешний вид и характеристики транспортного средства, автомобили обладают между собой и другими фундаментальными сходствами. Например, все они могут ехать, тормозить, переключать скорости, поворачивать и сигналить. В нашем случае, всё это — методы класса «Автотранспорт». То есть действия, которые любые объекты данного класса могут выполнять.
Мы разрабатываем игру, поэтому предполагается, что машины в ней будут исправными. Значит, вполне естественно, что каждая из них может ехать и тормозить.
В Питоне класс «Автотранспорт» может выглядеть так:
# класс автотранспорт class MotorTransport(object): def __init__(self, color, year, auto_type): self.color = color self.year = year self.auto_type = auto_type # тормозить def stop(self): print(«Pressing the brake pedal») # ехать def drive(self): print(‘WRRRRRUM!’)
Теперь никто не помешает нам получить собственную красную феррари. Пусть и в симуляторе.
# создадим объект класса Автотранспорт ferrari_testarossa = MotorTransport(‘Red’, 1987, ‘passenger car’) # жмём на газ и вперёд! ferrari_testarossa.drive() > WRRRRRUM!
Принципы ООП
Абстракция
Абстракция — это выделение основных, наиболее значимых характеристик объекта и игнорирование второстепенных.
Любой составной объект реального мира — это абстракция. Говоря «ноутбук», вам не требуется дальнейших пояснений, вроде того, что это организованный набор пластика, металла, жидкокристаллического дисплея и микросхем. Абстракция позволяет игнорировать нерелевантные детали, поэтому для нашего сознания это один из главных способов справляться со сложностью реального мира. Если б, подходя к холодильнику, вы должны были иметь дело с отдельно металлом корпуса, пластиковыми фрагментами, лакокрасочным слоем и мотором, вы вряд ли смогли бы достать из морозилки замороженную клубнику.
Полиморфизм
Наследование
Это способность одного класса расширять понятие другого, и главный механизм повторного использования кода в ООП. Вернёмся к нашему автосимулятору. На уровне абстракции «Автотранспорт» мы не учитываем особенности каждого конкретного вида транспортного средства, а рассматриваем их «в целом». Если же более детализировано приглядеться, например, к грузовикам, то окажется, что у них есть такие свойства и возможности, которых нет ни у легковых, ни у пассажирских машин. Но, при этом, они всё ещё обладают всеми другими характеристиками, присущими автотранспорту.
Мы могли бы сделать отдельный класс «Грузовик», который является наследником «Автотранспорта». Объекты этого класса могли бы определять все прошлые атрибуты (цвет, год выпуска), но и получить новые. Для грузовиков это могли быть грузоподъёмность, снаряженная масса и наличие жилого отсека в кабине. А методом, который есть только у грузовиков, могла быть функция сцепления и отцепления прицепа.
Инкапсуляция
Инкапсуляция — это ещё один принцип, который нужен для безопасности и управления сложностью кода. Инкапсуляция блокирует доступ к деталям сложной концепции. Абстракция подразумевает возможность рассмотреть объект с общей точки зрения, а инкапсуляция не позволяет рассматривать этот объект с какой-либо другой.
Вы разработали для муниципальных служб класс «Квартира». У неё есть свойства вроде адреса, метража и высоты потолков. И методы, такие как получение информации о каждом из этих свойств и, главное, метод, реализующий постановку на учёт в Росреестре. Это готовая концепция, и вам не нужно чтобы кто-то мог добавлять методы «открыть дверь» и «получить место хранения денег». Это А) Небезопасно и Б) Избыточно, а также, в рамках выбранной реализации, не нужно. Работникам Росреестра не требуется заходить к вам домой, чтобы узнать высоту потолков — они пользуются только теми документами, которые вы сами им предоставили.
Класс
Классы, в некотором смысле, подобны чертежам: это не объекты сами по себе, а их схемы. Класс «банковских счетов» имеет строго определенные и одинаковые для всех атрибуты, но объекты в нём — сами счета — уникальны.
Как в Python создать класс
class SimpleClass: pass
Для именования классов в Python обычно используют стиль «camel case», где первая буква — заглавная.
Конструктор
Метод, который вызывается при создании объектов, в ООП зовётся конструктором. Он нужен для объектов, которые изначально должны иметь какие-то значение. Например, пустые экземпляры класса «Студент» бессмысленны, и желательно иметь хотя бы минимальный обозначенный набор вроде имени, фамилии и группы.
В качестве Питоновского конструктора выступает метод __init__() :
class Student: def __init__(self, name, surname, group): self.name = name self.surname = surname self.group = group alex = Student(«Alex», «Ivanov», «admin»)
Атрибуты класса
Поля могут быть статическими и динамическими:
☝️ Обратите внимание — статический и динамический атрибут может иметь одно и то же имя:
class MightiestWeapon: # статический атрибут name = «Default name» def __init__(self, name): # динамический атрибут self.name = name weapon = MightiestWeapon(«sword») print(MightiestWeapon.name) print(weapon.name)
Методы класса
Метод — это функция класса.
class SpaceShip: def atack(self): print(‘Пиу!’) star_destroyer = SpaceShip() star_destroyer.atack() > Пиу!
Что такое self?
🐈 Отличный пример с котофеями:
Уровни доступа атрибутов и методов
В Питоне не существует квалификаторов доступа к полям класса. Отсутствие аналогов связки public/private/protected можно рассматривать как упущение со стороны принципа инкапсуляции.
Декораторы
Декоратор — это функция-обёртка. В неё можно завернуть другой метод, и, тем самым, изменить его функциональность, не меняя код.
Объекты или экземпляры класса
Чем объекты отличаются от классов
Как уже было сказано, объект — это конкретный экземпляр класса. Все мы относимся к классу людей, но каждый из нас — уникальный объект этого класса.
Как создать объект класса в Python
Если у нас есть реализация класса, то его экземпляр создать очень просто:
class AirConditioner: def __init__(self, model, capacity): self.model = model self.capacity = capacity def turn_on(self): print(‘Now in the room will be cool’) # создадим объект класса Кондиционер ballu = AirConditioner(‘BPAC-07’, 785) ballu.turn_on() > Now in the room will be cool
Атрибуты объекта
Атрибуты класса могут быть динамическими и статическими. На уровне объекта они инициализируются так:
class MightiestWeapon: name = «Default name» def __init__(self, weapon_type): self.weapon_type = weapon_type # атрибут name можно переопределить и не создавая объекта MightiestWeapon.name = ‘Steel Sword’ print(MightiestWeapon.name) > Steal Sword # создаём объект и сразу же инициализируем динамический атрибут с помощью конструктора hero_sword = MightiestWeapon(‘sword’) # и теперь, уже для конкретного объекта, можно задать имя hero_sword.name = ‘Excalibur’ # новое статическое имя по умолчанию для всего класса не изменится print(MightiestWeapon.name) > Steal Sword print(hero_sword.name) > Excalibur
Наследование
Нередко в процессе написания кода выясняется, что некоторые объекты аналогичны другим за исключением нескольких различий. Определение сходств и различий между такими объектами называется «наследованием».
# класс «Животное». Это достаточно абстрактный класс всего с одним методом «Издать звук». class Animal: def make_a_sound(self): print(«Издаёт животный звук»)
Мы все прекрасно знаем, что котики, к примеру, любят всё ронять, а собакены — рыть землю. Создадим два соответствующих класса-наследника:
# факт наследования в Python указывается при объявлении класса-наследника. # в скобках, после имени класса, указывается класс-родитель class Cat(Animal): def drop_everything(self): print(‘Вставай скорее, я всё уронил!’) class Dog(Animal): def dig_the_ground(self): print(‘Однажды я докопаюсь до ядра планеты!’)
Теперь объекты этих двух классов могут не только издавать животные звуки, но и выполнять собственные уникальные действия:
Tom = Cat() Tom.make_a_sound() > Издаёт животный звук Tom.drop_everything() > Вставай скорее, я всё уронил!
Переопределение
Сейчас у нас и кошка, и собака просто «издают животные звуки», а хотелось бы, конечно, слышать звуки, свойственные именно этим животным. Для этого существует механика переопределения. Достаточно объявить в классе-наследнике метод с тем же названием, что и в базовом классе:
class Dog(Animal): def dig_the_ground(self): print(‘Однажды я докопаюсь до ядра планеты!’) # отныне для объектов класса «Собака» будет выполняться именно эта реализация метода def make_a_sound(self): print(‘Гав-гав!’) Balto = Dog() Balto.make_a_sound() > Гав-гав!
Документирование классов
Весь код нужно комментировать и документировать. Классы — не исключение. Стоит помнить, что код вы пишите не для себя, и вполне вероятно, что написанное вами придётся поддерживать другим людям. Комментарии повышают читаемость и увеличивают легкость восприятие кода в разы, тем самым экономя время и деньги.
ООП ещё долгое время будет оставаться передовой парадигмой программирования. Но учить её полезно и по другой причине. Прямая связь объектно-ориентированного программирования с реальным миром помогает глубже понимать устройство и принципы работы, как самого языка, так и написания кода в целом.