Основные принципы ооп в java

Что такое «абстракция»?

Абстрагирование – это способ выделить набор общих характеристик объекта, исключая из рассмотрения частные и незначимые. Соответственно, абстракция – это набор всех таких характеристик.

Пример:

// Abstract class
abstract class Animal {
    // Abstract method (does not have a body)
    public abstract void animalSound();

    // Regular method
    public void sleep() {
        System.out.println("Zzz");
    }
}

// Subclass (inherit from Animal)
class Pig extends Animal {
    public void animalSound() {
        // The body of animalSound() is provided here
        System.out.println("The pig says: wee wee");
    }
}

class MyMainClass {
    public static void main(String[] args) {
        Pig myPig = new Pig(); // Create a Pig object
        myPig.animalSound();
        myPig.sleep();
    }
}

Поля

Поле представляет собой переменную любого типа, объявленную внутри класса. Через модификатор доступа можно управлять уровнем доступа к полю (так же как для методов), через ключевое слово static можно определять принадлежность поля объекту либо классу:

class DemoClass 
{ 
    // ... 
    int field = 0; 
    public int publicField = 0; 
    public static int publicStaticField = 0; 
    // ...
}

(Код в Main):

var d9 = new DemoClass();
// Доступ к private полям запрещен
// Console.WriteLine($"Get private field: {d9.field}"); // Compile ERROR           
// Доступ к полю объекта
d9.publicField = 123;
Console.WriteLine($"Get public field: {d9.publicField}"); // Get public field: 123
// Доступ к статическому полю класса
DemoClass.publicStaticField = 456;
Console.WriteLine($"Get public static field: {DemoClass.publicStaticField}"); // Get public static field: 456

Работать с открытыми полями напрямую (поля, которые имеют модификатор public) является плохой практикой. Если необходимо читать и изменять значение какого-либо поля, то лучше это делать через getter’ы и setter’ы – специальные методы, которые выполняют эту работу.

Создадим для класса Building методы для доступа и модификации значения поля height

class Building 
{ 
    float height;

    public float GetHeight() => height;
    public float SetHeight(float height) => this.height = height; 
}

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

var b1 = new Building();
b1.SetHeight(12);
Console.WriteLine($"Height of building: {b1.GetHeight()}");

Создание специальных методов для работы с полями – возможный вариант, но в C# принят подход работы через свойства. Им посвящен следующий раздел.

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

Ключевое слово this используется внутри класса для ссылки на текущий экземпляр класса. Чаще всего его приходится использовать в методах для доступа к полям класса. Например, если у класса Building, с которым мы работали в предыдущем разделе, есть поле height, метод SetHeight был создан нами для того, чтобы присваивать этому полю значение, аргумент этого метода – переменная с именем height

float SetHeight(float height)

В теле данного метода мы должны как-то явно задать, что значение этого аргумента будет присвоено приватному полю height, записать height = height мы не можем, так как в этом случае нет четкого понимания, что чему будет присваиваться. В этом случае, для явного указания, что мы хотим присвоить значение полю класса, следует использовать ключевое слово this так, как мы это реализовали в методе SetHeight

public float SetHeight(float height) => this.height = height;

D: Принцип инверсии зависимостей

DIP: изменения объектов A и B не повлияют на другие

«Следует полагаться на абстракции, а не на конкреции».

Принцип инверсии зависимостей (DIP) состоит из двух частей:

  1. Модули высокого уровня не должны зависеть от модулей низкого уровня. Вместо этого оба должны зависеть от абстракций (интерфейсов).
  2. Абстракции не должны зависеть от деталей. Детали (например, конкретные реализации) должны зависеть от абстракций.

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

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

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

Если вы минимизируете зависимости, изменения будут более локализованными и потребуют меньше усилий для поиска всех затронутых компонентов.

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

Детали — это конкретные закулисные реализации, которые делают поведение программы видимым для пользователя. В программе DIP мы могли бы полностью пересмотреть закулисную реализацию того, как программа достигает своего поведения без ведома пользователя.

Этот процесс известен как рефакторинг.

Это означает, что вам не придётся жёстко программировать интерфейс для работы только с текущими деталями (реализацией). Это сохраняет наш код слабосвязанным и даёт нам гибкость для реорганизации наших реализаций позже.

Выполнение

Мы создадим общую деловую программу с интерфейсом, высокоуровневыми, низкоуровневыми и детализированными компонентами.

Во-первых, давайте создадим интерфейс с getCustomerName()методом. С этим столкнутся наши пользователи.

Теперь мы реализуем детали, которые будут зависеть от ICustomerDataAccessинтерфейса. Таким образом достигается вторая часть принципа DIP.

Теперь мы создадим фабричный класс, который реализует абстрактный интерфейс ICustomerDataAccessи возвращает его в пригодной для использования форме. Возвращённый CustomerDataAccessкласс — это наш низкоуровневый компонент.

Наконец, мы реализуем высокоуровневый компонент, CustomerBuisnessLogicкоторый также реализует интерфейс ICustomerDataAccess

Обратите внимание, что наш высокоуровневый компонент не реализует наш низкоуровневый компонент, а просто использует его

Вот полная программа как в коде, так и в наглядной диаграмме:

Визуальная схема нашей программы доступа к данным

Плюсы и минусы объектно-ориентированных языков программирования

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

Плюсы

1. Возможность повторного использования

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

2. Параллельная разработка

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

3. Обслуживание

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

4. Безопасность

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

5. Модульность

В объектно-ориентированных языках программирования приложение разбивается на объекты и классы. Это полезно, потому что даёт вашему приложению более модульную структуру. Модульный код легче читать. Поэтому его легче обслуживать.

Минусы

1. Часто грязный

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

2. Требуется больше планирования

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

3. Непрозрачность

Это как плюс, так и минус. Объекты и функции могут работать независимо. Они могут получать информацию и (обычно) возвращать надёжные результаты. В результате они могут оказаться чёрными ящиками, а это означает, что то, что они делают, не всегда очевидно. Хотя программист, вероятно, создал этот объект и знает, что он делает, языки ООП просто не так прозрачны, как другие языки.

4. Производительность

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

Однако это не всегда так или важно. C ++ — язык ООП, но это один из самых быстрых доступных языков

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

Основы Объектно-Ориентированного Программирования

Объектно-Ориентированное Программирование (ООП) является одной из наиболее популярных парадигм в мире промышленной разработки программного обеспечения. Из других парадигм программирования следует выделить – структурное программирование (основной представитель этого направления – это язык C) и функциональное программирование (к этой группе относятся языки Haskell, F#, Clojure).

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

Выделяют три основных “столпа” ООП- это инкапсуляция, наследование и полиморфизм.

Инкапсуляция

Инкапсуляция предполагает два основных момента:

  • сокрытие деталей реализации;
  • связывание данных и методов для их обработки в одном месте.

“Житейским” примером первого аспекта – сокрытия деталей реализации, может служить автомобиль. Вся его сложность скрыта от пользователя, и нет необходимости разбираться в том, как автомобиль работает, чтобы им пользоваться. Связываение данных и методов предполагает, что в рамках одного класса располагаются данные, определяющие некоторые свойства сущности (например, имя и возраст, если сущность – это человек), и методы для их обработки,  получения и изменения.

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

Наследование – это концепция, которая предполагает, что один класс может наследовать функции и данные другого класса. Класс, от которого производится наследование называется родительским или базовым классом, класс который наследует – наследником. Отношение между классом наследником и базовым классом можно определить словом “является”.

Например, представим, что у нас есть базовый класс Фигура, и этот класс содержит только одно свойство – Цвет. Тогда про класс Круг – наследник класса Фигура, можно сказать так: Круг “является” Фигурой. Чего нельзя сказать про отношение между Автомобилем и Двигателем, т.е. Автомобиль не является Двигателем. Это означает, что создание иерархии наследования, в которой Автомобиль – это неследник от Двигателя было бы ошибочной (такой тип отношений назвается композиция).

Полиморфизм

Говоря про полиморфизм в общем, можно сказать, что это возможность обработки данных разных типов одной и той же функцией. Различают параметрический полиморфизм и ad–hoc полиморфизм. Параметрический полиморфизм предполагает, что один и тот же код в функции может работать с данными разных типов. Ad–hoc полиморфизм предполагает создание различных реализаций функций в зависимости от типа аргумента(ов), при этом их сигнатура (без учета типов входных аргументов) остается одной и той же.

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

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

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

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

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

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

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

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

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

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

Кайф в том, что нам не так важно, как именно будет происходить удаление: ООП позволяет нам не думать об этом в момент обращения

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

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

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

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

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

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

Полиморфизм — единый язык общения

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

Нельзя, чтобы у одного объекта это было «Удалить», а у другого «Стереть».

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

События разные, но для программиста это неважно. У него просто есть метод «Удалить()», и он ему доверяет

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

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

Определение 1. Единая ответственность.

Официальное определение принципа единой ответственности (SRP) говорит о том, что у каждого объекта есть своя ответственность и причина существования и эта ответственность у него только одна.

Рассмотрим объект «Выпивоха» (Tippler).
Для выполнения принципа SRP разделим обязанности на троих:

  • Один наливает (PourOperation)
  • Один выпивает (DrinkUpOperation)
  • Один закусывает (TakeBiteOperation)

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

Выпивоха же, в свою очередь является фасадом для данных операций:

Зачем?

Человек-программист пишет код для человека-обезьяны, а человек-обезьяна невнимателен, глуп и вечно куда-то спешит. Он может удержать и понять около 3 — 7 термов в один момент времени.
В случае выпивохи этих термов три. Однако если мы напишем код одной простыней, то в нем появятся руки, стаканы, мордобои и бесконечные споры о политике. И все это будет в теле одного метода. Уверен — вы видели такой код в своей практике. Не самое гуманное испытание для психики.

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

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

Так вот, SRP — это принцип, объясняющий КАК декомпозировать, то есть где провести линию разделения.

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

Вернемся к выпивохе и плюсам, которые получает человек-обезьянка при декомпозировании:

  • Код стал предельно ясен на каждом уровне
  • Код могут писать несколько программистов сразу (каждый пишет отдельный элемент)
  • Упрощается автоматическое тестирование — чем проще элемент, тем легче его тестировать
  • Из этих трех операций, в будущем, вы сможете сложить обжору ( используя только TakeBitOperation), Алкоголика (используя только DrinkUpOperation напрямую из бутылки) и удовлетворить многие другие требования бизнеса.

И, конечно же, минусы:

  • Придется создать больше типов.
  • Выпивоха впервые выпьет на пару часов позже, чем мог бы

Закономерности в развитии программного проекта

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

  1. Утверждения, описывающие свойства компонента.
    1.1. Корректно написанный компонент обязательно используется и чаще несколько раз.
    1.2. В каждом месте использования компонента от него ожидается неизменное поведение, приводящее к повторяемому результату.
    1.3. При использовании компонента в нескольких местах результат должен удовлетворять каждому месту использования.
    1.4. Поведение, заложенное в компоненте, формирует ограничения для мест использования этого компонента.
    1.5. В каждом месте использования компонента могут быть задействованы все его ограничения.
    1.6. Любое изменение компонента изменяет его ограничения и требует проверки всех мест его использования, что порождает затраты времени программиста.
    1.7. Компонент целесообразно записывать в виде кода в одном экземпляре, то есть необходимо устранять дублирование одинакового кода. Это уменьшит количество правок при внесения изменения в компонент.
  2. Утверждения, описывающие закономерности в реализации программистом новой задачи.
    2.1 Вариант реализации новой задачи целесообразно выбирать минимизируя затраты времени программиста.
    2.2. Для реализации новой задачи программист может добавить новые компоненты или изменить поведения старых компонентов.
    2.3. Добавление компонента в основном требует проверки только в месте нового использования, и порождает минимальные затраты времени программиста.
    2.4. Обусловленное новой задачей изменение поведения компонента, согласно утверждению , требует проверки в месте нового использования и во всех местах старого использования, что порождает дополнительные затраты времени программиста по сравнению с ситуацией в утверждении . В случае опубликованного компонента это требует работы всех программистов, использовавших измененный компонент.
  3. Утверждения, описывающие закономерности во взаимодействии универсальных алгоритмов и их специализаций:
    3.1. Существует возможность написать базовый компонент (название вводится по аналогии с базовым классом и далее для краткости будем использовать слово «база»). База выполняет только самые главные черты некоторого универсального алгоритма.
    3.2. Существует возможность написать компонент-специализацию (далее для краткости будем использовать слово «специализация»). Специализация дополняет универсальный алгоритм базы, делая его применимым в конкретной области использования.
    3.3. База, как следует из утверждений , , имеет меньшую сложность и меньше ограничений в применении, чем специализация.
    3.4. Согласно утверждению специализацию целесообразно разрабатывать без дублирования кода универсального алгоритма из базы.
    3.5. Места использования базы не требуют проверки после внесения изменений в корректно сформированную специализацию.

I: Принцип разделения интерфейса

Интернет-провайдер: наследовать только применимые методы

«Многие клиентские интерфейсы лучше, чем один интерфейс общего назначения».

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

Это относится к нашему первому принципу SOLID, поскольку вместе эти два принципа лишают класс всех переменных, методов или поведения, которые напрямую не влияют на их роль. Методы должны полностью способствовать достижению конечной цели.

Любую неиспользованную часть метода следует удалить или выделить в отдельный метод.

Преимущество ISP в том, что он разбивает большие методы на более мелкие и более конкретные методы. Это упрощает отладку программы по трём причинам:

  1. Между классами переносится меньше кода. Меньше кода — меньше ошибок.
  2. Один метод отвечает за меньшее количество вариантов поведения. Если есть проблема с поведением, вам нужно только просмотреть более мелкие методы.
  3. Если общий метод с несколькими поведениями передаётся классу, который не поддерживает все варианты поведения (например, вызов свойства, которого нет у класса), возникнет ошибка, если класс попытается использовать неподдерживаемое поведение.

Выполнение

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

Во-первых, программа, которая не следует за интернет-провайдером:

Эта программа не следует за ISP, потому что FullTimeEmployeeклассу не нужна CalculateWorkedSalary()функция, а ContractEmployeeклассу не нужна CalculateNetSalary().

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

Вот как можно реорганизовать программу в соответствии с принципом ISP:

В этой версии мы разделили общий интерфейс IWorkerна один базовый интерфейс IBaseWorkerи два дочерних интерфейса IFullTimeWorkerSalaryи IContractWorkerSalary.

Общий интерфейс содержит методы, общие для всех рабочих. Дочерние интерфейсы разделяют методы по типам работников, FullTimeпо заработной плате или Contractпочасовой оплате.

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

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

Классы

Для меня класс — это всего лишь калька (шаблон) объекта, хранящий его поведение (методы). И, честно говоря, классы сами по себе могут быть объектами (например, как в Ruby). Так что давайте добавим классы.

Сначала давайте «стандартизируем» как методы вызываются и выполняются. Мне уже лень писать, поэтому я просто вывалю эту кучу кода прям здесь (сорян):

И так. Сообщение для вызова метода — это просто хеш, состоящий из двух вещей: имя метода и аргументы для него.

Еще обратите внимание на функцию. Я захожу немного вперед, но мы будем давать классам методы в виде хеша

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

Обработка сообщений тоже довольно проста:

Теперь давайте глянем, как будут выглядеть наши классы:

Как можно заметить, я решил создавать классы объектами. Я не был обязан делать этого, классы могли бы быть более абстрактным концептом, но я решил, что так забавнее. Можно пойти еще дальше и сделать функцию приватной и создать объект , который будет создавать классы с помощью метода . Это довольно легко реализовать, но я решил не тратить время.

Ну что ж, наши классы — всего лишь объекты, в которых состояние — это методы, конструктор (для инициализации инстансов класса) и массив с экземплярами класса. Массив, на самом деле, нам не нужен, но почему бы и нет.

Так, что же это за такая функция ? А вот она:

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

Еще я добавил вспомогательную функцию для синхронизированного создания:

Ну что, давайте попробуем создать класс-ориентированный string-builder.

Четко!

3 Что учить дальше?

Мы разобрали ценности и знаем к чему стоит стремиться, кроме того, мы поверхностно посмотрели на механизмы, которые предоставляет нам объектно-ориентированное программирование, не фокусируясь на каком-либо определенном языке

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

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

3.1 Список использованных источников

  1. Рейтинг популярности языков программирования TIOBE. URL: https://tiobe.com/tiobe-index/
  2. SOLID принципы. Рефакторинг. URL: https://pro-prof.com/archives/1914
  3. Почему мне кажется, что студентов учат ООП неправильно. URL: https://habr.com/ru/post/345658/
  4. C++ Russia 2018: Фёдор Короткий, Память – идеальная абстракция. URL: https://vk.com/wall-105242702_701
  5. Мейер Б. Объектно-ориентированное конструирование программных систем. М.: Издательско-торговый дом «Русская Редакция», «Интернет-университет информационных технологий», 2005. 1232 с.: ил.
  6. Мартин Р. Чистый код. Создание, анализ и рефакторинг. Библиотека программиста. – СПб.: Питер, 2014. – 464 с.
  7. Джейсон Мак-Колм Смит Элементарные шаблоны проектирования : Пер. с англ. — М. : ООО “И.Д. Вильямс”, 2013. — 304 с.
  8. Диаграммы классов UML. URL: https://pro-prof.com/archives/3212
  9. Юнит-тестирование. Пример. Boost Unit Test. URL: https://pro-prof.com/archives/1549
  10. Э. Гамма Приемы объектно-ориентированного проектирования. Паттерны проектирования / Э. Гамма, Р. Хелм, Р. Джонсон, Д. Влиссидес. – СПб.: Питер, 2009. – 366 с.

Понятие «объект» в ООП

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

Объект — это появляющийся при создании экземпляра класса набор свойств и их значений в памяти,  на которые можно сослаться с помощью идентификатора:

  • каждое свойство состоит из имени и значения, ассоциированного с этим именем;
  • значением свойства может быть функция, которую можно назвать методом объекта. 

Свойства объекта в JavaScript

В JavaScript объект имеет свойства, ассоциированные с ним. Свойство объекта:

  • можно понимать как переменную, закрепленную за объектом;
  • определяют характеристики объекта.

Получение доступа к свойству объекта:

JavaScript

objectName.propertyName
// или
objectName

1
2
3

objectName.propertyName

// или

objectName»propertyName»

Примеры:

JavaScript

var myCar = new Object(); // создание объекта с помощью конструктора
myCar.make = «Ford»;
myCar.model = «Mustang»;
myCar.year = 1969;

// или
myCar = «Ford»;
myCar = «Mustang»;
myCar = 1969;

1
2
3
4
5
6
7
8
9

varmyCar=newObject();// создание объекта с помощью конструктора

myCar.make=»Ford»;

myCar.model=»Mustang»;

myCar.year=1969;

 
// или

myCar»make»=»Ford»;

myCar»model»=»Mustang»;

myCar»year»=1969;

Имена свойств объекта могут быть строками JavaScript, или тем, что может быть сконвертировано в строку, включая пустую строку. Как бы то ни было, доступ к любому имени свойства, которое содержит невалидный JavaScript идентификатор (например, имя свойства содержит в себе пробел и тире или начинается с цифры), может быть получен с использованием квадратных скобок. Этот способ записи также полезен, когда имена свойств должны быть динамически определены (когда имя свойства не определено до момента исполнения).

Примеры

JavaScript

var myObj = new Object(),
str = «myString»,
rand = Math.random(),
obj = new Object();

myObj.type = «Dot syntax»;
myObj = «String with space»;
myObj = «String value»;
myObj = «Random Number»;
myObj = «Object»;
myObj = «Even an empty string»;

console.log(myObj);

1
2
3
4
5
6
7
8
9
10
11
12
13

varmyObj=newObject(),

str=»myString»,

rand=Math.random(),

obj=newObject();

myObj.type=»Dot syntax»;

myObj»date created»=»String with space»;

myObjstr=»String value»;

myObjrand=»Random Number»;

myObjobj=»Object»;

myObj»»=»Even an empty string»;

console.log(myObj);

Обратите внимание, что все ключи с квадратными скобками преобразуются в тип String, поскольку объекты в JavaScript могут иметь в качестве ключа только тип String. Например, в приведенном выше коде, когда ключ obj добавляется в myObj, JavaScript вызывает метод obj.toString () и использует эту результирующую строку в качестве нового ключа

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

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

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

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

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector