• ,

Объектно-ориентированное программирование (перевод статьи)

Автор: Mohit Kanwar
9 ноября, 2015. Java Zone.
Оригинал статьи

От переводчика:
К сожалению, у меня нет сколько-нибудь значительного опыта перевода с английского, хотя я и довольно много читаю на английском. Но выяснилось, что читать и переводить – разные вещи. Также, к сожалению, у меня нет значительного опыта в программировании (недавно только сделал простейшее веб-приложение на Spring MVC и Hibernate). Поэтому перевод получился значительно хуже, чем мог бы быть. Я взял на себя смелость несколько подкорректировать примеры кода, которые даются в статье, так как они не соответствуют соглашениям по наименованию (code conventions) в Java. Возможно, не стоило переводить название некоторых паттернов (такой перевод мало что дает для понимания), но я посчитал, что это меньшее зло. Отдельно стоит сказать о «высокой сцепленности» как переводе «high cohesion». Согласен, не самый лучший перевод. Но «сильная связность» — это «high coupling» (другое важное понятие), а «когерентность» здесь вряд ли подойдет. Я открыт для критики и с признательностью приму замечания по статье в любой форме.


Объектно-ориентированное программирование – это стиль программирования, в котором программа состоит из компонентов, соответствующих объектам реального мира

Любой реальный объект имеет какие-то свойства (которые могут изменяться или нет с течением времени) и поведение (которое может меняться или нет в зависимости от других условий). Например, карандаш – это объект реального мира, который имеет следующие свойства:
  • Он красный (это не меняется с течением времени).
  • Он 10 сантиметров в длину сейчас (это может меняться, если карандаш заточить).
И он имеет следующее поведение:
  • Он оставляет след, если его использовать правильно.
  • След может отличаться в зависимости от давления (зависит от внешних факторов).
  • Его длина сокращается, если его затачивать (постоянное поведение).
Как в этом примере, объекты реального мира могут иметь много свойств, но при написании программ мы принимаем во внимание только необходимые свойства.
Объектно-ориентированное программирование имеет свои преимущества. Например, оно облегчает установление связи между объектом реального мира и программой так, как это ожидается. Это реально помогает по мере того, как приложение растет и множество объектов взаимодействуют друг с другом. Это помогает в распределении ответственности внутри объективного мира, позволяя сфокусироваться на продумывании приложения.
Другая важная особенность, ассоциируемая с ООП (Объектно-ориентированным программированием), — это классификация объектов. Так как мир (реальный/виртуальный) полон объектов, сложно управлять по отдельности. Нам нужен способ классификации этих объектов, который поможет нам связать различные объекты и их свойства, например, черный карандаш. Он был бы неотличим (тот же самый?), если бы использовался в предыдущем примере, но это другой объект. Но так как это оба карандаши, они принадлежат одному и тому же классу «Карандаш». Тогда как ручка, которая очень похожа на карандаш, принадлежит к другому классу. Тем не менее, ручка и карандаш оба являются «Пишущими инструментами».

Объектно-ориентированное программирование имеет следующие принципы:

Абстракция
Абстракция определяется как характерная черта (quality) взаимодействия с идеями, а не событиями или, другими словами, свобода от репрезентирующих качеств. Это позволяет программистам сфокусироваться на том, что программировать, а не как. Абстракцию можно рассматривать в качестве соглашения, посредством которого мы предоставляем функциональность. Детали реализации могут быть скрыты, если использовать этот концепт.
Например, если нам нужен класс, который пишет, то мы должны быть уверены, что обладает методов «писать»
abstract class writer {
	write ();
}

Что мы сделали? Мы разработали класс высокого уровня, являющийся абстрактными, иными словами, он знает, что за функциональность нам нужна, но как ее реализовать – это за пределами видимости (out of scope) данного класса.
Это дает много преимуществ:
  • Мы раскрываем минимум информации необходимой внешним сущностям, это позволяет сосредоточиться на продумывании программы (this enable focused thinking), избежать путаницы и не давать непреднамеренных обещаний.
  • Мы оставляем место для улучшений в будущем, которые были бы невозможны, если детали реализации были раскрыты.

Наследование
«Наследование» в общеупотребительном английском означает «приобретать и передавать дальше». Это слова существует в нашей культуре очень давно. Предки приобретали землю с помощью тяжелой работы и передавали ее своим детям, даже природа благоволит наследованию. Все свойства тела, например, рост, цвет кожи/глаз/волос и т.д. зависят от генов, наследуем нами от наших родителей. Наследование предотвращает изобретение колеса заново и ускоряет прогресс. То же самое и в ООП. Мы создаем родительский класс с несколькими базовыми свойствами/поведением. Все классы, наследуемые от этого родителя, будут содержать такие же свойства/поведение, что и их родитель. Тем не менее, наследуемые классы могут получить больше свойств/поведения или изменить реализацию поведения.
class WritingInstrument {
	colour;
	write() {
}
}
class Pen (child of parent) {
	inkcolour;
}

В примере сверху класс-родитель (WritingInstrument) имеет свойство «цвет» и поведение «писать». Когда класс-наследник (ручка) объявляется, повторное объявление свойства «цвет» и поведения «писать» не требуется. Они присутствуют в классе «ручка» в силу наследования. Однако класс-наследник может объявить собственные дополнительные свойства/поведение.
Как мы можем использовать это на практике? Мы, разработчики, очень ленивы. Мы не хотим печатать что-то снова и снова. Существование множества копий одного и того же кода не приветствуется в силу следующих соображений:
  • Чем меньше копий кода, тем легче его сопровождать.
  • Если нет множества копий кода, то изменение в одном месте становится видимым везде.
  • Чем меньше кода – тем меньше ошибок.
  • Если один код используется во многих местах, то так достигается обобщение.
  • Мы фокусируемся на написании кода.
  • Мы фокусируемся на тестах.
Наследование в Java достигается с помощью ключевых слов «extends» и «implements».
class WritingInstrument {
}
class Pen extends WritingInstrument {
}


Полиморфизм
Слова «полиморфизм» произошло от двух слов:
«Поли», т.е. «множество» / «больше, чем один»
«морф», т.е. «форма»
Буквально, слово «полиморфизм» отсылает к способности объектов вести себя различным образом в зависимости от условий.
В программировании полиморфизм может быть воплощен в нескольких местах:
  • Классы
  • Методы
  • Операторы
Все, перечисленное выше, может вести себя различным образом в зависимости от условий, возможно, от контекста, в которых они используются. Это полезно, так как клиенту (программисту, использующему ваши библиотеки) не нужно знать множество тонкостей, и желаемая функциональность реализуется посредством отбора необходимой информации из контекста.
Class WritingObject {
	wrire() {
	// пишем, используя стандартные (по дефолту) цвета
	}
}

class Pencil extends WritingObject {
	write() {
	// пишем, используя серый цвет, написанный текст можно стереть
	}
}

class Pen extends WritingObject {
	write() {
	// пишем, используя голубой цвет, написанный текст нельзя стереть
	}
}

class Main {
	main() {
		WritingObject wr = new WritingObject();
		wr.write(); // первый вызов
		WritingObject wr = new Pen();
		wr.write(); // второй вызов
		WritingObject wr2 = new Pencil();
		wr2.write(); // третий вызов 
	}
}

В примере выше имеет реализация по умолчанию в WritingObject, которая расширена/переопределена классами-наследниками перо и ручка. Метод write() вызван три раза в классе Main. Каждый раз вызывается различная реализация в зависимости от того, у какого объекта вызывается этот метод. В данном случае метод write() имеет множество типов поведения, так как он полиморфичен.

Инкапсуляция
Инкапсуляция определяется как сбор связанных данный/функциональностей в одном модуле (unit). Это помогает в облегчении доступа/модификации данных. Например, если нам нужно напечатать все свойства, которыми данный пользователь обладает, мы имеем следующие опции:
printUserProperties(userName, userId, firstname, lastname, email, phone, … … ….)

Мы создали метод, который принимает все свойства и печатает их друг за другом. С увеличением количества элементов в списке пропадет возможность идентифицировать корректные поля, а добавление/удаление одного поля изменит сигнатуру метода. Поэтому нам нужно заменить всех пользователей этого метода, если даже недавно добавленные поля им не нужны. Чтобы сделать код более читаемым и упростить будущие модификации проще, мы инкапсулируем свойства в классе и превращаем его в коллективный объект (collective object)
class User {
	userName
	userId
	firstname
	lastname
email
	phone
	..
	..
	..
}
printUserProperties(user) {}

Объект – это система (software bundle) переменных и связанных методов. Вы можете представить объекты реального мира, используя объекты программы. Вы можете представить реальных собак в анимационной программе или реальный велосипед как программный объект внутри велотренажера.
В ООП класс – расширяемый шаблон (program-code-template) для создания объектов, обеспечения их начальным состоянием (переменные) и реализацией поведения (функции, методы).
Аббревиатура SOLID была введена Michael Feather’ом для «первых пяти принципов», названных так Robert C. Martin’ом в начале 2000-х. Целью принципов, реализуемых совместно, является увеличение вероятности того, что программист создаст систему, которую легко будет поддерживать и расширять. Принципы SOLID – ориентиры в разработке программ, которые необходимы для удаления «протухшего» кода посредством рефакторинга, в результате которого код должен стать легко читаемым и расширяемым. Это часть стратегии agile and adaptive programming (гибкого и адаптирующегося программирования).

Принцип единой ответственности (Single Responsibility Principle)
В ООП принцип единой ответственности гласит, что каждый класс должен быть ответственен за одну часть функциональности, обеспечиваемой программой, и что ответственность должна быть полностью инкапсулирована этим классом. Вся его функциональность должна быть тесно связана с этой ответственностью.

Принцип открытости/закрытости (Open/Closed Principle)
В ООП принцип открытости/закрытости гласит «сущности программного обеспечения (классы, модули, методы и т.д.) должны быть открыты для расширения, но закрыты для изменения». Иными словами, сущность должна позволять расширять ее поведение без изменения исходного кода.

Принцип подстановки Лисковой (Liskov Substitution Principle)
Возможность подстановки (Substituability) – это принцип в ООП. Он гласит, что если S в компьютерной программе является подтипом T, то объекты типа T должны быть такими, чтобы их можно было заменить объектами типа S (т.е. объекта типа S могут заменить объекты типа T) без изменения каких-либо требуемых свойств программы (точность, выполнение задачи и т.д.).

Принцип разделения интерфейса (Interface Segregation Principle)
Принцип разделения интерфейса гласит, что программист-клиент не должен быть принужден зависеть от методов, которые он не использует. Согласно этому принципу нужно разделять большие интерфейсы на маленькие и более специфичные, чтобы программист-клиент знал только о методах, которые ему интересны. Целью принципа разделения интерфейса является сохранение системы в несвязанном состоянии (system decoupled), что облегчит рефакторинг, внесение изменений и повторное развертывание (redeploy).

Принцип инверсии зависимостей (Dependency Inversion Principle)
В ООП принцип инверсии зависимости означает специфическую форму несвязности программных модулей. При следовании этому принципу стандартные отношения зависимости, установленные от модулей высокого уровня, формирующих архитектуру приложения (policy-setting) к зависимым модулям низкого уровня инвертированы (обращены), поэтому измененные модули высокого уровня становятся независимы от деталей реализации модулей низкого уровня. Этот принцип утверждает:
  • Модули высокого уровня не должны зависеть от модулей низкого уровня. Модули обоих типов должны зависеть от абстракций.
  • Абстракции не должны зависеть от деталей реализации. Детали должны зависеть от абстракций.
Принцип обращает (inverts) путь, согласно которому люди могут думать об объектно-ориентированном дизайне, утверждая, что объекты высокого и низкого уровней должны зависеть от одних и тех же абстракций.

Принципы GRASP

Паттерны (принципы), используемые для решения общих задач по назначению обязанностей классам и объектам (General Responsibility Assignment Software Patterns (GRASP)) содержат руководства (guidelines) по назначению ответственности классам и объектам в объектно-ориентированном дизайне.

Контроллер (Controller)
Паттерн Контроллер назначает ответственность за взаимодействие с системными событиями классов без графического интерфейса, которые представляют всю систему или use case scenario (сценарии вариантов использования).
Контроллер:
  • Это не взаимодействующий напрямую с пользователем объект, ответственный за получение и реакцию на системные события.
  • Должен использоваться так, чтобы иметь дело со всем системными событиями одного (или множества взаимосвязанных) use cases.
  • Это первый объект за графическим интерфейсом, который контролирует системные операции.
  • Он не должен делать работу сам, его задача – контроль над потоком событий.

Создатель (Creator)
Задача класса-создателя – создание и инициация объектов для последующего использования. Он знает, параметры инициализации, а также какой объект будет создан. Иногда класс-создатель создает объекты активно и помещает их в кэш, и обеспечивает один экземпляр, когда он нужен.

Высокая сцепленность (High Cohesion)
Высокая сцепленность – оценочный паттерн, целью применения которого является сохранение объектов в таком состоянии, чтобы они были нацелены на выполнение одной четкой задачи, легко управляемы и понимаемы. Высокая сцепленность обычно используется для поддержки слабой связности (Low Coupling). Высокая связность означает, что ответственность данного элемента четко обозначена (strongly related and highly focused). Разбиение программы на классы и подсистемы – пример действий, что повышает сцепленность свойств системы. Слабая сцепленность, наоборот, — ситуация, в которой элемент имеет слишком много несвязанных задач. Элементы со слабой сцепленностью обычно отличаются тем, что их сложно понять, сложно повторно использовать, поддерживать и изменять.

Окольный путь (Indirection)
Паттерн Окольный путь поддерживает слабую связность (и возможность повторного использования) между двумя элементами, назначая ответственность за взаимодействие между ними промежуточному объекту. Примером является введение контроллера для посредничества между данными (моделью) и их отображением (представлением) в паттерне Модель-Представление-Контроллер (MVC).

Информационный эксперт (Information Expert)
Информационный эксперт (также Эксперт или принцип Эксперта) – принцип, используемый для определения того, кому делегировать ответственность. Ответственность включает методы, вычисляемые поля и т.д. При использовании данного принципа при назначении ответственности главным подходом является следующая последовательность действий: анализ ответственности, определение информации, которая нужна для ее исполнения, наконец, установление того, где эта информация находится. Использование принципа Информационный эксперт приводит к назначению ответственности классу, который имеет наибольшее количество информации для ее исполнения.

Слабая связность (Low Coupling)
Слабая связность – оценочный паттерн, который указывает, как назначать ответственность: слабая зависимость между классами, изменение одного должно иметь минимальные последствия для другого, максимальная возможность повторного использования.

Полиморфизм (Polymorphism)
В соответствии с полиморфизмом изменения поведения основывается на типе объекта, на который ссылается переменная (responsibility of defining the variation of behaviors based on type is assigned to the types for which this variation happens). Это достигается использованием полиморфных операций.

Защищенные изменения (Protected Variations)
Паттерн Защищенные изменения защищает элементы от изменений других элементов (объектов, систем, подсистем) путем обертывания центра нестабильности (the focus of instability) интерфейсом и использования полиморфизма для создания различных реализаций данного интерфейса.

Чистое конструирование (Pure Fabrication)
Чистое конструирование предполагает класс, не представляющий концепт в проблемной области (the problem domain) и созданный специально для достижения слабой связности, высокой сцепленности и, следовательно, максимального потенциала повторного использования (решение, предлагаемое паттерном Информационный эксперт этого не обеспечивает). Такая класс обычно называется “Service” в Предметно-ориентированном дизайне (Domain-driven design).

Критика

Исследования Potok’a и др. показали отсутствие существенных различий между ООП и процедурными подходами.
Критическое сравнение ООП с другими технологиями, в особенности реляционными, затруднено в силу отсутствия определения ООП, которое было бы строгим и широко принятым (Christopher J. Date)
В сравнении с другими языками (LISP диалекты, функциональные языка и т.д.) ООП языки не имеют уникального преимущества и навязывают ненужную сложность. (Lawrence Krubner)
Я нахожу объектно-ориентированное программирование технически неосновательным. Оно пытается разложить мир на части в терминах интерфейсов, которые изменяются в пределах одного типа. Чтобы иметь дело с реальными проблемами, вам нужны многосортные алгебры — семейства интерфейсов, которые простираются на многие типы. Я нахожу объектно-ориентированное программирование философски нездоровым. Оно утверждает, что всё является объектом. Даже если это так, это не очень интересно: сказать, что всё является объектом — значит, не сказать вообще ничего. (Александр Степанов)
Популярность ООП среди больших компаний связана с «большими (и часто меняющимися) группами посредственных программистов». Дисциплина, навязываемая ООП, предотвращает нанесение программистом «слишком большого вреда». (Paul Graham)
Объектно-ориентированное программирование ставит существительные первыми и самыми главными. Зачем идти на такие крайние меры и ставить одну часть речи на пьедестал? Почему один концепт получает преимущество над другим? Это невозможно, чтобы ООП внезапно сделало глаголы менее важными для нашего мышления. Это странным образом перекошенная перспектива. (Steve Yegge)
Rick Hickey, создатель Clojure, описывал объектные системы как крайне упрощенные модели реального мира. Он подчеркивал неспособность ООП моделирования времени правильно, что создает огромные проблемы, когда в программах большое распространение получает многопоточность.
Eric S. Raymond, Unix-программист и сторонник программного обеспечения с открытым кодом, был критически настроен в отношении заявления, что ООП является «Единственно верным решением», и писал, что ООП поощряет многослойные программы, что препятствует понятности (transparency). В качестве противоположного подхода Raymond приводил пример Unix и С.

Ссылки

By Margaret Rouse @ WhatIs.com
Wikipedia! (Русский вариант)
inheritance is polymorphism
SOLID (Object Oriented Design) (Русский вариант)
Single Responsibility PrincipleArguments against OOPS (Русский вариант)
What is OOPS (without the hype)

Перевод: Варыгин Д.В.