Объектно-ориентированное программирование (ооп)
Содержание:
- Методы
- Философия ООП ¶
- Что такое ООП
- 7.2 Понятие Тип объект в ООП .
- Что такое язык программирования?
- Что такое «полиморфизм»?
- Популярные языки объектно-ориентированного программирования
- Принцип № 1: любовь к ребенку
- Плюсы и минусы объектно-ориентированного программирования
- Наследование[]
- В чем заключаются преимущества и недостатки объектно-ориентированного подхода в программировании?
- Что такое ООП?
Методы
Методом класса называют функцию или процедуру, которая принадлежит классу или объекту. Отличие функции от процедуры в том, что функция возвращает значение, а процедура нет. В общем виде синтаксис объявления метода выглядит следующим образом:
модификатор(ы) тип_возвращаемого_значения имя_функции(аргументы)
Модификаторы определяют область видимости, принадлежность метода объекту или классу, является ли метод переопределением и т.п. Тип возвращаемого значения – это любой доступный в C# тип. В качестве типа возвращаемого значения не может использоваться ключевое слово var. Если метод не возвращает ничего, то указывается тип void. Метод может содержать ноль или более аргументов, которые также могут иметь специальные модификаторы, указывающие на то является ли аргумент входным или выходным и т.п. Более подробно про все эти аспекты будет рассказано в одном из уроков, посвященных более глубокому изучению ООП в C#. В рамках данного урока, наша задача – это на интуитивном уровне научиться принципам работы с классами в C#.
Работа с модификатором доступа
Если метод объявлен с модификатором public, то его можно использовать вне класса, например метод Printer из DemoClass
public void Printer() { Console.WriteLine($"field: {field}, Property: {Property}"); }
Такой метод может вызываться в любом месте программы у соответствующих объектов:
var d6 = new DemoClass(11) { Property = 12 }; d6.Printer(); // field: 11, Property: 12
Если мы объявим метод с модификатором private или без модификатора (тогда, по умолчанию, будет принят private), то его уже нельзя будет вызвать снаружи класса:
class DemoClass { // ... private void PrivateMethod() { Console.WriteLine($"Secret method"); } // ... }
(Код в методе Main):
var d7 = new DemoClass(); d7.PrivateMethod(); // Ошибка компиляции!!!
Но при этом внутри класса его вызвать можно:
class DemoClass { // ... public void PublicMethod() { Console.WriteLine($"Public method"); PrivateMethod(); } // ... }
Статические методы и методы объекта
Различают статические методы и методы объекта. Статические имеют модификатор static перед именем метода и принадлежат классу. Для вызова таких методов не обязательно создавать экземпляры класса, мы уже пользовались такими методами из класса Console – это методы Write и WriteLine. Для вызова метода объекта, необходимо предварительно создать экземпляр класса, пример – это метод PublicMethod и Priter у класса DemoClass. Добавим статический метод и метод класса в DemoClass
class DemoClass { // ... public static void StaticMethod() { Console.WriteLine("Message from static method"); } public void NoneStaticMethod() { Console.WriteLine("Message from non static method"); } // ... }
Вызовем эти методы из класса DemoClass в методе Main
DemoClass.StaticMethod(); // Message from static method var d8 = new DemoClass(); d8.NoneStaticMethod(); // Message from none static method
Методы принимающие аргументы и возвращающие значения
Как было сказано в начале данного раздела, методы могут принимать данные через аргументы и возвращать значения, продемонстрируем эту возможность на примере:
class DemoClass { // ... public int MulField(int value) { return field * value; } // ... }
(Код в Main):
var d8 = new DemoClass(10); Console.WriteLine($"MulField() result: {d8.MulField(2)}"); // MulField() result: 20
Философия ООП ¶
Первое, что нужно понять, ООП это лишь один из способов организации кода. С помощью объектов мы разделяем большую логику на на много маленьких, чтобы нам легче было управлять своим же кодом. Это значит, что не нужно во все места совать классы и интерфейсы. Если ООП не упрощает код, а только запутывает, то это неправильное ООП! Каждый раз спрашивайте себя: «А что я упрощу, если сделаю объект?»
Но давайте вернёмся к примеру выше и посмотрим, как можно упростить себе жизнь.
Первое, на что нужно обратить внимание, это то, что мы работаем с пользователем как с массивом:
А что будет, если свойств у пользователя будет много или ключи массива будут меняться? Тогда придётся где-то вести документацию, какие ключи можно использовать, а какие нет. И кто-то должен будет контролировать, чтобы программисты использовали только правильные ключи. Оказывается, эту задачу можно переложить на среду исполнения, если использовать ООП.
Давайте создадим класс пользователя, который на вход принимает данные пользователя и имеет функцию для возврата имени:
Исправим объявление списка:
Теперь для доступа к имени пользователя можно использовать функцию . Мы «спрятали» (инкапсулировали) структуру исходного массива с данными пользователя за функциями класса . Если исходный массив поменяется, то изменения нужно будет внести только в функции класса.
Дальше решим проблему с постраничной навигацией. Поскольку базовый массив не поддерживает такую навигацию, «спрячем» его в объект, а уже объект «научим» выдавать страницы. Для начала опишем класс коллекции:
Изменим объявление исходных данных:
Теперь можно обращаться к нашим данным так:
Осталось решить проблему с форматом вывода пользователей. Дело в том, что в этом цикле является низкоуровневой логикой, которую надо «прятать». Собственно, также является лишним звеном в этой цепочке. В идеале логику отображения нужно вынести в шаблон и передавать ему список пользователей:
Шаблон представляет из себя псевдо язык разметки, в котором в теории удобно делать вёрстку:
Основная цель таких шаблонов — разделить логику отображения от логики данных. Преимущество, которое нам дают шаблоны заметны сразу: основной код стал «чище» и проще. Ходят легенды, что подобные шаблоны могут делать дизайнеры, верстальщики, папы, мамы, кошки без участия программистов. То есть налицо разделение труда (а если вспомнить историю за 5-й класс, то это уже прогресс в развитии).
Что такое ООП
Возникло как результат развития процедурного программирования. Основой объектно-ориентированных языков являются такие принципы, как:
- инкапсуляция;
- наследование;
- полиморфизм.
Некоторые принципы, которые были изначально заложены в первые ООЯ, подверглись существенному изменению.
Примеры объектно-ориентированных языков:
- Pascal. С выходом Delphi 7 на официальном уровне стал называться Delphi. Основная область использования Object Pascal — написание прикладного ПО.
- C++ широко используется для разработки программного обеспечения, является одним из самых популярных языков. Применяется для создания ОС, прикладных программ, драйверов устройств, приложений, серверов, игр.
- Java — транслируется в байт-код, обрабатывается виртуальной машиной Java. Преимуществом такого способа выполнения является независимость от операционной системой и оборудования. Существующие семейства: Standard Edition, Enterprise Edition, Micro Edition, Card.
- JavaScript применяется в качестве языка сценариев для web-страниц. Синтаксис во многом напоминает Си и Java. Является реализацией Ecmascript. Сам Ecmascript используется в качестве основы для построения других скриптовых языков, таких как JScript, ActionScript.
- Objective-C построен на основе языка Си, а сам код Си понятен компилятору Objective-C.
- Perl — высокоуровневый интерпретируемый динамический язык общего назначения. Имеет богатые возможности для работы с текстом, изначально разработан именно для манипуляций с текстом. Сейчас используется в системном администрировании, разработке, сетевом программировании, биоинформатике и т. д.
- PHP. Аббревиатура переводится как препроцессор гипертекста. Применяется для разработки веб-приложений, в частности серверной части. С его помощью можно создавать gui-приложения с помощью пакетов PHP-GTK, PHP-Qt, WinBinder.
- Python — язык общего назначения, ориентирован на повышение производительности разработчика и читаемость кода. Был разработан проект Cython, с помощью которого осуществляется трансляция программ, написанных на Python в код на языке Си.
7.2 Понятие Тип объект в ООП .
Объект можно рассматривать как усовершенствование типа запись, в которой описание свойств и параметры моделируемой сущности дополняются методами — описаниями действий с объектом. В отличие от записи объект объявляется словом object.
Объект – конкретная реализация абстрактного типа, обладающий характеристиками состояния, поведения, индивидуальности.
Категории объектов:
-
Реальные объекты – абстракция фактического существующего объекта реального мира.
-
Роли – абстракции цели или назначения человека, части оборудования или организации.
-
Инциденты – абстракция чего-то происшедшего или случившегося (наводнение, скачёк напряжения, выборы). —
-
Взаимодействия – объекты получаемые из отношений между другими объектами (перекресток, договор, взятка). —
-
Спецификации – используется для представления правил, критериев качества, стандартов (правила дорожного движения, распорядок дня).
Отношения между объектами:
-
Отношения использования (старшинства) — каждый объект включается в отношения. Может играть 3 роли:
- Активный объект– объект может воздействовать на другие объекты, но сам не поддается воздействию (воздействующий).
- Пассивный объект – объект может только подвергаться управлению, но не выступает в роли воздействующего (исполнитель).
- Посредники – такой объект может выступать в роли воздействующего, так и в роли исполнителя (создаются для помощи воздействующим). Чем больше посредников тем легче модифицировать программу.
-
Отношения включения – один объект включает другие объекты.
Объявление класса создает только шаблон, но не конкретный объект. Чтобы создать объект класса Вох в Java, нужно воспользоваться оператором наподобие следующего:
При создании экземпляра класса, создается объект, который содержит собственную копию каждой переменной экземпляра, определенной в данном классе.
Создание объектов класса представляет собой двух этапный процесс:
-
Объявление переменной типа класса. Эта переменная не определяет объект. Она является лишь переменной, которая может ссылаться на объект:
Вох myBox;
-
Создание объекта. С помощью оператора new динамически (то есть во время выполнения) резервируется память для объекта и возвращается ссылка на него:
myBox = new Вох();
После объявления объекта класса Box, всем переменным класса присваивается значение по умолчанию для заданного типа. Для того, чтобы обратиться к переменной класса и изменить ее или получить значение, используется имя переменной объекта:
В следующем примере объявляется два объекта класса Box и каждому устанавливаются свои значения. Изменения в переменных экземпляра одного объекта не влияют на переменные экземпляра другого.
Объект – конкретная реализация абстрактного типа, обладающая следующими характеристиками: состояние, поведение и индивидуальность.
Модель состояний Мура состоит:
- Из множества состояний: каждое состояние представляет стадию в жизненном цикле объекта.
- Из множества событий: каждое событие означает инцидент, указывающий на эволюционирование.
- Из правил перехода: правило определяет, какое новое состояние достигается объектом под воздействием события.
- Из действий: операции, которые должны быть выполнены, чтобы объект перешел в какое-то состояние.
Категории объектов:
- Реальные объекты – абстракция фактического существующего объекта реального мира.
- Роли – абстракции цели или назначения человека, части оборудования или организации.
- Инциденты – абстракция чего-то происшедшего или случившегося (наводнение, скачёк напряжения, выборы).
- Взаимодействия – объекты получаемые из отношений между другими объектами (перекресток, договор, взятка).
- Спецификации – используется для представления правил, критериев качества, стандартов (правила дорожного движения, распорядок дня).
Отношения между объектами:
-
Отношения использования (старшинства) — каждый объект включается в отношения. Может играть 3 роли:
- Активный объект– объект может воздействовать на другие объекты, но сам не поддается воздействию (воздействующий).
- Пассивный объект – объект может только подвергаться управлению, но не выступает в роли воздействующего (исполнитель).
- Посредники – такой объект может выступать в роли воздействующего, так и в роли исполнителя (создаются для помощи воздействующим). Чем больше посредников тем легче модифицировать программу.
-
Отношения включения – один объект включает другие объекты.
Что такое язык программирования?
Язык программирования — это набор правил и процедур, которые позволяют программистам давать компьютерам набор инструкций для выполнения. У каждого языка программирования есть собственный синтаксис, который после изучения позволяет указывать компьютеру, какие задачи он должен выполнять.
Подумайте об этом таким образом. Английский — это язык, на котором вы можете общаться с англоговорящими. Когда вы знаете основные правила английского языка, вы можете разговаривать с любым, кто понимает эти же правила. Но компьютеры не могут понимать английский или любой другой «традиционный» язык в этом отношении.
Что такое «полиморфизм»?
Полиморфизм – это свойство системы использовать объекты с одинаковым интерфейсом без информации о типе и внутренней структуре объекта.
Полиморфная переменная, это переменная, которая может принимать значения разных типов, а полиморфная функция, это функция, у которой хотя бы один аргумент является полиморфной переменной.
Выделяют два вида полиморфных функций:
- ad hoc, функция ведет себя по разному для разных типов аргументов (например, функция — рисует по разному фигуры разных типов);
- параметрический, функция ведет себя одинаково для аргументов разных типов (например, функция — одинаково кладет в контейнер элементы разных типов).
Принцип в ООП, когда программа может использовать объекты с одинаковым интерфейсом без информации о внутреннем устройстве объекта, называется полиморфизмом.
Пример:
Давайте представим, что нам в программе нужно описать пользователя, который может пользоваться любыми моделями телефона, чтобы позвонить другому пользователю. Вот как можно это сделать:
public class User { private String name; public User(String name) { this.name = name; } public void callAnotherUser(int number, AbstractPhone phone) { // вот он полиморфизм - использование в коде абстактного типа AbstractPhone phone! phone.call(number); } }
Теперь опишем различные модели телефонов. Одна из первых моделей телефонов:
public class ThomasEdisonPhone extends AbstractPhone { public ThomasEdisonPhone(int year) { super(year); } @Override public void call(int outputNumber) { System.out.println("Вращайте ручку"); System.out.println("Сообщите номер абонента, сэр"); } @Override public void ring(int inputNumber) { System.out.println("Телефон звонит"); } }
Обычный стационарный телефон:
public class Phone extends AbstractPhone { public Phone(int year) { super(year); } @Override public void call(int outputNumber) { System.out.println("Вызываю номер" + outputNumber); } @Override public void ring(int inputNumber) { System.out.println("Телефон звонит"); } }
И, наконец, крутой видеотелефон:
public class VideoPhone extends AbstractPhone { public VideoPhone(int year) { super(year); } @Override public void call(int outputNumber) { System.out.println("Подключаю видеоканал для абонента " + outputNumber); } @Override public void ring(int inputNumber) { System.out.println("У вас входящий видеовызов..." + inputNumber); } }
Создадим объекты в методе main() и протестируем метод callAnotherUser:
AbstractPhone firstPhone = new ThomasEdisonPhone(1879); AbstractPhone phone = new Phone(1984); AbstractPhone videoPhone=new VideoPhone(2018); User user = new User("Андрей"); user.callAnotherUser(224466,firstPhone); // Вращайте ручку //Сообщите номер абонента, сэр user.callAnotherUser(224466,phone); //Вызываю номер 224466 user.callAnotherUser(224466,videoPhone); //Подключаю видеоканал для абонента 224466
Используя вызов одного и того же метода объекта user, мы получили различные результаты. Выбор конкретной реализации метода call внутри метода callAnotherUser производился динамически на основании конкретного типа вызывающего его объекта в процессе выполнения программы. В этом и заключается основное преимущество полиморфизма – выбор реализации в процессе выполнения программы.
В примерах классов телефонов, приведенных выше, мы использовали переопределение методов – прием, при котором изменяется реализация метода, определенная в базовом классе, без изменения сигнатуры метода. По сути, это является заменой метода, и именно новый метод, определенный в подклассе, вызывается при выполнении программы.
Обычно, при переопределении метода, используется аннотация @Override, которая подсказывает компилятору о необходимости проверить сигнатуры переопределяемого и переопределяющего методов.
Популярные языки объектно-ориентированного программирования
Java, Python, C ++, Lisp и Perl — всё это примеры популярных объектно-ориентированных языков программирования. Они поддерживают программирование с использованием парадигмы классов и объектов.
Пять из самых популярных объектно-ориентированных языков включают:
- Java.
- Python.
- C++.
- Ruby.
- C#.
- Java — есть повсюду, и это один из самых используемых и востребованных языков всех времён. Девиз Java — «напиши один раз, запусти где угодно», и это отражается на количестве платформ, на которых она работает, и местах её использования.
- Python — универсален и используется во многих местах. Однако Python прочно обосновался в машинном обучении и науке о данных. Это один из предпочтительных языков для этой новой и постоянно растущей области.
- C ++ — обладает скоростью C с функциональностью классов и объектно-ориентированной парадигмой. Это скомпилированный, надежный и мощный язык. Фактически, он даже используется для создания компиляторов и интерпретаторов для других языков.
- Ruby — ещё один язык программирования общего назначения. Он был построен для простоты. С учётом сказанного, Ruby — невероятно мощный язык. Создатель Ruby, Юкихиро «Мац» Мацумото, сказал: «Ruby очень прост на вид, но очень сложен внутри, как и наше человеческое тело».
- C # — это язык программирования, разработанный Microsoft. Он был разработан для улучшения существующих концепций C. C # поддерживает платформу Microsoft.NET вместе со многими веб-приложениями, играми, настольными приложениями и мобильными приложениями.
Есть и другие объектно-ориентированные языки, которые мы не рассмотрели выше. Perl, Objective-C, Dart, Lisp, JavaScript и PHP тоже являются объектно-ориентированными или поддерживают объектно-ориентированные принципы.
Принцип № 1: любовь к ребенку
Без сомнения можно утверждать, что каждый родитель любит свое чадо
Но так важно любить ребенка всецело: принимать его недостатки, плохие стороны, понимать его слабости, толерантно относиться к его капризам. Закономерности родительского воспитания таковы, что отец и мать не хотят видеть ребенка в плохом свете, не терпят его недостатки, пытаясь «перекроить» дитя
Любовь родителей должна быть безусловной к каждому ребенку Но это относится к тем случаям, когда они не представляют собой сущность ребенка. Например, чадо гиперактивно и не может сидеть на одном месте, постоянно в поиске новых приключений. Не нужно пытаться запереть его пол семи замками в комнате, чтобы тот не влез в какую-то передрягу. Любящие родители не увидят в чрезмерной активности недостаток: они найдут ей полезное применение, записав сына или дочь в спортивную секцию или на интересный кружок, где эта активность будет большим преимуществом. Или же, когда ребенок очень чувствительный, ласковый и мягкий, любящие родители не прозовут его «нытиком» (особенно это касается мальчиков). Они найдут этим качествам свой выход: например, реализация в творчестве.
Любовь и уважение в семье передается по наследству
Примеров можно приводить массу. Любовь к ребенку в процессе воспитания отвечает совершенно за все. Уважая дитя, прислушиваясь к нему и стараясь его понять, родители получат личность, которая с таким же уважением и толерантностью относится к окружающему миру. Очень часто ребенок не может достучаться до мамы или папы, рассказать о том, что его тревожит или высказать свои желания, так как родители заняты своими делами. Уделив своему чаду немного времени, выслушав его и поняв, родители не теряют так много своего времени. Всего пару минут способны изменить многое, дать ребенку ту необходимую поддержку
Закономерности истерик и капризов ребенка таковы, что причиной служит невнимание родителей. Выслушав сына или дочь, можно искоренить такие странные, бурные проявления эмоций
Ребенок сможет узнать, что такое любовь и научиться ей только в атмосфере любви.
Плюсы и минусы объектно-ориентированного программирования
Плюсы
ООП так популярно, потому что позволяет защитить вещи от нежелательного внешнего использования. Он скрывает переменные внутри класса и, таким образом, предотвращает доступ извне. Кроме того, ООП допускает модульность (возможность разделения функций программы на независимые модули) и управление общими состояниями.
Объекты можно легко повторно использовать в другом приложении. Легко создавать новые объекты для одного и того же класса, легко поддерживать и изменять код.
В ООП есть управление памятью. Это дает большое преимущество, когда дело доходит до создания больших программ, поскольку позволяет легко разделять вещи на более мелкие части и помогает различать компоненты, которые необходимо выполнить определенным образом.
Минусы
ООП нельзя использовать повторно. Поскольку некоторые функции зависят от класса, который их использует, их трудно использовать с другим классом. Кроме того, он менее эффективен, и с ним сложнее справиться. Многие объектно-ориентированные программы также предназначены для моделирования массивных архитектур и могут быть сложными.
Наследование[]
Наследование — один из четырёх важнейших механизмов объектно-ориентированного программирования (наряду с инкапсуляцией, полиморфизмом и абстракцией), позволяющий описать новый класс на основе уже существующего (родительского), при этом свойства и функциональность родительского класса заимствуются новым классом.
Другими словами, класс-наследник реализует спецификацию уже существующего класса (базовый класс). Это позволяет обращаться с объектами класса-наследника точно так же, как с объектами базового класса.
Простое наследование:
Класс, от которого произошло наследование, называется базовым или родительским (англ. base class). Классы, которые произошли от базового, называются потомками, наследниками или производными классами (англ. derived class).
В некоторых языках используются абстрактные классы. Абстрактный класс — это класс, содержащий хотя бы один абстрактный метод, он описан в программе, имеет поля, методы и не может использоваться для непосредственного создания объекта. То есть от абстрактного класса можно только наследовать. Объекты создаются только на основе производных классов, наследованных от абстрактного. Например, абстрактным классом может быть базовый класс «сотрудник вуза», от которого наследуются классы «аспирант», «профессор» и т. д. Так как производные классы имеют общие поля и функции (например, поле «год рождения»), то эти члены класса могут быть описаны в базовом классе. В программе создаются объекты на основе классов «аспирант», «профессор», но нет смысла создавать объект на основе класса «сотрудник вуза».
Множественное наследование
При множественном наследовании у класса может быть более одного предка. В этом случае класс наследует методы всех предков. Достоинства такого подхода в большей гибкости. Множественное наследование реализовано в C++. Из других языков, предоставляющих эту возможность, можно отметить Python и Эйфель. Множественное наследование поддерживается в языке UML.
Множественное наследование — потенциальный источник ошибок, которые могут возникнуть из-за наличия одинаковых имен методов в предках. В языках, которые позиционируются как наследники C++ (Java, C# и др.), от множественного наследования было решено отказаться в пользу интерфейсов. Практически всегда можно обойтись без использования данного механизма. Однако, если такая необходимость все-таки возникла, то, для разрешения конфликтов использования наследованных методов с одинаковыми именами, возможно, например, применить операцию расширения видимости — «::» — для вызова конкретного метода конкретного родителя.
Попытка решения проблемы наличия одинаковых имен методов в предках была предпринята в языке Эйфель, в котором при описании нового класса необходимо явно указывать импортируемые члены каждого из наследуемых классов и их именование в дочернем классе.
Большинство современных объектно-ориентированных языков программирования (C#, Java, Delphi и др.) поддерживают возможность одновременно наследоваться от класса-предка и реализовать методы нескольких интерфейсов одним и тем же классом. Этот механизм позволяет во многом заменить множественное наследование — методы интерфейсов необходимо переопределять явно, что исключает ошибки при наследовании функциональности одинаковых методов различных классов-предков.
В чем заключаются преимущества и недостатки объектно-ориентированного подхода в программировании?
Преимущества:
- Объектная модель вполне естественна, поскольку в первую очередь ориентирована на человеческое восприятие мира, а не на компьютерную реализацию.
- Классы позволяют проводить конструирование из полезных компонентов, обладающих простыми инструментами, что позволяет абстрагироваться от деталей реализации.
- Данные и операции над ними образуют определенную сущность, и они не разносятся по всей программе, как нередко бывает в случае процедурного программирования, а описываются вместе. Локализация кода и данных улучшает наглядность и удобство сопровождения программного обеспечения.
- Инкапсуляция позволяет привнести свойство модульности, что облегчает распараллеливание выполнения задачи между несколькими исполнителями и обновление версий отдельных компонентов.
- Возможность создавать расширяемые системы.
- Использование полиморфизма оказывается полезным при:
- Обработке разнородных структур данных. Программы могут работать, не различая вида объектов, что существенно упрощает код. Новые виды могут быть добавлены в любой момент.
- Изменении поведения во время исполнения. На этапе исполнения один объект может быть заменен другим, что позволяет легко, без изменения кода, адаптировать алгоритм в зависимости от того, какой используется объект.
- Реализации работы с наследниками. Алгоритмы можно обобщить настолько, что они уже смогут работать более чем с одним видом объектов.
- Возможности описать независимые от приложения части предметной области в виде набора универсальных классов, или фреймворка, который в дальнейшем будет расширен за счет добавления частей, специфичных для конкретного приложения.
- Повторное использование кода:
- Сокращается время на разработку, которое может быть отдано другим задачам.
- Компоненты многоразового использования обычно содержат гораздо меньше ошибок, чем вновь разработанные, ведь они уже не раз подвергались проверке.
- Когда некий компонент используется сразу несколькими клиентами, улучшения, вносимые в его код, одновременно оказывают положительное влияние и на множество работающих с ним программ.
- Если программа опирается на стандартные компоненты, ее структура и пользовательский интерфейс становятся более унифицированными, что облегчает ее понимание и упрощает использование.
Недостатки:
- В сложных иерархиях классов поля и методы обычно наследуются с разных уровней. И не всегда легко определить, какие поля и методы фактически относятся к данному классу.
- Код для обработки сообщения иногда «размазан» по многим методам (иначе говоря, обработка сообщения требует не одного, а многих методов, которые могут быть описаны в разных классах).
- Документирование классов — задача более трудная, чем это было в случае процедур и модулей. Поскольку любой метод может быть переопределен, в документации должно говориться не только о том, что делает данный метод, но и о том, в каком контексте он вызывается.
- Неэффективность и неэкономное распределения памяти на этапе выполнения (по причине издержек на динамическое связывание и проверки типов на этапе выполнения).
- Излишняя универсальность. Часто содержится больше методов, чем это реально необходимо текущей программе. А поскольку лишние методы не могут быть удалены, они становятся мертвым грузом.
Что такое ООП?
Я подойду к вопросу с редукционистских позиций. Есть много правильных определений ООП которые покрывают множество концепций, принципов, техник, паттернов и философий. Я намерен проигнорировать их и сосредоточиться на самой соли. Редукционизм тут нужен из-за того, что всё это богатство возможностей, окружающее ООП на самом деле не является чем-то специфичным для ООП; это просто часть богатства возможностей встречающихся в разработке программного обеспечения в целом. Тут я сосредоточусь на части ООП, которая является определяющей и неустранимой.
Посмотрите на два выражения:
1: f(o); 2: o.f();
В чём разница?
Никакой семантической разницы явно нет. Вся разница целиком и полностью в синтаксисе. Но одно выглядит процедурным, а другое объектно ориентированным. Это потому что мы привыкли к тому, что для выражения 2. неявно подразумевается особая семантика поведения, которой нет у выражения 1. Эта особая семантика поведения — полиморфизм.
Когда мы видим выражение 1. мы видим функцию f, которая вызывается в которую передаётся объект o. При этом подразумевается, что есть только одна функция с именем f, и не факт, что она является членом стандартной когорты функций, окружающих o.
С другой стороны, когда мы видим выражение 2. мы видим объект с именем o которому посылают сообщение с именем f. Мы ожидаем, что могут быть другие виды объектов, котоые принимают сообщение f и поэтому мы не знаем, какого конкретно поведения ожидать от f после вызова. Поведение зависит от типа o. то есть f — полиморфен.
Вот этот факт, что мы ожидаем от методов полиморфного поведения — суть объектно ориентированного программирования. Это редукционистское определение и это свойство неустранимо из ООП. ООП без полиморфизма это не ООП. Все другие свойства ООП, такие как инкапсуляция данных и методы привязанные к этим данным и даже наследование имеют больше отношения к выражению 1. чем к выражению 2.
Программисты, использующие Си и Паскаль (и до некоторой степени даже Фортран и Кобол) всегда создавали системы инкапсулированных функций и структур. Чтобы создать такие структуры даже не нужен объектно ориентированный язык программирования. Инкапсуляция и даже простое наследование в таких языках очевидны и естественны. (В Си и Паскале более естественно, чем в других)
Поэтому то, что действительно отличает ООП программы от не ООП программ это полиморфизм.
Возможно вы захотите возразить, что полифорфизм можно сделать просто используя внутри f switch или длинные цепочки if/else. Это правда, поэтому мне нужно задать для ООП ещё одно ограничение.
Использование полиморфизма не должно создавать зависимости вызывающего от вызываемого.
Чтобы это объяснить, давайте ещё раз посмотрим на выражения. Выражение 1: f(o), похоже зависит от функции f на уровне исходного кода. Мы делаем такой вывод потому что мы также предполагаем, что f только одна и что поэтому вызывающий должен знать о вызываемом.
Однако, когда мы смотрим на Выражение 2. o.f() мы предполагаем что-то другое. Мы знаем, что может быть много реализаций f и мы не знаем какая из этих функций f будет вызвана на самом деле. Следовательно исходный код, содержащий выражение 2 не зависит от вызываемой функции на уровне исходного кода.
Если конкретнее, то это означает, что модули (файлы с исходным кодом), которые содержат полиморфные вызовы функций не должны ссылаться на модули (файлы с исходным кодом), которые содержат реализацию этих функций. Не может быть никаких include или use или require или каких-то других ключевых слов, которые создают зависимость одних файлов с исходным кодом от других.
Итак, наше редукционистское определение ООП это: