Elettracompany.com

Компьютерный справочник
0 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Объектно ориентированное программирование для начинающих

Введение в ООП с примерами на C#. Часть первая. Все, что нужно знать о полиморфизме

Введение в ООП с примерами на C#. Часть первая. Все, что нужно знать о полиморфизме

  • Переводы , 14 июля 2016 в 21:00
  • Пётр Соковых

Я много писал на смежные темы, вроде концепции MVC, Entity Framework, паттерна «Репозиторий» и т.п. Моим приоритетом всегда было полное раскрытие темы, чтобы читателю не приходилось гуглить недостающие детали. Этот цикл статей опишет абсолютно все концепции ООП, которые могут интересовать начинающих разработчиков. Однако эта статья предназначена не только для тех, кто начинает свой путь в программировании: она написана и для опытных программистов, которым может потребоваться освежить свои знания.

Сразу скажу, далеко в теорию мы вдаваться не будем — нас интересуют специфичные вопросы. Где это будет нужно, я буду сопровождать повествование кодом на C#.

Что такое ООП и в чём его плюсы?

«ООП» значит «Объектно-Ориентированное Программирование». Это такой подход к написанию программ, который основывается на объектах, а не на функциях и процедурах. Эта модель ставит в центр внимания объекты, а не действия, данные, а не логику. Объект — реализация класса. Все реализации одного класса похожи друг на друга, но могут иметь разные параметры и значения. Объекты могут задействовать методы, специфичные для них.

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

Что за концепции ООП?

Сейчас коротко о принципах, которые мы позже рассмотрим в подробностях:

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

Ладно, тут мы коснулись большого количества теории, настало время действовать. Я надеюсь, это будет интересно.

Полиморфизм

В этой статье мы рассмотрим буквально все сценарии использования полиморфизма, использование параметров и разные возможные типы мышления во время написания кода.

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

  • Давайте создадим консольное приложение InheritanceAndPolymorphism и класс Overload.cs с тремя методами DisplayOverload с параметрами, как ниже:

В главном методе Program.cs теперь напишем следующее:

И теперь, когда мы это запустим, вывод будет следующим:

DisplayOverload 100
DisplayOverload method overloading
DisplayOverload method overloading100

Класс Overload содержит три метода, и все они называются DisplayOverload , они различаются только типами параметров. В C# (как и в большистве других языков) мы можем создавать методы с одинаковыми именами, но разными параметрами, это и называется «перегрузка методов». Это значит, что нам нет нужды запоминать кучу имён методов, которые совершают одинаковые действия с разными типами данных.

Что нужно запомнить: метод идентифицируется не только по имени, но и по его параметрам.
Если же мы запустим следующий код:

Мы получим ошибку компиляции:

Error: Type ‘InheritanceAndPolymorphism.Overload’ already defines a member called ‘DisplayOverload’ with the same parameter types

Здесь вы можете видеть две функции, которые различаются только по возвращаемому типу, и скомпилировать это нельзя.

Что нужно запомнить: метод не идентифицируется по возвращаемому типу, это не полиморфизм.
Если мы попробуем скомпилировать

…то у нас это не получится:

Error: Type ‘InheritanceAndPolymorphism.Overload’ already defines a member called ‘DisplayOverload’ with the same parameter types

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

Что нужно запомнить: модификаторы вроде static также не являются свойствами, идентифицирующими метод.
Если мы запустим нижеследующий код, в надежде, что теперь-то идентификаторы у методов будут разными:

То нас ждёт разочарование:

Error: Cannot define overloaded method ‘DisplayOverload’ because it differs from another method only on ref and out

Что нужно запомнить: на идентификатор метода оказывают влияние только его имя и параметры (их тип, количество). Модификаторы доступа не влияют. Двух методов с одинаковыми идентификаторами существовать не может.

Роль ключевого слова params в полиморфизме

Параметры могут быть четырёх разных видов:

  • переданное значение;
  • преданная ссылка;
  • параметр для вывода;
  • массив параметров.

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

  • Если мы запустим следующий код:

То получим две ошибки:

Error1: The parameter name ‘a’ is a duplicate

Error2: A local variable named ‘a’ cannot be declared in this scope because it would give a different meaning to ‘a’, which is already used in a ‘parent or current’ scope to denote something else

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

  • Теперь попробуем запустить следующий код:

Overload.cs

Program.cs

Мы получим следующий вывод:

Akhil
Akhil 1
Akhil 2
Akhil 3

Мы можем передавать одинаковые ссылочные параметры столько раз, сколько захотим. В методе Display строка name имеет значение «Akhil». Когда мы меняем значение x на «Akhil1», на самом деле мы меняем значение name , т.к. через параметр x передана ссылка именно на него. То же и с y — все эти три переменных ссылаются на одно место в памяти.

Overload.cs

Program.cs

Это даст нам такой вывод:

Akhil 100
Mittal 100
OOP 100
Akhil 200

Нам часто может потребоваться передать методу n параметров. В C# такую возможность предоставляет ключевое слово params .

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

    В случае DisplayOverload первый аргумент должен быть целым числом, а остальные — сколь угодно много строк или наоборот, ни одной.

200 100
300 100
100 200

Важно запомнить: C# достаточно умён, чтоб разделить обычные параметры и массив параметров, даже если они одного типа.

    Посмотрите на следующие два метода:

    Разница между ними в том, что первый запустится, и такая синтаксическая конструкция будет подразумевать, что в метод будет передаваться n массивов строк. Вторая же выдаст ошибку:

    Error: The parameter array must be a single dimensional array

    Запомните: массив параметров должен быть одномерным.

  • Следует упомянуть, что последний аргумент не обязательно заполнять отдельными объектами, можно его использовать, будто это обычный аргумент, принимающий массив, то есть:

Overload.cs

Program.cs

Вывод будет следующим:

Akhil 3
Ekta 3
Arsh 3

Однако такой код:

Уже вызовет ошибку:

Error: The best overloaded method match for ‘InheritanceAndPolymorphism.Overload.DisplayOverload(int, params string[])’ has some invalid arguments

Error:Argument 2: cannot convert from ‘string[]’ to ‘string’

Думаю, тут всё понятно — или, или. Смешивать передачу отдельными параметрами и одним массивом нельзя.

  • Теперь рассмотрим поведение следующей программы:

Overload.cs

Program.cs

После её выполнения мы получим в консоли:

Это происходит из-за того, что при подобном синтаксисе массив передаётся по ссылке. Однако стоит отметить следующую особенность:

Результатом выполнения такого кода будет

Ведь из переданных параметров C# автоматически формирует новый, временный массив.

  • Теперь поговорим о приоритете языка в выборе методов. Предположим, у нас есть такой код:

C# рассматривает методы с массивом параметров последними, так что во втором случае будет вызван метод, принимающий два целых числа. В первом и третьем случае будет вызван метод с params , так как ничего кроме него запустить невозможно. Таким образом, на выходе мы получим:

parameterArray
The two integers 200 300
parameterArray

  • Теперь кое-что интересное. Как вы думаете, каким будет результат выполнения следующей программы?

Overload.cs

Program.cs

В консоли мы увидим:

System.Int32 System.String System.Double
System.Object[] System.Object[] System.Int32 System.String System.Double

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

В заключение

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

  • Метод идентифицируется не только по имени, но и по его параметрам.
  • Метод не идентифицируется по возвращаемому типу.
  • Модификаторы вроде static также не являются свойствами, идентифицирующими метод.
  • На идентификатор метода оказывают влияние только его имя и параметры (их тип, количество). Модификаторы доступа не влияют. Двух методов с одинаковыми идентификаторами существовать не может.
  • Имена параметров должны быть уникальны. Также не могут быть одинаковыми имя параметра метода и имя переменной, созданной в этом же методе.
  • Ключевое слово params может быть применено только к последнему аргументу метода.
  • C# достаточно умён, чтоб разделить обычные параметры и массив параметров, даже если они одного типа.
  • Массив параметров должен быть одномерным.

Основы работы с ООП в PHP. Часть 1

Учебник PHP

Практика

Важное

Регулярки

Работа с htaccess

Файлы, папки

Сессии и куки

Работа с БД

Практика по работе с БД в PHP

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

Практика

Движок PHP

Продвинутые БД

Аутентификация

Практика

ООП и MVC

Сейчас мы с вами займемся изучением ООП в PHP.

Что такое объекты в жизни

Давайте рассмотрим пример из жизни, а потом перенесем его на PHP. Пусть у нас есть автомобиль. У него есть колеса, цвет, вид кузова, объем двигателя и так далее. Кроме того, водитель может отдавать ему команды: ехать, остановится, повернуть направо, налево и тп.

Можно говорить о том, что существует некоторый класс автомобилей, обладающий общими свойствами (у всех есть колеса и всем им можно отдавать команды).

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

Если бы мы программировали автомобиль, то можно было бы сделать что-то вроде такого:

Что в этом коде? Первой строчкой мы создаем объект $мояМашина — представителя класса Автомобиль. У нашей машины появляются свойства и методы, характерные для всех автомобилей — и мы можем отдавать ей команды. Можем также менять и свойства:

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

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

Наши первые классы и объекты

Давайте сделаем класс User, у которого будет два свойства — имя и возраст (на слово public не обращайте внимание — о нем позже):

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

Обратите внимание на то, что классы принято называть большими буквами (User), а объекты этих классов — маленькими ($user). Кроме того, можно писать new User, а можно — new User() — разницы нет.

Давайте теперь поменяем свойства нашего объекта, а потом выведем их на экран. Обращение к свойствам происходит через стрелочку «->» — имяОбъекта->егоСвойство:

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

Давайте теперь сделаем 2 юзера (объекта) ‘Коля’ и ‘Вася’, заполним их данными и выведем на экран сумму их возрастов:

Методы

Перейдем теперь к методам. Методы — это по сути функции (обычные функции function, к которым вы привыкли), которые может вызывать каждый объект.

При написании кода разница между методами и свойствами в том, что для методов надо писать круглые скобки в конце, а для свойств — не надо. Пример: $user->name — свойство, а $user->getName() — метод, который что-то делает.

Метод — это практически обычная функция, он может принимать параметры так же, как и все функции.

Давайте потренируемся — сделаем бесполезный тренировочный метод ->show(), который будет выводить ‘. ‘:

Пусть теперь наш метод ->show() выводит нечто полезное — имя пользователя. Это имя можно получить в нашем методе — вопрос как. Если снаружи мы легко можем обратиться к имени через $user->name, то внутри класса следует делать так: $this->name.

Переменная $this указывает на объект класса и предназначена для использования внутри класса.

Итак, сделаем так, чтобы метод show возвращал имя юзера:

Итак, с помощью переменной $this мы можем обратиться к свойствам объекта внутри самого кода объекта. Почему мы не можем обратиться к ним, например, так: $user->name? Потому что переменной $user не существует в момент написания кода класса да и сам класс ничего не знает ни про какую переменную $user.

Через $this можно обращаться не только к свойствам объекта, но и к его методам. Давайте сделаем два метода — метод show(), который будет использовать внутри себя метод getName():

Сделаем что-нибудь полезное с методами

Давайте расширим наш класс User — сделаем метод addYearsToAge, который будет добавлять заданное количество лет к возрасту:

Разбираем public и private

Давайте теперь разберем, что делает ключевое слово public, которое мы писали перед всеми свойствами и методами.

Ключевое слово public указывает на то, что данные свойства и методы доступны извне (вне кода класса). В противоположность public есть ключевое слово private, которое указывает на то, что свойства и методы недоступны извне.

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

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

То же самое касается и свойств. Некоторые свойства выполняют чисто вспомогательную функцию, и не должны быть доступны вне класса, иначе мы их можем случайно поредактировать и сломать наш код.

Давайте попробуем — объявим свойства $name и $age приватными и попытаемся обратиться к ним снаружи — мы сразу увидим ошибку PHP:

Геттеры и сеттеры

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

Например, у нас есть свойство ->name — сделаем его приватным, а взамен сделаем метод getName(), который будет возвращать содержимое этого свойства во внешний мир, и метод setName(новое имя), который будет менять значение этого свойства.

Аналогично поступим с возрастом и вот, что у нас получится:

Давайте теперь разбираться, зачем это нужно.

Во-первых, таким образом мы можем создавать свойства только для чтения. Например, возраст можно будет только получить через getAge, но нельзя будет поменять без setAge.

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

Во-третьих, мы можем проверять, к примеру, новые значения возраста — а вдруг кто-то пытается установить некорректный возраст — реализуем проверку возраста в setAge и будем изменять свойство age, только если возраст корректный.

Такие методы, как мы реализовали, называются геттерами и сеттерами.

Рассмотрим private методы

Давайте для сеттера setAge реализуем вспомогательный private метод checkAge, который будет проверять возраст на корректность перед его установкой:

Метод __construct

Существует специальный встроенный метод __construct. Это метод (если мы его написали в коде) будет вызываться при создании объекта. Пример:

В __construct можно также передавать параметры:

Чем может быть полезен этот метод? Давайте, для примера, сделаем объект User, для которого в момент создания объекта будут указываться имя и возраст. Эти значение в дальнейшем нельзя будет поменять, а только прочитать (геттеры сделаем, а сеттеры нет):

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

Представьте, что у вас есть класс User. Он нужен вам для каких-то целей и в общем-то полностью нас устраивает — доработки этому классу в общем-то не нужны.

А теперь представим себе ситуацию, когда нам понадобился еще и класс Worker (работник). Работник очень похож на юзера, имеет те же свойства и методы, но еще и добавляет свои — свойство $salary (зарплата), а также геттеры и сеттеры для этого свойства.

Что же делать в этом случае? Глупо копировать код класса User и вставлять его в Worker — дублирование кода мы не любим.

Лучше всего будет унаследовать класс Worker от класса User — в этом случае у работника будут все свойства и методы родительского класса (кроме private!) и он спокойно сможет добавить к ним свои.

Наследование реализуется с помощью ключевого слова extends, вот так: class Worker extends User.

Вот реализация класса Worker:

Пусть кроме работника нам нужен еще и студент Student — унаследуем его от User:

Ключевое слово protected

Я уже писал выше, что private свойства и методы не наследуются. Это не мешает работать public геттерам и сеттерам, унаследованным от User, например: $student->setName() работает, но напрямую получить свойство name внутри класса потомка мы не сможем — это приведет к ошибке.

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

Давайте в классе Student реализуем метод addOneYear — он будет добавлять 1 год к свойству age. Однако, если в классе User свойство age оставить приватным — мы увидим ошибку:

Для исправления ошибки поправим класс User — сделаем его свойства protected, а не private:

Перегрузка и parent::

Пусть у нас есть класс Student, наследующий от класса User метод setAge. Предположим, что этот метод setAge от родителя нам чем-то не подходит и мы хотим написать свою реализацию в классе-потомке. Так можно делать (это называется перегрузка).

Давайте напишем студент свой setAge в классе Student. Наш setAge будет проверять то, что возраст студента меньше 25 лет:

Пусть мы затерли (перегрузили) метод родителя, но хотели бы использовать и его. То есть мы хотим иметь в классе Student свой метод setAge а также метод родителя setAge. В этом случае к методу родителя можно обратиться так: parent::setAge().

Давайте доработаем наш класс Student так, чтобы использовался метод setAge родителя (убираем некоторое дублирование кода таким образом):

На использование классов внутри других классов

Бывает такое, что мы хотели бы использовать методы одного класса внутри другого, но не хотели бы наследовать от этого класса.

Пусть, к примеру, у нас есть класс User, который хочет использовать класс работы с базами данных Db. Создадим внутри User новый объект класса Db, запишем его в любую переменную, например, в $this->db и будем спокойно использовать public методы и свойства класса Db:

Некоторая практика

Давайте напишем реализацию класса Db и класса User. Попробуйте сами разберитесь в этом коде:

Переменные названия свойств и методов

Названия свойств и методов можно хранить в переменной. К примеру, есть переменная $var, в которой лежит строка ‘name’. Тогда обращение $user->$var по сути эквивалентно обращению $user->name.

Иногда такие финты полезны.

Что вам делать дальше:

Приступайте к решению задач по следующей ссылке: задачи к уроку.

Когда все решите — переходите к изучению новой темы.

Что такое ООП на примерах. Для чайников

Наверное, в половине вакансий(если не больше), требуется знание и понимание ООП. Да, эта методология, однозначно, покорила многих программистов! Обычно понимание ООП приходит с опытом, поскольку годных и доступно изложенных материалов на данный счет практически нет. А если даже и есть, то далеко не факт, что на них наткнутся читатели. Надеюсь, у меня получится объяснить принципы этой замечательной методологии, как говорится, на пальцах.

Итак, уже в начале статьи я уже упомянул такой термин «методология». Применительно к программированию этот термин подразумевает наличие какого-либо набора способов организации кода, методов его написания, придерживаясь которых, программист сможет писать вполне годные программы.

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

Об объектах и классах

Класс — это такая структура данных, которую может формировать сам программист. В терминах ООП, класс состоит из полей (по-простому — переменных) и методов (по-простому — функций). И, как выяснилось, сочетание данных и функций работы над ними в одной структуре дает невообразимую мощь. Объект — это конкретный экземпляр класса. Придерживаясь аналогии класса со структурой данных, объект — это конкретная структура данных, у которой полям присвоены какие-то значения. Поясню на примере:

Допустим, нам нужно написать программу, рассчитывающую периметр и площадь треугольника, который задан двумя сторонами и углом между ними. Для написания такой программы используя ООП, нам необходимо будет создать класс (то есть структуру) Треугольник. Класс Треугольник будет хранить три поля (три переменные): сторона А, сторона Б, угол между ними; и два метода (две функции): посчитать периметр, посчитать площадь. Данным классом мы можем описать любой треугольник и вычислить периметр и площадь. Так вот, конкретный треугольник с конкретными сторонами и углом между ними будет называться экземпляром класса Треугольник. Таким образом класс — это шаблон, а экземпляр — конкретная реализация шаблона. А вот уже экземпляры являются объектами, то есть конкретными элементами, хранящими конкретные значения.

Одним из самых распространенных объектно-ориентированных языков программирования является язык java. Там без использования объектов просто не обойтись. Вот как будет выглядеть код класса, описывающего треугольник на этом языке:

Если мы внутрь класса добавим следующий код:

то программу уже можно будет запускать на выполнение. Это особенность языка java. Если в классе есть такой метод

то этот класс можно выполнять. Разберем код подробнее. Начнем со строки

Здесь мы создаем экземпляр triangle1 класса Triangle и тут же задаем ему параметры сторон и угла между ними. При этом, вызывается специальный метод, называемый конструктор и заполняет поля объекта переданными значениями в конструктор. Ну, а строки

выводят рассчитанные площадь треугольника и его периметр в консоль.

Аналогично все происходит и для второго экземпляра класса Triangle .

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

Еще раз, самое важное:

ООП — это способ организации кода программы;

Класс — это пользовательская структура данных, которая воедино объединяет данные и функции для работы с ними(поля класса и методы класса);

Объект — это конкретный экземпляр класса, полям которого заданы конкретные значения.

Три волшебных слова

ООП включает три ключевых подхода: наследование, инкапсуляцию и полиморфизм. Для начала, приведу определения из wikipedia:

Инкапсуляция — свойство системы, позволяющее объединить данные и методы, работающие с ними, в классе. Некоторые языки (например, С++) отождествляют инкапсуляцию с сокрытием, но большинство (Smalltalk, Eiffel, OCaml) различают эти понятия.

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

Полиморфизм — свойство системы, позволяющее использовать объекты с одинаковым интерфейсом без информации о типе и внутренней структуре объекта.

Понять, что же все эти определения означают на деле достаточно сложно. В специализированных книгах, раскрывающих данную тему на каждое определение, зачастую, отводится целая глава, но, как минимум, абзац. Хотя, сути того, что нужно понять и отпечатать навсегда в своем мозге программиста совсем немного.
А примером для разбора нам будут служить фигуры на плоскости. Из школьной геометрии мы знаем, что у всех фигур, описанных на плоскости, можно рассчитать периметр и площадь. Например, для точки оба параметра равны нулю. Для отрезка мы можем вычислить лишь периметр. А для квадрата, прямоугольника или треугольника — и то, и другое. Сейчас же мы опишем эту задачу в терминах ООП. Также не лишним будет уловить цепь рассуждений, которые выливаются в иерархию классов, которая , в свою очередь, воплощается в работающий код. Поехали:

Итак, точка — это самая малая геометрическая фигура, которая является основой всех прочих построений (фигур). Поэтому именно точка выбрана в качестве базового родительского класса. Напишем класс точки на java:

У получившегося класса Point пустой конструктор, поскольку в данном примере мы работаем без конкретных координат, а оперируем только параметрами значениями сторон. Так как у точки нет никаких сторон, то и передавать ей никаких параметров не надо. Также заметим, что класс имеет методы Point::getSquare() и Point::getPerimeter() для расчета площади и периметра, оба возвращают 0. Для точки оно и логично.

Поскольку у нас точка является основой всех прочих фигур, то и классы этих прочих фигур мы наследуем от класса Point . Опишем класс отрезка, наследуемого от класса точки:

означает, что класс LineSegment наследуется от класса Point . Методы LineSegment::getSquare() и LineSegment::getPerimeter() переопределяют соответствующие методы базового класса. Площадь отрезка всегда равняется нулю, а площадь периметра равняется длине этого отрезка.

Теперь, подобно классу отрезка, опишем класс треугольника(который также наследуется от класса точки):

Тут нет ничего нового. Также, методы Triangle::getSquare() и Triangle::getPerimeter() переопределяют соответствующие методы базового класса.
Ну а теперь, собственно, тот самый код, который показывает волшебство полиморифзма и раскрывает мощь ООП:

Мы создали массив объектов класса Point , а поскольку классы LineSegment и Triangle наследуются от класса Point , то и их мы можем помещать в этот массив. Получается, каждую фигуру, которая есть в массиве figures мы можем рассматривать как объект класса Point . В этом и заключается полиморфизм: неизвестно, к какому именно классу принадлежат находящиеся в массиве figures объекты, но поскольку все объекты внутри этого массива принадлежат одному базовому классу Point , то все методы, которые применимы к классу Point также и применимы к его классам-наследникам.

Теперь о инкапсуляции. То, что мы поместили в одном классе параметры фигуры и методы расчета площади и периметра — это и есть инкапсуляция, мы инкапсулировали фигуры в отдельные классы. То, что у нас для расчета периметра используется специальный метод в классе — это и есть инкапсуляцию, мы инкапсулировали расчет периметра в метод getPerimiter() . Иначе говоря, инкапсуляция — это сокрытие реализции (пожалуй, самое короткое, и в то же время емкое определением инкапсуляции).

Объектно-ориентированное программирование: на пальцах

Ста­тья не маль­чи­ка, но мужа.

Наста­ло вре­мя серьёз­ных тем: сего­дня рас­ска­жем про объектно-ориентированное про­грам­ми­ро­ва­ние, или ООП. Это тема для про­дви­ну­то­го уров­ня раз­ра­бот­ки, и мы хотим, что­бы вы его постиг­ли.

Из это­го тер­ми­на мож­но сде­лать вывод, что ООП — это такой под­ход к про­грам­ми­ро­ва­нию, где на пер­вом месте сто­ят объ­ек­ты. На самом деле там всё немно­го слож­нее, но мы до это­го ещё добе­рём­ся. Для нача­ла пого­во­рим про ООП вооб­ще и раз­бе­рём, с чего оно начи­на­ет­ся.

Обычное программирование (процедурное)

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

Напри­мер, в интернет-магазине может быть функ­ция «Про­ве­рить email». Она полу­ча­ет на вход какой-то текст, сопо­став­ля­ет со сво­и­ми пра­ви­ла­ми и выда­ёт ответ: это пра­виль­ный элек­трон­ный адрес или нет. Если пра­виль­ный, то true, если нет — то false.

Функ­ции полез­ны, когда нуж­но упа­ко­вать мно­го команд в одну. Напри­мер, про­вер­ка элек­трон­но­го адре­са может состо­ять из одной про­вер­ки на регу­ляр­ные выра­же­ния, а может содер­жать мно­же­ство команд: запро­сы в сло­ва­ри, про­вер­ку по базам спа­ме­ров и даже сопо­став­ле­ние с уже извест­ны­ми элек­трон­ны­ми адре­са­ми. В функ­цию мож­но упа­ко­вать любой ком­байн из дей­ствий и потом про­сто вызы­вать их все одним дви­же­ни­ем.

Что не так с процедурным программированием

Про­це­дур­ное про­грам­ми­ро­ва­ние иде­аль­но рабо­та­ет в про­стых про­грам­мах, где все зада­чи мож­но решить, гру­бо гово­ря, десят­ком функ­ций. Функ­ции акку­рат­но вло­же­ны друг в дру­га, вза­и­мо­дей­ству­ют друг с дру­гом, мож­но пере­дать дан­ные из одной функ­ции в дру­гую.

Напри­мер, вы пише­те функ­цию «Заре­ги­стри­ро­вать поль­зо­ва­те­ля интернет-магазина». Внут­ри неё вам нуж­но про­ве­рить его элек­трон­ный адрес. Вы вызы­ва­е­те функ­цию «Про­ве­рить email» внут­ри функ­ции «Заре­ги­стри­ро­вать поль­зо­ва­те­ля», и в зави­си­мо­сти от отве­та функ­ции вы либо реги­стри­ру­е­те поль­зо­ва­те­ля, либо выво­ди­те ошиб­ку. И у вас эта функ­ция встре­ча­ет­ся ещё в деся­ти местах. Функ­ции как бы пере­пле­те­ны.

Тут при­хо­дит продакт-менеджер и гово­рит: «Хочу, что­бы поль­зо­ва­тель точ­но знал, в чём ошиб­ка при вво­де элек­трон­но­го адре­са». Теперь вам нуж­но научить функ­цию выда­вать не про­сто true — false, а ещё и код ошиб­ки: напри­мер, если в адре­се опе­чат­ка, то код 01, если адрес спа­мер­ский — код 02 и так далее. Это неслож­но реа­ли­зо­вать.

Вы зале­за­е­те внутрь этой функ­ции и меня­е­те её пове­де­ние: теперь она вме­сто true — false выда­ёт код ошиб­ки, а если ошиб­ки нет — пишет «ОК».

И тут ваш код лома­ет­ся: все десять мест, кото­рые ожи­да­ли от про­ве­ряль­щи­ка true или false, теперь полу­ча­ют «ОК» и из-за это­го лома­ют­ся.

Теперь вам нуж­но:

  • либо пере­пи­сы­вать все функ­ции, что­бы научить их пони­мать новые отве­ты про­ве­ряль­щи­ка адре­сов;
  • либо пере­де­лать сам про­ве­ряль­щик адре­сов, что­бы он остал­ся сов­ме­сти­мым со ста­ры­ми места­ми, но в нуж­ном вам месте как-то ещё выда­вал коды оши­бок;
  • либо напи­сать новый про­ве­ряль­щик, кото­рый выда­ёт коды оши­бок, а в ста­рых местах исполь­зо­вать ста­рый про­ве­ряль­щик.

Зада­ча, конеч­но, реша­е­мая за час-другой.

Но теперь пред­ставь­те, что у вас этих функ­ций — сот­ни. И изме­не­ний в них нуж­но делать десят­ки в день. И каж­дое изме­не­ние, как пра­ви­ло, застав­ля­ет функ­ции вести себя более слож­ным обра­зом и выда­вать более слож­ный резуль­тат. И каж­дое изме­не­ние в одном месте лома­ет три дру­гих места. В ито­ге у вас будут нарож­дать­ся десят­ки кло­ни­ро­ван­ных функ­ций, в кото­рых вы сна­ча­ла буде­те раз­би­рать­ся, а потом уже нет.

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

Объектно-ориентированное программирование

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

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

Про­грам­ми­сты дого­во­ри­лись, что дан­ные внут­ри объ­ек­та будут назы­вать­ся свой­ства­ми, а функ­ции — мето­да­ми. Но это про­сто сло­ва, по сути это те же пере­мен­ные и функ­ции.

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

Объ­ек­ты харак­те­ри­зу­ют­ся четырь­мя сло­ва­ми: инкап­су­ля­ция, абстрак­ция, насле­до­ва­ние и поли­мор­физм.

Инкап­су­ля­ция — объ­ект неза­ви­сим: каж­дый объ­ект устро­ен так, что нуж­ные для него дан­ные живут внут­ри это­го объ­ек­та, а не где-то сна­ру­жи в про­грам­ме. Напри­мер, если у меня есть объ­ект «Поль­зо­ва­тель», то у меня в нём будут все дан­ные о поль­зо­ва­те­ле: и имя, и адрес, и всё осталь­ное. И в нём же будут мето­ды «Про­ве­рить адрес» или «Под­пи­сать на рас­сыл­ку».

Абстрак­ция — у объ­ек­та есть «интер­фейс»: у объ­ек­та есть мето­ды и свой­ства, к кото­рым мы можем обра­тить­ся извне это­го объ­ек­та. Так же, как мы можем нажать кноп­ку на блен­де­ре. У блен­де­ра есть мно­го все­го внут­ри, что застав­ля­ет его рабо­тать, но на глав­ной пане­ли есть толь­ко кноп­ка. Вот эта кноп­ка и есть абстракт­ный интер­фейс.

В про­грам­ме мы можем ска­зать: «Уда­лить поль­зо­ва­те­ля». На язы­ке ООП это будет «пользователь.удалить()» — то есть мы обра­ща­ем­ся к объ­ек­ту «поль­зо­ва­тель» и вызы­ва­ем метод «уда­лить». Кайф в том, что нам не так важ­но, как имен­но будет про­ис­хо­дить уда­ле­ние: ООП поз­во­ля­ет нам не думать об этом в момент обра­ще­ния.

Напри­мер, над мага­зи­ном рабо­та­ют два про­грам­ми­ста: один пишет модуль зака­за, а вто­рой — модуль достав­ки. У пер­во­го в объ­ек­те «заказ» есть метод «отме­нить». И вот вто­ро­му нуж­но из-за достав­ки отме­нить заказ. И он спо­кой­но пишет: «заказ.отменить()». Ему неваж­но, как дру­гой про­грам­мист будет реа­ли­зо­вы­вать отме­ну: какие он отпра­вит пись­ма, что запи­шет в базу дан­ных, какие выве­дет пре­ду­пре­жде­ния.

Насле­до­ва­ние — спо­соб­ность к копи­ро­ва­нию. ООП поз­во­ля­ет созда­вать мно­го объ­ек­тов по обра­зу и подо­бию дру­го­го объ­ек­та. Это поз­во­ля­ет не копи­па­стить код по две­сти раз, а один раз нор­маль­но напи­сать и потом мно­го раз исполь­зо­вать.

Напри­мер, у вас может быть некий иде­аль­ный объ­ект «Поль­зо­ва­тель»: в нём вы про­пи­сы­ва­е­те всё, что может про­ис­хо­дить с поль­зо­ва­те­лем. У вас могут быть свой­ства: имя, воз­раст, адрес, номер кар­ты. И могут быть мето­ды «Дать скид­ку», «Про­ве­рить заказ», «Най­ти зака­зы», «Позво­нить».

На осно­ве это­го иде­аль­но­го поль­зо­ва­те­ля вы може­те создать реаль­но­го «Поку­па­те­ля Ива­на». У него при созда­нии будут все свой­ства и мето­ды, кото­рые вы зада­ли у иде­аль­но­го поку­па­те­ля, плюс могут быть какие-то свои, если захо­ти­те.

Иде­аль­ные объ­ек­ты про­грам­ми­сты назы­ва­ют клас­са­ми.

Поли­мор­физм — еди­ный язык обще­ния. В ООП важ­но, что­бы все объ­ек­ты обща­лись друг с дру­гом на понят­ном им язы­ке. И если у раз­ных объ­ек­тов есть метод «Уда­лить», то он дол­жен делать имен­но это и писать­ся вез­де оди­на­ко­во. Нель­зя, что­бы у одно­го объ­ек­та это было «Уда­лить», а у дру­го­го «Сте­реть».

При этом внут­ри объ­ек­та мето­ды могут быть реа­ли­зо­ва­ны по-разному. Напри­мер, уда­лить товар — это выдать пре­ду­пре­жде­ние, а потом поме­тить товар в базе дан­ных как уда­лён­ный. А уда­лить поль­зо­ва­те­ля — это отме­нить его покуп­ки, отпи­сать от рас­сыл­ки и заар­хи­ви­ро­вать исто­рию его поку­пок. Собы­тия раз­ные, но для про­грам­ми­ста это неваж­но. У него про­сто есть метод «Уда­лить()», и он ему дове­ря­ет.

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

Плюсы и минусы ООП

У объектно-ориентированного про­грам­ми­ро­ва­ния мно­го плю­сов, и имен­но поэто­му этот под­ход исполь­зу­ет боль­шин­ство совре­мен­ных про­грам­ми­стов.

  • Визу­аль­но код ста­но­вит­ся про­ще, и его лег­че читать. Когда всё раз­би­то на объ­ек­ты и у них есть понят­ный набор пра­вил, мож­но сра­зу понять, за что отве­ча­ет каж­дый объ­ект и из чего он состо­ит.
  • Мень­ше оди­на­ко­во­го кода. Если в обыч­ном про­грам­ми­ро­ва­нии одна функ­ция счи­та­ет повто­ря­ю­щи­е­ся сим­во­лы в одно­мер­ном мас­си­ве, а дру­гая — в дву­мер­ном, то у них боль­шая часть кода будет оди­на­ко­вой. В ООП это реша­ет­ся насле­до­ва­ни­ем.
  • Слож­ные про­грам­мы пишут­ся про­ще. Каж­дую боль­шую про­грам­му мож­но раз­ло­жить на несколь­ко бло­ков, сде­лать им мини­маль­ное напол­не­ние, а потом раз за разом подроб­но напол­нить каж­дый блок.
  • Уве­ли­чи­ва­ет­ся ско­рость напи­са­ния. На стар­те мож­но быст­ро создать нуж­ные ком­по­нен­ты внут­ри про­грам­мы, что­бы полу­чить мини­маль­но рабо­та­ю­щий про­то­тип.

А теперь про мину­сы:

  • Слож­но понять и начать рабо­тать. Под­ход ООП намно­го слож­нее обыч­но­го функ­ци­о­наль­но­го про­грам­ми­ро­ва­ния — нуж­но знать мно­го тео­рии, преж­де чем будет напи­са­на хоть одна строч­ка кода.
  • Тре­бу­ет боль­ше памя­ти. Объ­ек­ты в ООП состо­ят из дан­ных, интер­фей­сов, мето­дов и мно­го дру­го­го, а это зани­ма­ет намно­го боль­ше памя­ти, чем про­стая пере­мен­ная.
  • Ино­гда про­из­во­ди­тель­ность кода будет ниже. Из-за осо­бен­но­стей под­хо­да часть вещей может быть реа­ли­зо­ва­на слож­нее, чем мог­ла бы быть. Поэто­му быва­ет такое, что ООП-программа рабо­та­ет мед­лен­нее, чем функ­ци­о­наль­ная (хотя с совре­мен­ны­ми мощ­но­стя­ми про­цес­со­ров это мало кого вол­ну­ет).

Что дальше

Впе­ре­ди нас ждёт раз­го­вор о клас­сах, объ­ек­тах и всём осталь­ном важ­ном в ООП. Кре­пи­тесь, будет инте­рес­но!

Объектно-ориентированное программирование для чайников

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

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

Начнем с самого базового понятия, — класса. Класс, он же тип, чтобы не мучить, и не сбивать с толку формулировками, можно объяснить на примере: класс это Собака, либо Кошка , либо Мышка , — всё это классы. А вот Жучка , Мурчик и Микки-Маус , — это экземпляры классов Собака , Кошка , Мышка . Экземпляры классов еще называют объектами. Итак, — Жучка это экземпляр класса Собака , Мурчик , — экземпляр класса Кошка , а Микки-Маус , — экземпляр класса Мышка , другими словами объект. Класс — единственный и неповторимый. У вас могут быть классы очень похожие, как овчарка и доберман, но это всё равно разные классы, хоть и очень похожие. Класс представляет собой набор характеристик/свойств, которые описывают его состояние и действия/методы, которые он может выполнить. Например, — нас есть класс Овчарка, к числу её свойств можно отнести «возраст» и «кличку». Экземпляр класса может выполнять действия, описанные в классе (лаять, сидеть, лежать), а также содержит значения свойств класса (класс Овчарка имеет свойство «возраст», а объект Барбос имеет значение, — 3 года). Помимо свойств и методов, объект имеет собственное имя, не путать со свойством «кличка», вот как это может выглядеть:

Что означает, что у объекта « МояСобака », есть кличка, и эта кличка — Барбос . А бывает и такая ситуация:

Тут у нас объект « ХозяинСобаки », у которого свойство « имя » имеет значение « Василий », а отчество равно « Иванович ».
Похожим образом работают методы (действия) объектов, например:

Методы могут параметризироваться, например, мы знаем что метод «лаять» заданное количество раз:

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

МояСобака = new Собака()

Так создается новый экземпляр класса Собака. Иногда он может параметризироваться дополнительной информацией, необходимой для объекта (зависит от реализации класса):

МояСобака = new Собака(«Барбос»)

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

Наследование, инкапсуляция, полиморфизм

Три кита объектного ориентирования это: наследование, инкапсуляция и полиморфизм. Без четкого понимания этих вещей программисту тяжело написать хороший объектно-ориентированный код, использовать всю силу этого подхода, а главное устроится на хорошую работу.
В прошлый раз я рассказывал о классах, объектах, свойствах и методах, если с этими понятиями проблем не возникает, дальше тоже всё будет очень просто. Для наглядности я буду приводить короткие куски кода на PHP (на самом деле язык тут не важен, просто мне кажется это самый распространенный на сегодня синтаксис, хоть и более классическим для примеров ООП является java, мне кажется PHP будет более полезным), поясняющие идею, и давать короткие описания.

Как видите здесь Корова (Cow) унаследовала функционал от Животного (Animal) , изменив реализацию метода draw (конкретизируя как корова на самом деле выглядит), и оставив реализацию метода eat() . Это и есть наследование. Теперь инкапсуляция. Сам по себе этот термин означает «сокрытие». Инкапсуляция, это способ сделать невозможным изменения критичных для работы класса свойств или вызова внутренних методов. Например у нас есть требование: каждое животное должно иметь кличку, и кличка, в течении его жизни не должна меняться. Самое правильное в таком случае это принимать кличку в качестве параметра конструктора (метода выполняемого при создании класса), и хранить его во внутреннем, сокрытом свойстве. Например так:

Вот это и есть инкапсуляция. Нет способа изменить кличку снаружи класса, и вы можете быть уверены, что в любом случае, кличка у экземпляра класса будет именно та, что была задана при создании. Ну а теперь полиморфизм. Это тут тоже начнем с примера. Добавим класс Sheep (овца) .

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

Данный пример будет случайным образом генерировать экземпляр Коровы и Овцы, и рисовать их.
Нужно сказать что данный пример не совсем «чистый» полиморфизм. Дело в том что полиморфизм подразумевает собой реализацию одного и того же интерфейса в разных классах. Объясню: если бы мы не реализовали метод draw() в одном из классов, у нас периодически возникала бы ошибка обращения к несуществующему методу, а в языках со строгой типизицией, ошибка бы возникала еще на стадии компиляции. Чтобы избежать подобных казусов, нужно использовать итерфейсы (interface):

Как только вы указали, что класс должен реализовывать интерфейс, компилятор или интерпретатор берет на себя обязательство проконтролировать что в классе реализованы методы, описанные в интерфейсе, что позволяет отлавливать ошибки еще до запуска приложения. Вот собственно и всё.

Читать еще:  Информатика и программирование
Ссылка на основную публикацию
ВсеИнструменты 220 Вольт
Adblock
detector