Три кита объектного ориентирования это: наследование, инкапсуляция и полиморфизм. Без четкого понимания этих вещей программисту тяжело написать хороший объектно-ориентированный код, использовать всю силу этого подхода, а главное устроится на хорошую работу.
В прошлый раз я рассказывал о классах, объектах, свойствах и методах, если с этими понятиями проблем не возникает, дальше тоже всё будет очень просто. Для наглядности я буду приводить короткие куски кода на PHP (на самом деле язык тут не важен, просто мне кажется это самый распространенный на сегодня синтаксис, хоть и более классическим для примеров ООП является java, мне кажется PHP будет более полезным), поясняющие идею, и давать короткие описания.
class Animal {
function draw() {
return "just animal";
}
function eat() {
return "the animal is eating";
}
}
class Cow extends Animal {
function draw() {
Return "something that looks like a cow";
}
}
Как видите здесь Корова (Cow) унаследовала функционал от Животного (Animal), изменив реализацию метода draw (конкретизируя как корова на самом деле выглядит), и оставив реализацию метода eat(). Это и есть наследование. Теперь инкапсуляция. Сам по себе этот термин означает «сокрытие». Инкапсуляция, это способ сделать невозможным изменения критичных для работы класса свойств или вызова внутренних методов. Например у нас есть требование: каждое животное должно иметь кличку, и кличка, в течении его жизни не должна меняться. Самое правильное в таком случае это принимать кличку в качестве параметра конструктора (метода выполняемого при создании класса), и хранить его во внутреннем, сокрытом свойстве. Например так:
сlass Animal {
private $name;
function __construct($name) {
$this->name = $name;
}
function getName() {
return $this->name;
}
...
}
Вот это и есть инкапсуляция. Нет способа изменить кличку снаружи класса, и вы можете быть уверены, что в любом случае, кличка у экземпляра класса будет именно та, что была задана при создании. Ну а теперь полиморфизм. Это тут тоже начнем с примера. Добавим класс Sheep (овца).
class Sheep extends Animal {
function draw() {
return 'something that looks like a sheep';
}
}
Теперь, предположим что у нас есть класс какого-то животного (любого), и мы всегда можем узнать как оно выглядит, абсолютно независимо от его типа (другими словами с экземпляром какого класса мы имеем дело).
$animal = rand(0,1) ? new Cow('burenka') : new Sheep('kudryashka'); echo $animal->draw();
Данный пример будет случайным образом генерировать экземпляр Коровы и Овцы, и рисовать их.
Нужно сказать что данный пример не совсем «чистый» полиморфизм. Дело в том что полиморфизм подразумевает собой реализацию одного и того же интерфейса в разных классах. Объясню: если бы мы не реализовали метод draw() в одном из классов, у нас переодически возникала бы ошибка обращения к несуществующему методу, а в языках со строгой типизицией, ошибка бы возникала еще на стадии компиляции. Чтобы избежать подобных казусов, нужно использовать итерфейсы (interface):
interface IDrawable {
function draw();
}
class Cow extends Animal implements IDrawable {
function draw() {
return 'something that looks like a cow';
}
}
class Sheep extends Animal implements IDrawable {
function draw() {
return 'something that looks like a sheep';
}
}
Как только вы указали, что класс должен реализовывать интерфейс, компилятор или интерпретатор берет на себя обязательство проконтролировать что в классе реализованы методы, описанные в интерфейсе, что позволяет отлавливать ошибки еще до запуска приложения. Вот собственно и всё.