Автор
Jesse Haniel
Главный архитектор программного обеспечения в Tribunal de Justiça da Paraíba

Принципы ООП

Статья из группы Java Developer
Привет! Ты когда-нибудь задумывался, почему Java устроена именно так, как она устроена? В том смысле, что ты создаешь классы, на их основе — объекты, у классов есть методы и т.д. Но почему структура языка такова, что программы состоят именно из классов и объектов, а не из чего-то другого? Зачем было придумано понятие «объект» и поставлено во главу угла? Все ли языки устроены так и, если нет, какие преимущества это дает Java? Вопросов, как видишь, много :) Попробуем ответить на каждый из них в сегодняшней лекции.

Принципы ООП:

  1. Наследование
  2. Абстракция
  3. Инкапсуляция
  4. Полиморфизм

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

Конечно, Java не просто так состоит из объектов и классов. Это не прихоть ее создателей, и даже не их изобретение. Есть множество других языков, в основе которых лежат объекты. Первый такой язык назывался Simula, и его изобрели еще в 1960-х годах в Норвегии. Помимо всего прочего, в Simula появились понятия «класс» и «метод». Принципы объектно-ориентированного программирования - 2
Кристен Нюгор и Оле Йохан Даль - создатели Simula
Казалось бы, Simula — древний язык по меркам программирования, но их «родственную» связь с Java видно невооруженным глазом. Скорее всего, ты легко прочтешь написанный на нем код и в общих чертах объяснишь, что он делает :)

Begin
  Class Rectangle (Width, Height); Real Width, Height;
                 
   Begin
      Real Area, Perimeter;  
   
      Procedure Update;      
      Begin
        Area := Width * Height;
              OutText("Rectangle is updating, Area = "); OutFix(Area,2,8); OutImage;
        Perimeter := 2*(Width + Height);
              OutText("Rectangle is updating, Perimeter = "); OutFix(Perimeter,2,8); OutImage;
      End of Update;
   
      Update;               
      OutText("Rectangle created: "); OutFix(Width,2,6);
      OutFix(Height,2,6); OutImage;
   End of Rectangle;

       Rectangle Class ColouredRectangle (Color); Text Color;
                 
  Begin       
      OutText("ColouredRectangle created, color = "); OutText(Color);
      OutImage;
        End of ColouredRectangle;

 
         Ref(Rectangle) Cr;            
   Cr :- New ColouredRectangle(10, 20, "Green"); 
End;
Пример кода взят из статьи Simula — 50 лет ООП. Как видишь, Java и его предок не так уж сильно отличаются друг от друга :) Это связано с тем, что появление Simula ознаменовало собой рождение новой концепции — объектно-ориентированного программирования. Википедия дает такое определение ООП: Объе́ктно-ориенти́рованное программи́рование (ООП) — методология программирования, основанная на представлении программы в виде совокупности объектов, каждый из которых является экземпляром определенного класса, а классы образуют иерархию наследования. Оно, на мой взгляд, очень удачное. Ты недавно начал изучать Java, но в нем вряд ли найдутся незнакомые тебе слова:) Сегодня ООП — самая распространенная методология программирования. Помимо Java принципы ООП используются во многих популярных языках, о которых ты, возможно, слышал. Это C++ (его активно применяют разработчики компьютерных игр), Objective-C и Swift (на них пишут программы для устройств Apple), Python (наиболее востребован в машинном обучении), PHP (один из самых популярных языков веб-разработки), JavaScript (проще сказать, чего на нем не делают) и многие другие. Собственно говоря, что это за «принципы» ООП? Расскажем подробнее.

Принципы ООП

Это основа основ. 4 главные особенности, которые вместе образуют парадигму объектно-ориентированного программирования. Их понимание — ключ к становлению успешного программиста. Принципы объектно-ориентированного программирования - 3

Принцип 1. Наследование

Хорошая новость: с некоторыми из принципов ООП ты уже знаком! :) Наследование нам уже пару раз встречалось в лекциях, и мы успели с ним поработать. Наследование — механизм, который позволяет описать новый класс на основе существующего (родительского). При этом свойства и функциональность родительского класса заимствуются новым классом. Для чего нужно наследование и какие преимущества оно дает? Прежде всего — повторное использование кода. Поля и методы, описанные в родительских классах, можно использовать в классах-потомках. Если у всех типов автомобилей есть 10 общих полей и 5 одинаковых методов, тебе достаточно вынести их в родительский класс Auto. Ты сможешь использовать их в классах-потомках безо всяких проблем. Сплошные плюсы: и количественно (меньше кода), и, как следствие, качественно (классы становятся гораздо проще). При этом механизм наследования очень гибкий, и недостающую в потомках функциональность ты можешь дописать отдельно (какие-то специфические для конкретного класса поля или поведение). В общем, как и в обычной жизни: все мы чем-то похожи на наших родителей, а чем-то отличаемся от них :)

Принцип 2. Абстракция

Это очень простой принцип. Абстракция означает выделение главных, наиболее значимых характеристик предмета и наоборот — отбрасывание второстепенных, незначительных. Не будем изобретать велосипед и вспомним пример из старой лекции про классы. Скажем, мы создаем картотеку работников компании. Для создания объектов «работник» мы написали класс Employee. Какие характеристики важны для их описания в картотеке компании? ФИО, дата рождения, номер социального страхования, ИНН. Но вряд ли в карточке такого типа нам нужны его рост, цвет глаз и волос. Компании эта информация о сотруднике ни к чему. Поэтому для класса Employee мы зададим переменные String name, int age, int socialInsuranceNumber и int taxNumber, а от лишней для нас информации вроде цвета глаз откажемся, абстрагируемся. А вот если мы создаем картотеку фотомоделей для агентства, ситуация резко меняется. Для описания фотомодели нам как раз очень важны рост, цвет глаз и цвет волос, а номер ИНН не нужен. Поэтому в классе Model мы создаем переменные String height, String hair, String eyes.

Принцип 3. Инкапсуляция

С ним мы уже сталкивались. Инкапсуляция в Java означает ограничение доступа к данным и возможностям их изменения. Как видишь, в его основе лежит слово «капсула». В эту «капсулу» мы прячем какие-то важные для нас данные, которые не хотим, чтобы кто-то менял. Простой пример из жизни. У тебя есть имя и фамилия. Их знают все твои знакомые. Но у них нет доступа к изменению твоего имени и фамилии. Этот процесс, можно сказать, «инкапсулирован» в паспортном столе: поменять имя фамилию можно только там, и сделать это можешь только ты. Остальные «пользователи» имеют доступ к твоему имени и фамилии «только на чтение» :) Еще один пример — деньги в твоей квартире. Оставлять их на виду посреди комнаты — не лучшая идея. Любой «пользователь» (человек, пришедший к тебе домой) сможет изменить число твоих денег, т.е. забрать их. Лучше инкапсулировать их в сейфе. Доступ будет только у тебя и только по специальному коду. Очевидные примеры инкапсуляции, с которыми ты уже работал, — это модификаторы доступа (private, public и т.д.), а также геттеры-сеттеры. Если поле age у класса Cat не инкапсулировать, кто угодно сможет написать:

Cat.age = -1000;
А механизм инкапсуляции позволяет нам защитить поле age при помощи метода-сеттера, в который мы можем поместить проверку того, что возраст не может быть отрицательным числом.

Принцип 4. Полиморфизм

Полиморфизм — это возможность работать с несколькими типами так, будто это один и тот же тип. При этом поведение объектов будет разным в зависимости от типа, к которому они принадлежат. Звучит сложновато? Сейчас разберемся. Возьмем самый простой пример — животных. Создадим класс Animal с единственным методом — voice(), и двух его наследников — Cat и Dog.

public class Animal {

   public void voice() {
      
       System.out.println("Голос!");
   }
}

public class Dog extends Animal {
  
  
   @Override
   public void voice() {
       System.out.println("Гав-гав!");
   }
}

public class Cat extends Animal {

   @Override
   public void voice() {
       System.out.println("Мяу!");
   }
}
Теперь попробуем создать ссылку Animal и присвоить ей объект Dog.

public class Main {

   public static void main(String[] args) {

       Animal dog = new Dog();
       dog.voice();
   }
}
Как ты думаешь, какой метод будет вызван? Animal.voice() или Dog.voice()? Будет вызван метод класса Dog: Гав-гав! Мы создали ссылку Animal, но объект ведет себя как Dog. При необходимости он может вести себя как кошка, лошадь или другое животное. Главное — присвоить ссылке общего типа Animal объект конкретного класса-наследника. Это логично, ведь все собаки являются животными. Именно это мы имели в виду, когда говорили «поведение объектов будет разным, в зависимости от того, к какому типу они принадлежат». Если бы мы создали объект Cat

public static void main(String[] args) {

   Animal cat = new Cat();
   cat.voice();
}
метод voice() вывел бы «Мяу!». А что значит «возможность работать с несколькими типами так, как будто это один и тот же тип»? Это тоже довольно легко. Давайте представим, что мы создаем парикмахерскую для животных. В нашей парикмахерской должны уметь стричь всех животных, поэтому мы создадим метод shear() («постричь») с параметром Animal — животным, которое мы будем стричь.

public class AnimalBarbershop {

   public void shear(Animal animal) {

       System.out.println("Стрижка готова!"); 
   }
}
И теперь мы можем передавать в метод shear и объекты Cat, и объекты Dog!

public static void main(String[] args) {

   Cat cat = new Cat();
   Dog dog = new Dog();

   AnimalBarbershop barbershop = new AnimalBarbershop();

   barbershop.shear(cat);
   barbershop.shear(dog);
}
Вот и наглядный пример: класс AnimalBarbershop работает с типами Cat и Dog так, как будто это один и тот же тип. При этом у Cat и Dog разное поведение: они по-разному подают голос. Принципы объектно-ориентированного программирования - 4

Причины появления ООП

Почему вообще возникла эта новая концепция программирования — ООП? У программистов были работающие инструменты: например, процедурные языки. Что же побудило их изобретать что-то принципиально новое? Прежде всего, усложнение задач, которые перед ними стояли. Если 60 лет назад задача программиста выглядела как «вычислить математическое уравнение такое-то», сейчас она может звучать как «реализовать 7 различных концовок для игры S.T.A.L.K.E.R. в зависимости от того, какие решения принимал пользователь в игровых моментах A, B, C , D, E, F и комбинаций этих решений». Задачи, как видишь, за прошедшие десятилетия явно усложнились. А значит, усложнились и типы данных. Это еще одна причина возникновения ООП. Пример с уравнением легко можно решить с помощью обычных примитивов, никаких объектов тут не надо. А вот задачу с концовками игры сложно будет даже описать, не используя каких-то придуманных тобой же классов. Но при этом описать ее в классах и объектах достаточно легко: нам явно будет нужен класс Игра, класс Сталкер, класс Концовка, класс РешениеИгрока, класс ИгровойМомент и так далее. То есть даже не приступив к решению задачи, мы в голове можем легко представить «наброски» ее решения. Усложнение задач поставило программистов перед необходимостью делить задачу на части. Но в процедурном программировании сделать это было не так просто. И очень часто программа представляла собой «дерево» из кучи веток со всеми возможными вариантами ее работы. В зависимости от каких-то условий, программа выполнялась по той или иной ветке. Для небольших программ такой вариант был удобен, но поделить на части объемную задачу было очень сложно. Эта необходимость стала еще одной причиной возникновения ООП. Эта концепция предоставила программистам возможность делить программу на кучу «модулей»-классов, каждый из которых делает свою часть работы. Все объекты, взаимодействуя между собой, образуют работу нашей программы. Кроме того, написанный нами код можно повторно использовать в другом месте программы, что также экономит большое количество времени.
Комментарии (170)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Ислам Уровень 19
30 апреля 2023
Интересная лекция. Like
Pavel Tsygankov Уровень 19
16 марта 2023
ребята, помогайте не очень умному как мы смогли отправить в барбершоп объекты типа cat и dog, если shear принимает в качестве параметра объекты типа animal? Почему это возможно? код из статьи: public class AnimalBarbershop { public void shear(Animal animal) { System.out.println("Стрижка готова!"); } } public static void main(String[] args) { Cat cat = new Cat(); Dog dog = new Dog(); AnimalBarbershop barbershop = new AnimalBarbershop(); barbershop.shear(cat); barbershop.shear(dog); }
otreyo Уровень 38
14 марта 2023
полиморфизм тут описан как то странно. и вообще как правило это самая плохообъясненная часть ООП, у него нет четкого определения и везде его объясняют по разному. как я понимаю ПОЛИМОРФИЗМ - это возможность для разных классов, имеющих общее свойство, реализовать это свойство по своему. ну допустим птица и самолет, оба умеют летать, поэтому у обоих должен быть какой то метод описывающий эту способность. fly к примеру. поэтому у обеих будет интерфейс который декларирует это свойство. но летают они по разному, поэтому в каждом случае и реализация будет разной. Это и есть полиморфизм - есть классы с общим свойством, оно абстрактное, но у разных классов (конкретных объектов) есть конкретная реализация этого свойства, и она будет разной, в зависимости от объекта
Andrey Уровень 5
12 марта 2023
Я не знаю почему абстракцию тут пишут как 4й принцип ООП. Есть разные приемы ООП, такие как ассоциация, агрегация, композиция и еще некоторые комбинации, абстракция это очень общее понятие, но в классических трудах все равно выделяют наследование, инкапсуляцию и полиморфизм. Хотя, как мне кажется, именно полиморфизм пришел с ООП, первые 2 уже были в С в виде "хаков". Сорри если оффтоп.
Никто Уровень 2
31 января 2023
Шилд - "Существует 3 принципа ООП".
Idunno Lol Уровень 10
28 января 2023
ХОРОШАЯ СТАТЬЯ
13 января 2023
не чего не понятно, нооо очень интересно , если бы мы знали что это такое , но мы не знаем что это такое . Страшно , страшно. Очень страшно....
Gvrko Уровень 32
9 января 2023
Набор методов которые можно вызвать у переменной определяется типом переменной(левая сторона), а какой именно метод (какая реализация) вызовется, определяется типом объекта(правая сторона) Animal dog = new Dog();
Aionymous #3229788 Уровень 12
9 января 2023
Надеюсь, на практике будет понятнее🧐
Сергей Уровень 20
26 декабря 2022
этот квест будет просто как закрепление первого квеста?