Принципы объектно-ориентированного проектирования
Post on 24-Jan-2015
802 views
DESCRIPTION
TRANSCRIPT
Принципыобъектно-ориентированного
проектирования
Interlabs
28 марта 2013
1 / 1
Лирическое отступление
Алан Кей
• термин и концепция ООП;• язык и интегрированная средаразработки Smalltalk;
• современная система GUI сперекрывающимися окнами;
• концепция планшетного компьютера:Dynabook;
• свободная реализация Smalltalk:Squeak;
• виртуальные 3D миры для совместнойдеятельности Croquet;
2 / 1
Объектно-ориентированноепрограммирование
Вообще говоря, бывает сильно разное, в нашем случае:
• функциональность реализуется в виде набора объектов,объединяющих данные и поведение;
• объекты группируются в классы;• классы образуют иерархию;• объекты взаимодействуют между собой, обмениваясьсообщениями по четко определенным протоколам.
3 / 1
Инкапсуляция
Детали реализации должны быть скрыты от потребителя:
• поведение объекта описывается набором четкоопределенных операций, формирующих интерфейсвзаимодействия с объектом;
• состояние объекта может быть изменено извне толькочерез внешний интерфейс;
• весь вспомогательный код должен быть скрыт внутриобъекта;
4 / 1
Закон Деметера• метод может вызывать другие методы своего класса;• метод может вызывать методы своих свойств (но несвойств своих свойств);
• метод может вызывать методы параметров;• метод может вызывать методы своих локальных объектов;• метод не может вызывать методы глобального объекта (ноего можно передать как параметр);
• метод не может использовать цепочку вызовов,возвращающих промежуточные объекты других классов.
Не абсолютная истина, но желательно учитывать.
5 / 1
НаследованиеПлюсы:
• повторное использование кода;• полиморфизм.
Минусы:
• изменения в базовом классе отражаются на всейиерархии;
• большое количество уровней наследования можетсущественно затруднить разработку.
Избыточное наследование — частая проблема6 / 1
Композицияinterface Feature1 {
public function m1();}interface Feature2 {
public function m2();}class Object {
private $f1, f2;
public function __construct(Feature1 $f1, Feature2 $f2) {$this->f1 = f1;$this->f2 = f2;
}public function m1() { return $this->f1->m1(); }public function m2() { return $this->f2->m2(); }
}
7 / 1
Композиция: реализация• в 5.3 объекты-свойства и делегирование вызовов;• можно уменьшить количество кода за счет magic methods;• в 5.4 поддержка на уровне языка:
trait Feature1 {public function m1() {...}
}trait Feature2 {
public function m2() {...}}class Object use Feature1, Feature2 {
...}
8 / 1
Базовые принципы
S. O. L. I. D.
SRP Single Responsibility PrincipleOCP Open/Closed PrincipleLSP Liskov Substitution PrincipleISP Interface Segregation PrincipleDIP Dependency Inversion Principle
9 / 1
Принцип единственнойобязанности
SRP Single ResponsibilityPrinciple
Класс должен нести ответственность завыполнение одной задачи.
10 / 1
SRP: нарушения
Сложный класс, пытающийся делать несколько делодновременно:
• работа с данными разных уровней абстракций;• большое количество зависимостей;• низкая связность между отдельными свойствами объектов;
В пределе получаем «God Class», вся функциональность водном классе.
11 / 1
Нарушение SRP
class User extends ActiveRecord {
// Работа с сущностью: одна ответственность.public function setName($name) {...}public function getName() {...}public function setGroup(Group $group) {...}
// Хранение сущности: вторая ответственность,// изменение способа хранение — переписывание класса.public function save() {...}public function delete() {...}
}
2 / 7
SRP: исправляемclass User extends Entity {
public function setName($name) {...}public function getName() {...}public function setGroup(Group $group) {...}
}
class UserMapper extends SQLMapper {
public function save(User $user) {...}public function delete(User $user) {...}
}
13 / 1
SRP: методы
Принцип SRP применим и к методам:
• метод должен реализовывать единственнуюфункциональность;
• чем меньше метод, тем его проще понимать и отлаживать;• именование методов очень важно, в идеале код долженчитаться как текст, описывающий то, что в нем происходит.
Методы должны быть компактными, если код метода невмещается на экран — значит вы что-то делаете не так.
14 / 1
Связность и связанность
Cohesion (связность) насколько сильно связаныи сонаправлены обязанности модуля
Coupling (связанность) насколько сильномодуль зависит от других модулей
Нужно стремиться к слабойсвязанности, но сильной связности :)
15 / 1
Принципоткрытости/закрытости
OCP Open/Closed Principle
Программные сущности должны быть открытыдля расширения, но закрыты для изменения.
16 / 1
Нарушение OCP
class Logger {
public function log($message) {switch ($this->type) {
case ’syslog’:return $this->logToSyslog($message);
case ’text’:return $this->logToFile($message);
// и так далее}
}}
Добавляем очередной вид логирования — меняем класс.
3 / 7
OCP: исправляем
class Logger {public function log($message) {
foreach ($this->transports as $t) {$t->log($message);
}}
}
abstract class LogTransport {abstract public function log($message);
}
18 / 1
OCP: что делатьЧем меньше кода нужно менять для добавленияфункциональности, тем лучше:
• наследование;• композиция + стратегии реализации в отдельных классах.
Иногда выгоднее не использовать, если увеличение количествавариантов маловероятно: арифметические и логическиедействия, HTTP-методы, операторы SQL и т.д.
Больше классов не значит сложнее!
19 / 1
Принцип подстановкиЛисков
LSP Liskov Substitution Principle
Объекты могут быть заменены объектамипроизводных классов без изменения свойствпрограммы.
20 / 1
Нарушение LSP
class Product {public function getName() {...}public function getAuthor() {...}
}
class Book extends Product {...}class Movie extends Product {
public function getAuthor() {// у Movie нет автора, что делать?
}}
4 / 7
Нарушение LSP<#small_begin |||>
// после добавления увеличивается countclass List {
private $items = array();public function add($item) { $this->items[] = $item; }public function count($item) {
return count($this->items);}
}
// после добавления не увеличивается countclass UniqueList extends List {
public function add($item) {if (array_find($item, $this->items) === false) {
$this->items[] = $item;}
}
<#small_end |||>
5 / 7
Принцип разделенияинтерфейса
ISP Interface Separation Principle
Классы не должны зависеть от интерфейсов,которые они не используют.
Несколько специализированных интерфейсовлучше, чем один общий.
23 / 1
Нарушение ISP
class Contact {public function getEmail() { ... }public function getPhoneNumber() { ... }
}
class Mailer {public function sendTo(Contact $contact) {...}
}
class Dialer {public function call(Contact $contact) {...}
}
6 / 7
Исправляем нарушение ISPinterface Emailable {
public function getEmail();}
interface Diallable {public function getPhoneNumber();
}
class Mailer {public function sendTo(Emailable$contact) {...}
}
class Dialer {public function call(Diallable $contact) {...}
}
25 / 1
Принцип инверсиизависимостей
DIP Dependency Inversion Principle
Высокоуровневые модули должны зависеть неот низкоуровневых, а от абстракций.
Абстракции не должны зависеть от деталей.Детали должны зависеть от абстракций.
26 / 1
Нарушение DIP
// Что делать, если мы хотим читать конфигурацию из базы?class Configuration {
private $storage;private $data;
public function __construct() {$this->storage = new FileStorage(’config.json’);
}
public function load()$this->data = $this->storage->load();
}}
7 / 7
Исправляем нарушение DIPinterface StorageInterface {
public function load();}
class FileStorage implements StorageInterface {...}class SQLStorage implemens StorageInterface {...}
class Configuration {private $storage;private $data;
// Constructor Injection:public function __construct(StorageInterface $storage) {
$this->storage = $storage;}
}
28 / 1
DIP: нарушения
• использование new;• использование registry-объектов (теперь наш класс жесткопривязан к registry и у нас все та же проблема);
• использование Singletone;
Проблемы:
• невозможность расширения без нарушения OCP;• невозможность написания тестов.
29 / 1
DIP: что делать
• не создавать зависимые объекты при создании класса(если это не узкоспециализированные вспомогательныеклассы);
• делить приложение на отдельные слои, слабо связанныедруг с другом;
• использовать интерфейсы для типизации зависимостей;• не использовать глобальные переменные и ихобъектно-ориентированные варианты;
• использовать DI-контейнер.
30 / 1
Самый главный принцип
Думайте и используйтездравый смысл.
31 / 1