SOLID - что это в программировании. ООП

(текст заметки в процессе создания)

SOLID -- это аббревиатура, где первые 5 букв 5-ти принципов разработки программ в стиле ООП составлены в одно слово, которое с английского переводится как "твердый, сплошной, цельный".

Главное, это что, что за принципы скрываются под каждой буквой:

  • S -- Принцип единственной ответственности (The Single Responsibility Principle):

    Каждый класс должен отвечать "за что-то одно", его функционал должен быть направлен на обеспечение этой ответственности, а не чего-то ещё.

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

  • O -- Принцип открытости/закрытости (The Open Closed Principle)

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

    -- вопрос "решен" в ООП, напр, в концепции наследования классов, наследуясь от класса мы расширяем (можем расширить) функционал в потомках, но не модифицируем поведение самого родительского класса.

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

  • L -- Принцип подстановки Барбары Лисков:
    смысл его можно охарактеризовать так (слова Саттера и Александреску из руководства по C++):

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

  • I -- Принцип разделения интерфейса:

    Роберт С. Мартин определил этот принцип так:

    Программные сущности не должны зависеть от методов, которые они не используют.

    Фактически тут предлагается завязываться на "минимальный" интерфейс (в смысле ООП сущности), а классу, который мы хотим использовать таким образом релизовывать этот интерфейс, конечно для удобного выполнения этого принципа крайне желательно чтобы язык программирования поддерживал множественную реализацию интерфейсов.

  • D -- Принцип инверсии зависимостей
    Формулировка:
    • Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций.
    • Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
  • -- опять же сама концепция ООП старается помочь нам следовать этому принципу, но если классы (или их группы) не связаны наследованием, но тем не менее кто-то из них более "общий", а кто-то более "частный" - мы должны уже сами сохранять однонаправленную зависимости, избегая перекрестных.

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

fgh's picture

Класс должен отвечать за что то одно.
При возникновения действия задействовать только один класс.
Простыми словами надо сделать так, чтобы класс не превратился в свалку с изменяющимися методами. У класса не должно быть полно причин, чтобы изменяться.

Пример, как неправильно:

class Auto {
    method1 () {} // новые авто поступающие в магазин на продажу
    method2 () {} // метод, отвечающий учет авто в автосервисе
    methodn () {} // авто, по программе tradein
}

Пример, как правильно (один класс один метод):

class AutoSetSale {
    method1 () {} // новые авто поступающие в магазин на продажу
}
class AutoService {
    method2 () {} // метод, отвечающий учет авто в автосервисе
}
class AutoTradeIn {
    method3 () {} // авто, по программе tradein
}

Источники информации:
Сайт о SOLID
Просто о SOLID (Принципы ООП)

fgh's picture

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

Пример, как неправильно:

Есть массив и цикл перебора массива:

    $array_cars = [
       'BMV' => '70000',
       'AUDI' => '50000';
   ]
    foreach ($array_cars as $key => $value) {
         switch ($key) {
              case 'BMW':
  	          ecoh return '70000';
            case 'AUDI':
  	        ecoh return '50000';	
         }
    }

При добавлении нового элемента массива изменяем массив и цикл foreach:

    $array_cars = [
       'BMV' => '70000',
       'AUDI' => '50000',
       'Tesla'=> '120000';
    ]   
    foreach ($array_cars as $key => $value) {
    switch ($key) {
        case 'BMW':
            ecoh return '70000';
  	    break;
        case 'AUDI':
  	    ecoh return '50000';
  	    break;	
        case 'Tesla':
  	    ecoh return '120000';	
  	    break;
        deafult:
            echo "No Cars Price";
     }
}

Пример, как правильно:


    abstract class Auto {
        function getprice () {} : string;
    }
    class BMV extends Auto {
        function getprice () {
        return '70000';
        }
    }
    class AUDI extends Auto {
        function getprice () {
        return '50000';
        }
    }
    class Tesla extends Auto {
        function getprice () {
        return '120000';
        }
    }
    foreach ($array_cars as $key) {
        for i:=1 to lenght.$array_cars
        '$key':getprice();
    }

Если добавляется новый автомобиль мы дополняем массив , но не изменяем цикл foreach.

fgh's picture

Сущности не должны зависеть от интрфейсов которые они используют. Класс который имплеметируется от интерфейса не должен содержать в себе ненужные методы (так как эти методы должны быть обязательно описаны). Интерфейс надо декомпозировать.

Пример, как неправильно:


Interface AutoSet {
    getTeslaSet();
    getBMVSet();
    getAudiSet();
}

class Tesla impliments AutoSet {
    getTeslaSet();
    getBMVSet();
    getAudiSet();
}

class BMV impliments AutoSet {
    getTeslaSet ();
    getBMVSet();
    getAudiSet();
}

class Audi impliments AutoSet {
    getTeslaSet();
    getBMVSet();
    getAudiSet();
}

Пример, как правильно:

Interface TeslaSet {
    getTeslaSet();
}

Interface BMVSet {
    getBMVSet();
}

Interface AudiSet {
    getAudiSet();
}

class Tesla impliments TeslaSet  {
    getTeslaSet();
}

class BMV impliments BMVSet  {
    getBMVSet();
}

class Audi impliments AudiSet {
    getAudiSet();
}
fgh's picture

Модули высших уровней не должны зависеть от модулей низких уровней. Оба должны зависеть от абстракций. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций. Уменьшение межмодульных зависимостей.

class XmlHttpRequestService {}
// low level
class XmlHttpService extends XmlHttpRequestService {
request (url,type) {}
}

// high level
Class Http {
constructor ( ) {}
get (url,options) {}
post (url) {}
}

Interface Connection {
request (url, options)
}

class http {
constructor ( httpConnection impliments Connectionb) {}
get () {}
post () {}

}

class XmlHttpRequestService {
open () {}
send () {}
}
Interface Connection {
request (url, options)
}
class XmlHttpService impliments Connection {
xhr = new XmlHttpRequestService
request (url, type) {
this.xhr.open
this.xhr.send
}
}

Mock