Метод в объектно-ориентированном программировании (ООП) — это процедура, связанная с объектом , и, как правило, также сообщение . Объект состоит из данных состояния и поведения ; они составляют интерфейс , который определяет, как объект может быть использован. Метод — это поведение объекта, параметризованное пользователем.
Данные представлены как свойства объекта, а поведение представлено как методы. Например, Window
объект может иметь такие методы, как open
и close
, в то время как его состояние (открыт он или закрыт в любой момент времени) будет свойством.
В программировании на основе классов методы определяются внутри класса , а объекты являются экземплярами данного класса. Одной из важнейших возможностей, предоставляемых методом, является переопределение метода — одно и то же имя (например, area
) может использоваться для нескольких различных видов классов. Это позволяет отправляющим объектам вызывать поведения и делегировать реализацию этих поведений принимающему объекту. Метод в программировании на Java устанавливает поведение объекта класса. Например, объект может отправлять area
сообщение другому объекту, и соответствующая формула вызывается, независимо от того, является ли принимающий объект rectangle
, circle
, triangle
, и т. д.
Методы также предоставляют интерфейс, который другие классы используют для доступа к свойствам объекта и их изменения; это известно как инкапсуляция . Инкапсуляция и переопределение являются двумя основными отличительными чертами между методами и вызовами процедур. [1]
Переопределение и перегрузка методов — два наиболее существенных способа, которыми метод отличается от обычного вызова процедуры или функции. Переопределение относится к подклассу, переопределяющему реализацию метода его суперкласса. Например, findArea
может быть метод, определенный в классе формы, [2] triangle
и т. д., каждый из которых будет определять соответствующую формулу для вычисления своей площади. Идея состоит в том, чтобы рассматривать объекты как «черные ящики», чтобы изменения во внутренних компонентах объекта могли быть сделаны с минимальным влиянием на другие объекты, которые его используют. Это известно как инкапсуляция и призвано упростить поддержку и повторное использование кода.
С другой стороны, перегрузка метода относится к дифференциации кода, используемого для обработки сообщения на основе параметров метода. Если рассматривать принимающий объект как первый параметр в любом методе, то переопределение — это просто частный случай перегрузки, где выбор основан только на первом аргументе. Следующий простой пример Java иллюстрирует разницу:
Методы доступа используются для чтения значений данных объекта. Методы мутатора используются для изменения данных объекта. Методы менеджера используются для инициализации и уничтожения объектов класса, например, конструкторы и деструкторы.
Эти методы предоставляют уровень абстракции , который облегчает инкапсуляцию и модульность . Например, если класс банковского счета предоставляет getBalance()
метод доступа для извлечения текущего баланса (вместо прямого доступа к полям данных баланса), то последующие версии того же кода могут реализовать более сложный механизм для извлечения баланса (например, выборку базы данных ), без необходимости изменения зависимого кода. Концепции инкапсуляции и модульности не являются уникальными для объектно-ориентированного программирования. Действительно, во многих отношениях объектно-ориентированный подход является просто логическим расширением предыдущих парадигм, таких как абстрактные типы данных и структурное программирование . [3]
Конструктор — это метод, который вызывается в начале жизненного цикла объекта для создания и инициализации объекта, процесс, называемый конструированием (или инстанциированием ). Инициализация может включать получение ресурсов. Конструкторы могут иметь параметры, но обычно не возвращают значения в большинстве языков. Смотрите следующий пример на Java:
public class Main { String _name ; int _roll ; Main ( String name , int roll ) { // метод конструктора this . _name = name ; this . _roll = roll ; } }
Деструктор — это метод, который вызывается автоматически в конце жизненного цикла объекта, процесс, называемый Destruction . Уничтожение в большинстве языков не допускает аргументов метода деструктора или возвращаемых значений. Деструкторы могут быть реализованы таким образом, чтобы выполнять очистку и другие задачи при уничтожении объекта.
В языках со сборкой мусора , таких как Java , [4] : 26, 29 C# , [5] : 208–209 и Python , деструкторы известны как финализаторы . Они имеют схожее назначение и функцию с деструкторами, но из-за различий между языками, использующими сборку мусора, и языками с ручным управлением памятью последовательность, в которой они вызываются, отличается.
Абстрактный метод — это метод, имеющий только сигнатуру и не имеющий тела реализации . Он часто используется для указания того, что подкласс должен предоставить реализацию метода, как в абстрактном классе . Абстрактные методы используются для указания интерфейсов в некоторых языках программирования. [6]
Следующий код Java демонстрирует абстрактный класс, который необходимо расширить:
абстрактный класс Shape { abstract int area ( int h , int w ); // сигнатура абстрактного метода }
Следующий подкласс расширяет основной класс:
открытый класс Rectangle extends Shape { @Override int area ( int h , int w ) { return h * w ; } }
Если подкласс предоставляет реализацию для абстрактного метода, другой подкласс может сделать его снова абстрактным. Это называется реабстракция .
На практике это применяется редко.
В C# виртуальный метод может быть переопределен абстрактным методом. (Это также применимо к Java, где все неприватные методы являются виртуальными.)
класс IA { public virtual void M () { } } абстрактный класс IB : IA { public override abstract void M (); // разрешено }
Методы интерфейсов по умолчанию также могут быть реабстрагированы, что потребует подклассов для их реализации. (Это также применимо к Java.)
interface IA { void M () { } } interface IB : IA { abstract void IA . M (); } class C : IB { } // ошибка: класс 'C' не реализует 'IA.M'.
Методы класса — это методы, которые вызываются для класса , а не для экземпляра. Обычно они используются как часть метамодели объекта . То есть для каждого класса, определенного в метамодели, создается экземпляр объекта класса. Протоколы метамодели позволяют создавать и удалять классы. В этом смысле они предоставляют ту же функциональность, что и конструкторы и деструкторы, описанные выше. Но в некоторых языках, таких как Common Lisp Object System (CLOS), метамодель позволяет разработчику динамически изменять модель объекта во время выполнения: например, создавать новые классы, переопределять иерархию классов, изменять свойства и т. д.
Специальные методы очень специфичны для языка, и язык может не поддерживать ни одного, поддерживать некоторые или все специальные методы, определенные здесь. Компилятор языка может автоматически генерировать специальные методы по умолчанию, или программисту может быть разрешено опционально определять специальные методы. Большинство специальных методов не могут быть вызваны напрямую, но вместо этого компилятор генерирует код для их вызова в соответствующие моменты.
Статические методы должны быть релевантны всем экземплярам класса, а не какому-то конкретному экземпляру. В этом смысле они похожи на статические переменные . Примером может служить статический метод для суммирования значений всех переменных каждого экземпляра класса. Например, если бы был класс, Product
он мог бы иметь статический метод для вычисления средней цены всех продуктов.
Статический метод может быть вызван, даже если экземпляров класса пока не существует. Статические методы называются «статическими», потому что они разрешаются во время компиляции на основе класса, в котором они вызываются, а не динамически, как в случае с методами экземпляра, которые разрешаются полиморфно на основе типа объекта во время выполнения.
В Java часто используемый статический метод:
Math.max(двойная a, двойная b)
Этот статический метод не имеет владеющего объекта и не запускается на экземпляре. Он получает всю информацию из своих аргументов. [2]
Операторы копирования-присваивания определяют действия, которые должен выполнить компилятор, когда объект класса присваивается объекту класса того же типа.
Методы операторов определяют или переопределяют символы операторов и определяют операции, которые должны быть выполнены с символом и связанными параметрами метода. Пример на C++:
#include <строка> class Data { public : bool оператор < ( const Data & data ) const { return roll_ < data . roll_ ; } bool оператор == ( const Data & data ) const { return name_ == data . name_ && roll_ == data . roll_ ; } private : std :: string name_ ; int roll_ ; };
Некоторые процедурные языки были расширены объектно-ориентированными возможностями, чтобы использовать большие наборы навыков и устаревший код для этих языков, но при этом по-прежнему обеспечивать преимущества объектно-ориентированной разработки. Возможно, самым известным примером является C++ , объектно-ориентированное расширение языка программирования C. Из-за требований к проектированию для добавления объектно-ориентированной парадигмы к существующему процедурному языку, передача сообщений в C++ имеет некоторые уникальные возможности и терминологию. Например, в C++ метод известен как функция- член . C++ также имеет концепцию виртуальных функций , которые являются функциями-членами, которые могут быть переопределены в производных классах и допускают динамическую диспетчеризацию .
Виртуальные функции — это средства, с помощью которых класс C++ может достичь полиморфного поведения. Невиртуальные функции-члены или регулярные методы — это те, которые не участвуют в полиморфизме .
Пример на С++:
#include <iostream> #include <память> класс Super { public : virtual ~ Super () = default ; virtual void IAm () { std :: cout << "Я суперкласс! \n " ; } }; class Sub : public Super { public : void IAm () override { std :: cout << "Я подкласс! \n " ; } }; int main ( ) { std :: unique_ptr <Super> inst1 = std :: make_unique <Super> ( ) ; std :: unique_ptr <Super> inst2 = std :: make_unique <Sub> ( ) ; inst1 -> IAm (); // Вызовы |Super::IAm|. inst2 -> IAm (); // Вызов |Sub::IAm|. }