Привет! Ты когда-нибудь задумывался, почему Java устроена именно так, как она устроена? В том смысле, что ты создаешь классы, на их основе — объекты, у классов есть методы и т.д.
Но почему структура языка такова, что программы состоят именно из классов и объектов, а не из чего-то другого? Зачем было придумано понятие «объект» и поставлено во главу угла? Все ли языки устроены так и, если нет, какие преимущества это дает Java?
Вопросов, как видишь, много :) Попробуем ответить на каждый из них в сегодняшней лекции.![Принципы объектно-ориентированного программирования - 2]()
Кристен Нюгор и Оле Йохан Даль - создатели Simula
Казалось бы, Simula — древний язык по меркам программирования, но их «родственную» связь с Java видно невооруженным глазом.
Скорее всего, ты легко прочтешь написанный на нем код и в общих чертах объяснишь, что он делает :)
![Принципы объектно-ориентированного программирования - 3]()
Принципы ООП:
Что такое объектно-ориентированное программирование (ООП)
Конечно, Java не просто так состоит из объектов и классов. Это не прихоть ее создателей, и даже не их изобретение. Есть множество других языков, в основе которых лежат объекты. Первый такой язык назывался Simula, и его изобрели еще в 1960-х годах в Норвегии. Помимо всего прочего, в Simula появились понятия «класс» и «метод».
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 главные особенности, которые вместе образуют парадигму объектно-ориентированного программирования. Их понимание — ключ к становлению успешного программиста.
Принцип 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
разное поведение: они по-разному подают голос.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ