solid

ПРИНЦИПЫ SOLID: Начать с объектно-ориентированного программирования

Наши соц. сети: instagram, fb, tg

Привет всем, сегодня я хотел бы обсудить нечто SOLIDное.

Солидное? Нееет! Я не о солидности.

погоди ... а может, именно об этом!

Сегодня я пошел на собеседование, и мне был задан вопрос о «принципах SOLID». И я такой "гм ... я не совсем уверен", что означало "я совсем не понимаю о чем ты говоришь". Выходя из офиса, я почувствовал, что определенно нуждаюсь в исследовании принципов SOLID, чтобы идти дальше в направлении объектно-ориентированного программирования в, том самом, правильном направлении.

Поэтому сегодня я хотел бы охватить эту тему несколькими базовыми определениями и простыми примерами, которые помогут нам понять концепцию в целом.

1) Что? Где? Когда? ПРИНЦИПЫ SOLID

SOLID означает …

S.O.L.I.D является аббревиатурой для первых пяти принципов объектно-ориентированного проектирования (OOD) ** Роберта Мартина.

S - Принцип единственной ответственности (single responsibility principle)

O - Принцип открытости/закрытости (open closed principle)

L - Принцип подстановки Барбары Лисков (liskov substitution principle)

I - Принцип разделения интерфейса (interface segregation principle)

D - Принцип инверсии зависимостей (dependency inversion principle)

На самом деле речь идет о наборе принципов, которые мы можем учитывать при кодировании, уточнении и структурировании кода.

SOLID может…

Прочитав это, я понятия не имел, что это должно означать. Насколько я понимаю, эти принципы заключаются в том, чтобы разработчикам было проще и понятнее разрабатывать программное обеспечение, которое легко поддерживать и модифицировать, когда его масштаб увеличивается и усложняется. Таким образом, мы можем действительно понять эту концепцию как способ приблизиться к объектно-ориентированному программированию, урезав ненужную часть и организовав код таким образом, чтобы улучшить и реструктурировать.

2) Погрузись в: ПРИНЦИПЫ SOLID

Первый принцип: Single-responsibility Principle

«У класса должна быть одна-единственная причина для изменения, и это означает, что у класса должна быть только одна единственная работа».

Это так же просто как и звучит, класс должен выполнять только одно задание, а не несколько заданий в одном и том же классе.

Давай посмотрим на пример внизу.

class Book {
protected $Author;
public getAuthor($Author) {
return $this->Author;
}
public function formatJson() {
return json_encode($this->getAuthor());
}
}

Как ты думаешь, ты уже овладел этим?

Мы видим, что класс с именем Book выполняет более одной работы, которые получают автора книги, а также кодирует вывод в формате Json. Но что, если мы хотим получить вывод в другом формате, не только Json? В этом случае нам, вероятно, придется написать еще несколько строк, чтобы добавить больше методов или модифицировать существующий код. На данный момент это может показаться незначительной работой, потому что этот пример, который у нас есть, чрезвычайно прост, но представь, что есть сотни или даже тысячи классов, было бы лучше заранее предотвратить путаницу, реализовав эти принципы.

Таким образом, подход, который мы можем использовать, будет что-то вроде этого

class Book {
protected $Author;
public getAuthor($Author){
return $this->Author;
}
}
class JsonBookForm {
public function format(Book $Author) {
return json_encode($Author->getAuthor());
}
}

Таким образом, если мы хотим получить другой тип выходных данных, нам не нужно вносить изменения непосредственно в класс Book.

Второй принцип: Open-closed Principle

«Объекты или сущности должны быть открыты для расширения, но закрыты для модификации».

Я знаю, что становится скучно, но на самом деле это означает, что классы следует расширять для изменения функциональности, а не для изменения. Это должно быть способом расширения его функциональности, когда они хотят добавить больше опций, и это не должно иметь место, просто изменяя существующий код напрямую.

Есть общий пример, который приведен ниже.

class Triangle{
public $width;
public $height;
}
class Board {
public $triangles= [];
public function calculateArea() {
$area = 0;
foreach ($this->triangles as $triangle) {
$area += $triangle->width * $triangle->height* 1/2;
}
}
}

У нас есть класс Triangle, который содержит данные для треугольника шириной и высотой, и класс Board, который используется в качестве массива объектов треугольника.

Этот код на данный момент выглядит абсолютно нормально, но когда ты подумаешь о форме и вычислении функции площади, возможно, ты захочешь использовать эту функцию позже для других фигур, а не только для треугольника, чтобы повысить эффективность. На данный момент эти строки кода не позволяют этого, ограничивая класс Board, работающий только с классом Triangle.

Подход, который мы можем попробовать здесь:

interface Shape {
public function area();
}
class Triangle implements Shape {
public function area() {
return $this->width * $this->height *1/2;
}
}
class Circle implements Shape {
public function area() {
return $this->radius * $this->radius * pi();
}
}

 

 

class Board {
public $shapes;
public function calculateArea() {
$area = 0;
foreach ($this->shapes as $shape) {
$area+= $shape->area();
}
return $area;
}
}

Таким образом, нам не нужно указывать имя класса в имени класса Board, поэтому всякий раз, когда мы хотим добавить больше свойств, например Rectangle, мы можем легко добавить класс с именем себя и реализовать интерфейс Shape, чтобы мы могли использовать area() в классе Board.

Третий принцип: Liskov substitution principle

«Пусть q (x) - свойство, доказуемое для объектов типа x. Тогда q (y) должно быть доказуемо для объектов типа y, где S является подтипом типа T».

Ну да, я знаю, это звучит как мой учитель математики в старшей школе.

Но посмотри на это изображение

solid_round

Знакомо? Ты, наверное, видел это изображение в школе, и я уверен, что твой учитель математики, должно быть рассказывал об этом. Где этот граф может применяться в нашем мире? Это то, что связано с предыдущим примером. Формы.

Говоря о формах, есть много форм, верно? Есть круги, треугольники, прямоугольники и квадраты ... подожди, мы все знаем, что прямоугольник и квадрат похожи, но не одинаковы, верно?

Прямоугольник включает в себя квадрат с точки зрения того факта, что прямоугольнику нужно больше условий, чтобы быть квадратным. Это было бы как-то так.

solid_practice

*** Ой, я поставил имя наоборот!

Общий код, который мы можем применить здесь, будет

class Rectangle {
public function setW($w) {
$this->width = $w;
}
public function setH($h) {
$this->height = $h;
}
public function Area() {
return $this->height * $this->width;
}
}

 

class Square extends Rectangle {
public function set($w) {
$this->width = $w;
$this->height = $w;
}
public function setH=($h) {
$this->height = $h;
$this->width = $h;
}
}

Ну ты понял, что несколько строк выглядят одинаково? и учитывая тот факт, что в этом уроке мы делаем все возможное, чтобы сделать код легким и эффективным, этого следует избегать, а скорее пытаться таким образом.

interface Setshape {
public function setH($h);
public function setW($w);
public function Area();
}
class Rectangle implements Setshape ;
class Square implements Setshape ;

Таким образом, нам не нужно писать один и тот же код снова и снова, и мы можем просто реализовать интерфейс к классу.

Четвертый принцип: Interface segregation principle

«Никогда нельзя заставлять клиента реализовывать интерфейс, который он не использует, или нельзя заставлять клиентов зависеть от методов, которые они не используют».

Мы почти на месте, четвертый принцип означает, что классы не должны принуждать к реализации интерфейсов, которые они не используют. Например, скажем, есть некоторые классы, похожие на

interface Employee {
public function generatereport()
public function clockin()
public function clockout()
public function customerservice()
public function getPaid()
}

И скажем, есть дежурный менеджер, который выполняет большинство функций, перечисленных выше, но не Clockin и Clockout, потому что они могут получать зарплату не ежечасно, поэтому для них эти две функции никогда не будут использоваться. Чтобы снова следовать этому принципу, мы должны подойти к нему следующим образом.

interface Generalemployee{
public function clockin()
public function clockout()
}
interface Employee{
public function customerservice()
public function getPaid()
}
interface management{
public function generatereport()
}

Последний принцип: Dependency Inversion principle

«Объекты должны зависеть от абстракций, а не от конкретных реализаций. В нем говорится, что модуль высокого уровня не должен зависеть от модуля низкого уровня, но они должны зависеть от абстракций».

Я перехожу сразу к примеру

class Getuserlists {
private $dbConn;
public function __construct(MySQLConn, $dbConn) {
$this->dbConn= $dbConn;
}
}
 

Это распространенная ошибка, поскольку в соответствии со структурой иерархии класс Getuserlists является модулем более высокого уровня, а MySQLConn - модулем более низкого уровня, однако ты можешь заметить, что класс полностью зависит от MySQLConn, и в дальнейшем это может привести к путанице, если мы захотим использовать другую базу данных, а не просто MySQL.

Таким образом, решение по этому вопросу будет …

interface DbConnectionInterface {
public function connect();
}
class MySqlConn implements DbConnectionInterface {
public function connect() {}
}
class Getuserlists {
private $dbConn;
public function __construct(DbConnectionInterface $dbConn) {
$this->dbConn= $dbConn;
}
}

Согласно приведенному выше небольшому фрагменту, ты видишь, что модули высокого и низкого уровня зависят от абстракции.

Вывод

Принципы SOLID поначалу казались очень запутанными, и с ними еще придется столкнуться с еще большими трудностями для того, чтобы они стали “родными” и достичь того уровня, когда я могу комфортно юзать их, даже того не осознавая. Но ведь всегда хорошо иметь некоторые рекомендации, которым ты можешь следовать, правда?