Что бы постичь объектно-ориентированное программирование давайте начнем с первого шага: забудем про программы, процедуры и функции и прочую чертовщину которую учат в последних классах школы и на первых курсах института. Благодаря замечательной системе образования, мной, когда-то, свойства воспринимались как переменные в программе. По аналогии с паскалевскими переменными. А классы как модули программы, которые содержали в себе эти самые переменные и функции. Так вот про подобное восприятие лучше забыть. В ООП все несет совершенно другую смысловую нагрузку. Свойства это некоторые данные которые описывают состояние объекта, методы это поведение объекта, а класс это описание этого самого объекта. ни больше ни меньше. Но обо всем по порядку.
Есть такое слово как "система". Оно означает некоторое множество элементов, находящихся в отношениях и связях друг с другом, которые образуют определённую целостность. Рассмотрим систему, которая у каждого из нас есть, точнее, которая и есть этот "каждый из нас". Система называется Человек. Я немного абстрагируюсь от реальности и выпишу немного из того, что может характеризовать человека.
Итак, каждого человека как-то зовут, он где-то и когда-то родился, где-то учился и работал, завел семью и в итоге умер. Все это мы назовем человеческими свойствами. Они в совокупности своей характеризуют каждого человека. Это есть описание человека и в ООП представляет собой класс.
Теперь с помощью джавы мы запишем свойства Человека кодом и получим класс:
| Java | 1
2
3
4
5
6
| class Human {
String name;
String birthPlace;
Date birthDate;
Date deathDate;
} |
|
Как вы видите, в нашем классе пока отсутствуют свойства, относящиеся к учебе и работе. Их мы добавим позже. Сейчас же мы займемся немного другим - применим на практике Инкапсуляцию, один из принципов ООП.
Вы можете видеть, слышать и трогать других людей, точно так же как и другие вас самих. Следовательно, на классе можно поставить модификатор доступа public.
Но вот ваши свойства, ваше я, от других закрыто, т.е. не написано у вас на лбу. Это ваша личная информация. К ней следует применить модификатор private.
Теперь отобразим эти изменения:
| Java | 1
2
3
4
5
6
| public class Human {
private String name;
private String birthPlace;
private Date birthDate;
private Date dathDate;
} |
|
Но теперь человек ни как не может поделиться информацией о себе. В реальности это было бы молчанием на вопрос "Как тебя зовут?", не из-за не желания что-то сказать, а из-за не способности сделать это. Для этого нужны методы называемые геттерами. Их напарниками служат сеттеры, они существуют для установки значения свойств. Согласитесь, трудно дать человеку имя, если он неспособен его принять. Эти методы имеют следующий вид (я приведу пример на одном свойстве и дальше употреблять их не буду дабы не загружать пост кодом. так что в дальнейшем будем считать что они есть у всех полей для которых они нужны):
| Java | 1
2
3
4
5
6
7
| public String getName () {
return name;
}
public void setName (String name) {
this.name = name;
} |
|
Теперь опишем еще два класса. Один будет представлять работу, другой учебу. Поскольку на работе платят деньги, а на учебе выдают дипломы, мы это отобразим зарплатой и дипломом соответственно:
| Java | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| public class WorkPlace {
private Integer compensation;
public void giveMoney (Human human) {
...
}
}
public class StudyPlace {
private Diploma diploma;
public giveDiploma (Human human) {
...
}
} |
|
Я не буду рассматривать класс Diploma поскольку нам это не будет интересно в дальнейшем. Будем полагать, что там находится: серия, номер и оценки.
Эти классы имеют общие свойства, такие как люди, которые в них перебывают и название. Что бы не дублировать одно и то же два раза мы вынесем эти свойства в некоторую абстракцию и назовем ее Place. Т.е. у нас будет некоторое место, в котором могут перебывать люди, но именно конкретно взятого физического "места" это место иметь не может. Например, можно провести аналогию с музыкальными инструментами: физической сущности "музыкальный инструмент" попросту не существует. Бывают скрипки, виолончели, рояли и бубны которые в сути своей отражают понятие "музыкальный инструмент". Надеюсь понятно. Итак, запишем наше абстрактное место:
| Java | 1
2
3
| public abstract class Place {
protected String name;
} |
|
Обратите внимание на модификатор protected. Он позволяет доступиться до информации частным случаям этого класса. Ведь, например, работа должна знать, как она вообще называется? =)
Как вы могли заметить, наш класс StudyPlace в сути своей тоже должен представлять абстракцию, т.к. понятие "учебное заведение" весьма абстрактно, ведь учиться можно в школе и университете. Т.е. мы выделяем частные, более широкие, сущности из общего понятия. Это называется Наследованием. Посему мы пронаследуем WorkPlace и StudyPlace от Place , а так же School и University от StudyPlace (я опущу код внутри классов для экономии места):
| Java | 1
2
3
4
5
6
7
| public class WorkPlace extends Place {}
public abstract class StudyPlace extends Place {}
public class School extends StudyPlace {}
public class University extends StudyPlace {} |
|
И наконец-то мы добавим недостающие свойства человеку. У него будет некоторое количество заработанных денег и дипломов из разных учебных заведений:
| Java | 1
2
3
4
5
6
7
8
| public class Human {
private String name;
private String birthPlace;
private Date birthDate;
private Date deathDate;
private Long money;
private Collection<Diploma> diplomas;
} |
|
Теперь суммируем. Мы описали четыре класса и две абстракции над ними. Выдали им свойств и таким макаром пробежались по двум из трех основных принципам ООП. Круто. Но у нас есть только свойства, а у всего реального есть еще и поведение. Т.е. мало наличия мозгов, нужно еще ими пользоваться.
Поведение классов описывается интерфейсами, а реализуются методами. Начнем визуализировать этот тезис с учебных заведений, которые выдают дипломы. Сейчас наши два класса и абстракция выглядят так:
| Java | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| public abstract class StudyPlace extends Place {
private Diploma diploma;
public giveDiploma (Human human) {
...
human.getDiplomas().add(diploma);
}
}
public class School extends StudyPlace {
}
public class University extends StudyPlace {
} |
|
Мы уберем из StudyPlace метод giveDiploma (Human human) и сделаем интерфейс, который будет описывать поведение "выдать диплом", который обязаны будут реализовать университет и школа. Согласитесь? у этих заведений слегка разные критерии выдачи диплома человеку. Интерфейс будет следующий:
| Java | 1
2
3
| public interface Graduatable {
giveDiploma (Human human);
} |
|
Теперь мы скажем, что учебное заведение дипломирует людей, но не будем реализовывать в нем саму процедуру дипломирования. Поскольку учебное заведение у нас абстрактно - реализовать это поведение обязаны будут его не абстрактные наследники. В итоге наши два заведения и абстракции будут выглядеть следующим образом:
| Java | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| public abstract class StudyPlace extends Place implements Graduatable {
private Diploma diploma;
}
public class School extends StudyPlace {
@Override
public giveDiploma (Human human) {
...
human.getDiplomas().add(diploma);
}
}
public class University extends StudyPlace {
@Override
public giveDiploma (Human human) {
...
human.getDiplomas().add(diploma);
}
} |
|
Маленькая ремарка: аннотация @Override не обязательна, я ее суда всунул что бы было видно, что эти методы замещают нечто родительское. Что оно делает и зачем вообще нужно лучше погуглить и запомнить навечно.
Собственно теперь мы плавно перейдем к понятию "объект", а потом поговорим о полиморфизме - последнему из основных принципов ООП. Переходить мы будем очень банально - мы опишем жизнь человека:
| Java | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| // В этот момент на свет, из маминой утробы, появляется маленький мальчик
Human joe = new Human();
// Ему выдали свидетельство о рождении, в котором было указано, что он родился сегодня
joe.setBirthDate(new Date());
// И родился он он не абы где, а в стольном граде Нью йорке
joe.setBirthPlace("New York");
// И родители, насмотревшись ситкомов, дали ему имя Джои
joe.setName("Joe");
// Родился он не мертворожденным, потому свидетельство о рождении не сопровождалось свидетельством о смерти
joe.setDeathDate(null);
// И его кредитки из мамы тоже не выпало
joe.setMoney(0);
// Но зато родители сразу решили, что он будет умным и получит много дипломов
joe.setDiplomas(new LinkedList<>());
// Джои рос и мужал. Но его судьба нам совершенно безразлична, пока он не пойдет в школу
// А ему уже 3 года. Быстро идет время.
// Уже 6. Такты процессора неумолимо ведут нашего героя вперед
// И вот ему стукнуло 7 и родители решили отдать его в престижную школу
StudyPlace lowerVasyukiSchool = new School();
lowerVasyukiSchool.setName ("Средняя школа Нижних Васюков");
// Он долго учился
// У него была девушка. Во сне. Лишь однажды
// Он курил с друзьями вещества и пил пиво
// Пока не выпустился со школы и ему не выдали диплом
lowerVasyukiSchool.giveDiploma(joe);
// И он пошел в театральный
StudyPlace teatral = new University();
teatral.setName ("Театральный институт");
// там он тоже отпинал и получил диплом
teatral.giveDiploma(joe);
// Потом он нашел работу в театре и даже получил свою первую зарплату за роли второго плана
WorkPlace work = new WorkPlace();
work.setName("Театр");
work.giveMoney (joe);
// А потом, 27 марта, его убило грудой снега свалившейся с неприметной киевской крыши. Но нам это совсем не важно
joe.setDeathDate(new Date()); |
|
Собственно как работает эта история:
1) Мы создаем новый объект класса Human и привязываем его к переменной joe. Тут нужно понимать что объект, который мы только что создали, не есть переменная joe. Переменная joe ссылается на только что созданный объект. Сам объект это набор байт, который лежит где-то в оперативной памяти и к которому сейчас можно доступиться через переменную joe. После создания наш герой уже уникален и висит в оперативе в одном единственном экземпляре.
2) Далее мы отправляем объекту сообщения, не вызываем методы-сеттеры, а именно отправляем сообщения с содержанием типа "Эй, объект класса Human, я не мог бы ты выполнить вот это свое поведение с вот таким вот параметром".
3) То же самое происходит со школой и университетом.
4) Выдача Джои любого из дипломов это опять же посылка сообщения одному из классов с просьбой выполнить поведение. В данном случае параметром будет не примитив, а, опять же, ссылка на объект, который мы создали в самом начале.
5) Один из образовательных объектов опять же отправит сообщение-просьбу объекту класса Human дать ему объект списка дипломов, которому, опять же, пошлет просьбу на добавление нового диплома.
6) То же самое происходит с работой.
Теперь, поскольку Джои умер, но сборщик мусора еще не превратил его тело в пыль, мы создадим нового человека, чем поможем Джои окончательно умереть:
| Java | 1
2
3
4
5
6
7
| joe = new Human();
joe.setBirthDate(new Date());
joe.setBirthPlace("Washington");
joe.setName("Elin");
joe.setDeathDate(null);
joe.setMoney(0);
joe.setDiplomas(new LinkedList<>()); |
|
Теперь переменная joe ссылается совсем не на нашего Джои, а на некую Элин. Джои же придастся полному забвению, как только сборщик мусора решится почистить память джавовской виртуальной машины. В нашей программе его уже попросту нет, и не будет никогда. Что поделать, объекты от такого умирают.
На этой депрессивной ноте перейдем к полиморфизму. Полиморфизм это проявление многоликости объекта. Выражение его через какой-то совместимый класс или интерфейс, взаимодействуя при этом через поведение этого класса или интерфейса.
Как вы помните, у нас есть два учебных заведения и работа. Что можно с ними сделать? Можем дать ей посмотреть, что ее ждет в грядущем:
| Java | 1
2
3
4
5
6
7
| List<Place> places = new LinkedList<>();
places.add(lowerVasyukiSchool);
places.add(teatral);
places.add(work);
for (Place place : places)
System.out.println( place.getName() ); |
|
Или сразу дать дипломы имеющихся у нас образовательных заведений:
| Java | 1
2
3
4
5
6
| List<Graduatable> graduatable = new LinkedList<>();
graduatable.add(lowerVasyukiSchool);
graduatable.add(teatral);
for (Graduatable grad : graduatable)
grad.giveDiploma(joe); |
|
Принципы работы те же, вот только человек у нас другой.
Если суммировать, то представьте себе, что есть понятие Человек, его представляет класс. Есть я, есть вы, ваши родственники и друзья - это объекты класса человек. Если вы подымаете руку или идете по улице - вы выполняете соответствующее поведение. Накопленный вами жизненный опыт - в какой-то мере ваше состояние описанное свойствами (с точки зрения социума это конечно же ваш паспорт и прочая бюрократическая мешанина). А душа... Ну, не знаю как в ООП, а в джаве есть рефлекшен. =)
З.Ы. Если вы нашли архитектурную ошибку где-то в районе учебных заведений – возможно, вы начали понимать дзен ООП.
|