Как стать автором
Обновить

Комментарии 65

НЛО прилетело и опубликовало эту надпись здесь
спасибо, поправлю.
первый вариант предпочтительнее, я думаю, поскольку лаконичнее и тем не менее понятнее. да и вроде как Мартин Фаулер в «Чистый код» советовал писать как в первом вариант
В «Чистом коде» советует Роберт Мартин, а не Мартин Фаулер :)
Хорошая статья, дала хороший толчок наконец то освоить SOLID в практике
спасибо!
public function removeItem($item){
    if(is_null($this->currentOrder)){
        return false;
    }
    return $this->currentOrder = new Order();
}

Улыбнул метод удаления продукта из заказа.

А так, спасибо за статью. Пригодится!
действительно интересный способ)
спасибо, что заметили, уже исправляю
В общем и целом согласен со всем. НО…
Есть несколько моментов, которые меня смутили.

1) Не
$this->config->getDns()
, а
$this->config->getDsn()


2)
… создать интерфейc IOrderSource, который будет реализовывать соответствующими класса MySQLOrderSource, ApiOrderSource и так далее...
Кажется я не понял. У нас интерфейс с реализацией?

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

Как-то так;)
извиняюсь, это всё опечатки, как мог вычитывал, но глаза замылились совсем, спасибо, что замечаете их.
… создать интерфейc IOrderSource, который будет реализовывать соответствующими класса MySQLOrderSource, ApiOrderSource и так далее...

должно быть
… создать интерфейc IOrderSource, который будет реализовываться соответствующими классами MySQLOrderSource, ApiOrderSource и так далее...
И лучше, наверно, юзера и пароль внести в DSN, а не пихать их отдельными атрибутами в конструкторе PDO.
Все проблемы программирования можно решить дополнительным слоем абстракции… кроме проблемы избыточной абстракции

© David Wheeler
Я из-за этой статьи уволился с предыдущей работы.
Проверяем, зависят ли классы от каких-то других классов(непосредственно инстанцируют объекты других классов и т.д) и если эта зависимость имеет место, заменяем на зависимость от абстракции.

Это называется «статическая зависимость».

Судя по тексту, с LSP вы сами до конца не разобрались. Описание этого принципа выглядит очень сумбурно и не содержит информации о том, какой же профит можно получить ему следуя.
c LSP, на мой взгляд, ситуация следующая (хотя и со всеми принципами так, да и не только в программировании) — мы не столько что-то получаем, соблюдая принцип, а что-то теряем и получаем негативный эффект, когда не соблюли его. В случае с несоблюдением LSP мы получаем изменчивость в поведении там, где мы её не ждали.

Если можно, бытовой пример — к примеру, Вы чистите зубы по утрам каждый день и Ваши зубы здоровы. Но если бы Вы не чистили зубы, у Вас мог образоваться кариес, т.е. не соблюдение какого-то принципа приводит к негативному эффекту. Хотя можно на это посмотреть и с другой стороны, что соблюдение принципа поддерживает Ваши зубы в здоровом состоянии. Но тогда можно сказать, что профит от LSP — поддержание кода в «здоровом» состоянии :)
Это принцип «поддержание кода в здоровом состоянии», а не LSP.
Оценивать тот или иной подход нужно, сравнивая с конкурентами, а не с абстракцией.
Т.е. да, чистить зубы хорошо, но чистить их можно тысячей способов и вот их нужно сравнивать. А вы сравниваете нечистку зубов с чисткой именно зубной пастой.
В случае с несоблюдением LSP мы получаем изменчивость в поведении там, где мы её не ждали.


Можно хоть один пример исходя из вашего описания?
Ибо у вас все очень сумбурно и с путаницей в описаниях.
Это, как раз, в тексте есть (см. «Пример иерархии прямоугольника и квадрата и вычислении их площади»). Метод calculateRectangleSquare работает с фигурами рассчитывая на то, что их поведение не будет отличаться, а это не так. Чтоб добиться правильного поведения, нужно править метод и реализовывать различную логику для квадрата и прямоугольника.

Но описано это все, как я писал выше, очень сумбурно.
Что касается примера. Простейший пример нарушения LSP:

sub processOperation
{
	if ($operation->type eq 'refund') {
		$operation->amount = $operation->amount * -1;
	}
           …
	$operation->process();
}

Нарушение LSP ведет к нарушению OCP т.к. появление еще одной операции, аналогичной возврату, потребует внесения изменений в метод processOperation.

Мой и ваш примеры изложены и подробно разжеваны в книге Дяди Боба «Быстрая разработка программ», в которой SOLID посвящен целый раздел. Можете порекомендовать ее в своем посте и прочесть, если еще этого не сделали.
я конечно извиняюсь, но — каким образом Ваш пример отражает принцип Лисков? Возможно, Вы не привели его до конца или что-то не досказали?
возможно, моя сумбурность в объяснении связана с тем, что у меня более математический склад ума, а сам по себе принцип Лисков более математичен, поэтому приведу его математическое описание из статьи Лисков:
Пусть q(x) является свойством, верным относительно объектов x некоторого типа T. Тогда q(y) также должно быть верным для объектов y типа S, где S является подтипом типа T


Так вот, где в Вашем пример подтип для processOpeartion? В контексте одного класса нельзя говорить о принципе Лисков.

Возвращаясь к приведённому мною в статье примеру (к слову, в приведённой Вами книги как раз описан такой же пример с квадратом и прямоугольником)

Предположим, разработчик знает о наличии классов Square и Rectangle, что они связаны в иерархию и меют методы для получения/установки высоты/ширины.

И наш программист создаёт следующий метод, который вполне логичен:

function myRectangle(Rectangle $rectangle)
{
    $rectangle->setWidth(4);
    $rectangle->setHeight(5);
    return $rectangle->getHeight * $rectangle->getWidth == 20;
}


И выполняет этот метод дважды следующим образом
myRectangle(new Rectangle()) // true;
myRectangle(new Square()) // false;


И теперь возвращаемся к принципу Лисков:
тип Т — класс Rectangle
подтип S — класс Square
свойство q — произведение width и height равно 20

но мы получаем, что для подтипа S (класса Square) свойство q не верно.
цитирую, что написано в Вашей книге
Пример с функцией показывает, что могут существовать функции, которые работают с указателями или ссылками на объекты Rectangle, но не могут оперировать с объектами Square. Таким образом, при использовании таких функций объект Square не является заменяемым для Rectangle, взаимосвязь между такими объектами нарушает принцип подстановки Лискоу


Надеюсь, сейчас пояснил чуть яснее.

Кроме того, это всё можно выразить в терминах контрактного программирования — пост и предусловия, инварианты.

И кроме того, чуть дальше в Вашей книге в этом разделе есть подтема «Неправильное поведение объекта», это как раз то, о чём я сумбурно рассказал, приводить данный раздел целиком не буду (страница 193 — «Неправильное поведение объекта»)
Пример показывает, что бывает, если ему не следовать :) Возвращаясь к вашему. Когда разработчик замечает проблему, он первым делом не меняет иерархию, а переписывает функцию примерно так:

function calculateRectangleSquare(Rectangle $rectangle, $width, $height)
{ 
    if (get_class($rectangle) == "Square") {
        $rectangle->setWidth($width);
    } else {
        $rectangle->setWidth($width);
        $rectangle->setHeight($height);
    }
    return $rectangle->getHeight * $rectangle->getWidth;
}

Теперь calculateRectangleSquare работает корректно, но нарушает OCP, а причиной этого стало нарушение классом Square принципа LS.
Простите меня, но где вы тут увидели нарушение OCP?
По одному методу то, когда принцип касается правила использования классов.
по примерам не понятно, как считать площадь фигур, в итоге, и каким образом преобразованный код иллюстрирует LSP, если ни наследования, ни полиморфизма в нем не осталось?
в рамках примера — наследования и полиморфизма не осталось, потому что с точки зрения поведения Square не является Rectangle, а связь IS-A как раз имеет прямое отношение к поведению объектов — поэтому в данном конкретном случае решение — избавление от наследования и связи двух этих классов.
Большинство и не задумывается над профитом, умные дяди сказали — хорошо, значит все правильно!
К сожалению, очень верно. И делают всё через сотни слоёв абстракции там, где она не нужна совсем или нужна по минимуму.
На мой взгляд, в принципах SOLID нет каких-то лишних и не нужных абстракций (если я где-то привёл в примерах что-то лишнее, что можно было бы сделать проще, то пишите, мне на самом деле всегда интересно знать другие решения)

Я же как разработчик прекрасно понимаю, что лишняя абстракция — это лишний шаг другого разработчика, который читает мой код, на пути понимания то, что я написал. И именно поэтому я немного скептически отношусь к паттернам проектирования и совсем скептически к использованию паттернов ради паттернов.
Зависит от того, насколько фанатично применять. Примеры сами по себе к практике мало отношения имеют и нужны для иллюстрации принципов, так что с ними всё нормально.
большинство не делают ни чего из того, о чем вы написали)
Не делать и делать фанатично вредно. Фанатичность, наверное, даже чуть вреднее.
andrewnester, у меня к вам вопрос — у вас в примере используется функция вне класса. Не могли бы вы прокомментировать ваше отношение к таким функциям: используете ли, где они расположены, как они взаимодействуют с классами и объектами.
В примере про заказы интересует, в какой момент вы заполняете поле config — если в чем-то вроде контроллеров, то можно ли пример такого контроллера в контексте SOLID. Спасибо.
функции вне класса использую довольно редко, в примере она использована просто для того, чтобы показать использование классов Square и Rectangle.

обычно использую просто функции в случаях
1) код написан с использованием парадигмы функционального программирования и не очень хорошо, мешать ООП с ФП. часто это задачи связаны с реализацией какого-нибудь алгоритма.
2) при написании относительных небольших скриптов для командной строки, где разного рода абстрагирования и иерархии — лишнее
3) когда-то при написании плагинов для Wordpress (при написании более-менее больших плагинов всё-таки переходил к ООП)
4) часто использую анонимные функции в качестве callback-функций

по поводу поля config.
если продолжать в рамках исходного примера, я бы сделал что-нибудь такое
// точка входа - index.php

//...


$config = new Config('path/to/conf');

$app = new Application($config);
$app->run();


// example controller

class ExampleController extends BaseController
{
	//...

	public function saveAction()
	{
		$config = $this->getApplication()->getConfig();
		$respository = $config->getServices()->getRepositoryManager()->getRepository('Order');
			
		//...
	}
}

// если хотим изменить источник, можно написать что-нибудь такое

public function saveAction()
{
	$config = $this->getApplication()->getConfig();
	$respository = $config->getServices()->getRepositoryManager()->getRepository('Order');
	$repository->setSource(new ApiOrderSource($config->getSources()->getApi()));

	//...
}


// сам файл конфигурации может выглядеть как-нибудь так

//...
{
	'services' : {
		'RepositoryManager' : {
			'class' => 'RepositoryManager',
			'options' => {
				//...
			}
		},
		//...
	},
	'sources' : {
		'MySQL' : {
			'host' => 'host'
			'username' => 'name',
			'password' => 'password',
			'dbname' => 'dbname'
		},
		'API' : {
			'getaway' => 'http://apipath.com/api',
			'oauth_key' => '',
		}
	}  
}
//...
Ну вот смотрите, разве saveAction выполняет одну функцию? Т.е. если обобщить до предела, то да, он загружает, допустим, контент для конкретного URL. Но таким образом можно обобщить и все ваши классы Order в один и сказать, что он выполняет одну функцию — работа с заказом. Расскажите, как вы обходите это противоречие и как это вписывается в SOLID.
можно дойти до крайности и в другую сторону и сказать, что цикл for выполняет не одну функцию, а 2 — и инкрементирует значение, и производит сравнение :)

по сути вопроса:
лично я основываюсь на таком вот способе определения выполняет ли метод 1 функцию или нет. Он приведён в книге Мартина «Чистый код» в разделе «Правило одной операции»:
Если функция выполняет только те действия, которые находятся на одном уровне под объявленным именем, то эта функция выполняет одну операцию


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

Это речь об абстракции, а не о единой операции.
можно дойти до крайности и в другую сторону и сказать, что цикл for выполняет не одну функцию, а 2 — и инкрементирует значение, и производит сравнение :)

Именно так, поэтому я и уточняю, раз вы говорите, что разобрались с этим вопросом, как вы определяете одну операцию?
Если функция выполняет только те действия, которые находятся на одном уровне под объявленным именем, то эта функция выполняет одну операцию

Что за уровни?
Что за уровни?

Это речь об абстракции, а не о единой операции.

Уровни абстракции/ответственности, поэтому я и завёл речь об абстракциях.

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


Каждая функция/метод выполняет только те действия, которые находятся на одном уровне абстракции/ответственности и соответственно причина для её/его изменении может быть только одна
Каждая функция/метод выполняет только те действия, которые находятся на одном уровне абстракции/ответственности и соответственно причина для её/его изменении может быть только одна

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

И еще вопрос, каким образом вы храните id заказа во всех классах, в виде ссылки на класс Order?
И тогда причин для изменений будет практически столько же сколько действий?


Цитирую Мартина (снова):
So a responsibility is a family of functions that serves one particular actor. (Robert C. Martin)

An actor for a responsibility is the single source of change for that responsibility. (Robert C. Martin)


Вы почему-то связываете причины изменения с конкретными функциями/методами, я же Вам говорю, что причины связаны с уровнями абстракции и ответственности. Если Вы выделяете каждую функцию/метод в свой уровень абстракции/ответственности, то да, причин будет столько, сколько функций/методов. Я же так не выделяю. Я выделяю слой представления данных, слой бизнес-логики и т.д., каждый из этих общих слоев может быть разбит ещё на какие-то исходя из задач и целей проекта конкретного проекта (например, бизнес-логику зачастую удобно разбивать на абстракции на основании данных из предметной области)

А вообще функция/метод должны делать ни больше, ни меньше чем указано в их названии.

И еще вопрос, каким образом вы храните id заказа во всех классах, в виде ссылки на класс Order?

Ну, например, в случае с классом Customer я использовал композицию объекта Order. Других классов у себя я не нашёл в примере.
Встречный вопрос — какое это отношение имеет к принципам SOLID? это уже детали реализации либо я Вас неправильно понял
На более простых словах это можно описать так — все классы, функции и т.д. должны проектироваться так, чтобы для изменения их поведения, нам не нужно было изменять их исходный код.


Во-первых, не все программные сущности. 100% расширяемости достичь невозможно, поэтому замкнутость должна быть стратегической. Об этом написано у Uncle Bob. То есть программист заранее должен позаботиться и спроектировать свою программу так, чтобы она была расширяема только для определенного вида изменений.

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

По поводу второго пункта с Вами не согласен. Цитата из статьи Мартина «The Open-Closed Principle»:
1. They are “Open For Extension”.
This means that the behavior of the module can be extended. That we can make
the module behave in new and different ways as the requirements of the application
change, or to meet the needs of new applications.

2. They are “Closed for Modification”.
The source code of such a module is inviolate. No one is allowed to make source
code changes
to it.


Так что, как раз тут то, о чём я говорю — мы должны проектировать классы так, чтобы могли изменить их поведение, без изменения его исходного кода.
Использование же адаптера — это просто изменение интерфейса на более удобный, поведение в целом остаётся таким же.
etyumentcev прав. Советую почитать автора принципа Б.Мэйера. Книжку ниже я вам уже посоветовал.
НЛО прилетело и опубликовало эту надпись здесь
допустим метод printOrder имеет у нас следующий вид

public function printOrder($order)
{
	$fields = $this->prepareOrderFields($order);
	//...
	return $this->printFields($fields);
}


И допустим нам захотелось поменять метод prepareOrderFields, чтобы он как-то по-другому обрабатывал поля заказа. Но окажется, что данный метод prepareOrderFields используется ещё в методе сохранения заказа и таким образом мы повлияем на сохранение заказа, сами того не ожидая
НЛО прилетело и опубликовало эту надпись здесь
А я правильно понимаю, что в итоговом примере с Dependency Invertion класс Customer остался зависим от класса Order, но в данной ситуации этого не избежать?
не совсем. Класс Customer не зависит от какой-то конкретной реализации класса Order, т.е. объектом currentOrder может быть и экземпляр, например, класса FreeShippingOrder, и класса LimitedItemCountOrder (классы немного надуманы, не обращайте внимания).
Если честно, то не совсем понятно.
В методе ведь используется конкретная реализация, а не абстракция.
public function addItem($item){
        if(is_null($this->currentOrder)){
            $this->currentOrder = new Order();
        }
        return $this->currentOrder->addItem($item);
    }


Я предположил бы, что такая реализация была бы более под стать принципа
//...
public function addItem(IOrder $currentOrder, $item){
        return $currentOrder->addItem($item);
    }
//...

//...
$oOrder = $oCustomer->currentOrder ?: new Order();
$oCustomer->addItem($oOrder, $oItem);

Что скажете?
да, всё верно, значит в первом случае Вы были правы.
это я упустил из виду, что не добавил метод или параметр для установки подтипа заказа.

вообще строго говоря такие нюансы(должны ли быть какие-то подтипы для Order и т.д.) — это детали конкретного проекта, в учебном примере я решил показать на примере класса OrderProccessor как избавиться от зависимости от конкретной реализации и перейти к зависимости от абстракции. и такой принцип может быть применён ко всем другим зависимостям, что Вы успешно и сделали в примере выше, спасибо :)
Да, конечно, в каждом конкретном случае все зависит от деталей. Просто хотелось понять сам принцип, и в каких случая его стоит применять.
Вы верно всё поняли — вы как раз и заменил конкретную реализацию, на абстракцию и классы теперь у Вас связаны, строго говоря, слабее и Вы всегда можете добавить новый класс, реализующий IOrder, и без труда использовать его
Спасибо за разъяснения и за статью, конечно.
Немного опоздал, но вот здесь человек разжовывает каждый из СОЛИД принципов с примерами кода.
blog.byndyu.ru/2009/10/solid.html
calculateRectangleSquare(new Square, 4, 5); // 25 ???

Ваш пример абсолютно не показал нарушения LSP. Ваш объект Square успешно используется и более того, он вычисляется правильно.
Дальше, почему вы используете функцию, которая изменяет объект, а потом вычисляет некоторое значение? Это вообще не попадает не под одну парадигму, ни под функциональную ни под Объектно ориентированную.
И если мы уж говорим о ООП, то давайте не смешивать два подхода и удивляться, что что-то пошло не так, по вашему мнению
К чему я веду, да к тому что принцип подстановки Барбары Лисков не раскрыт чуть больше чем полностью. Вы говорите вообще не о том. Весь принцип сводится к интерфейсу класса.

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

class Rectangle
{
    protected $width;
    protected $height;

    public function __construct($width, $height ) {
        $this->width = $width;
        $this->height = $height;
    }

    public function getWidth()
    {
        return $this->width;
    }

    public function getHeight()
    {
        return $this->height;
    }

    public function calculateSquare()
    {
       return $this->height * $this->width;
    }
}

class Square extends Rectangle
{
    public function __construct($size) {
        parent::__construct($size, $size);
    }
}


class TwoPointsData
{
     private $width;
     private $height;

     public function getWidth() 
     { 
        return $this->width; 
     }

     public function setWidth($width) 
     { 
        $this->width = $width; 
     }

     public function getHeight() 
     { 
        return $this->height; 
     }

     public function setHeight($height) 
     { 
        $this->height = $height; 
     }
}

class Rectangle 
{
    public function __construct(TwoPointsData $data) 
    {
        ...
    }

    public function calculateSquare()
    {
       return $this->height * $this->width;
    }
}

class Square extends Rectangle
{
    public function __construct(TwoPointsData $data) 
    {
        ...
    }
}
Дело не в правильности или неправильности вычисления площади квадрата, дело в замене поведения методов класса
Принцип гласит — «Объекты в программе могут быть заменены их наследниками без изменения свойств программы»

Мы ожидаем от программы (в данном случае метода calculateRectangleSquare) одно поведение, получаем другое. Нарушение принципа на лицо

В Вашем примере я не увидел
1) замену объекта его наследником
2) ваш объект класса TwoPointsData нигде не используется
Дело не в правильности или неправильности вычисления площади квадрата, дело в замене поведения методов класса
Принцип гласит — «Объекты в программе могут быть заменены их наследниками без изменения свойств программы»

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

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

Извините, но вы не понимаете LSP
Забыл про тип возвращаемого значения. Тип возвращаемого значения должен быть такой же как и тип перегружаемого метода.
Чем это
«Объекты в программе могут быть заменены их наследниками без изменения свойств программы»

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

кроме языка на котором это написано?

Если я не понимаю LSP, то приведу другой пример
Удовлетворяет ли данному принципу следующий код и почему?
class DataCollector
{
	protected $data = array();

	public function add($item)
	{
		$this->data[] = $item;
	}

	public function getData()
	{
		return $this->data;
	}
}

class AnotherDataCollector extends DataCollector
{
	public function add($item)
	{
		$this->data[$item] = true;
	}
}
В том виде, в котором написано — да.
Подставьте вместо DataCollector AnotherDataCollector, поведение программы не изменится. Изменится результат.

Вопрос, что результат будет вас не удовлетворять — лежит совершенно в другой плоскости.
Принцип не соблюдён.
Пост-условие контракта для метода add($item)
$this->data[count($this->data) - 1] == $item
В переопределённом методе данное условие не выполняется(изменено)
Где вы этого бреда нахватались. Что ещё за пост-условие контракта?
В контракте у вас описано, что возвращается итератор. Он как возвращался, так и возвращается. Беда PHP в том что списки и словари объединены, но это отдельная тема. У вас же концептуальное не понимание, что такое свойство контракта.
Контракт это не мифические вещи, которые программист придумал и описал в юнит-тесте, как ему вздумалось, а условия переданные путем языковых конструкций.

Если грубо, то при замене класса его наследником программа должна дальше работать без ошибок. Либо выбрасывать ошибки такого же типа, что кидала и с родительским классом. Всё остальное — это не понимание LSP.

Советую почитать Б.Мэйера «Объектно-ориентированное конструирование программных систем»
Извините, но вы не понимаете LSP

Если можно, попрошу вас привести пример нарушения принципа LSP, хотел бы разобраться
Чуть выше, случайно умудрился ответить :)
class A
{
     protected $data;
     public function add($item)
     {
        $this->data[] = $item;
     }
}

class B extends A
{
     public function add($item)
     {
        if ($item < 52) {
           throw new Exception("Итем не может быть меньше 52");
        }
        $this->data[] = $item;
     }
}

Вот тут идет нарушение. Класс наследник в методе add бросает исключение, которое не бросает класс родитель. Это нарушение LSP.

Вообще SOLID стараются как можно больше перенести в язык(на сколько это возможно), т.к тогда можно контролировать на уровне компилятора(интерпретатора). Но естественно, это не всегда возможно.
И если мы уж говорим о ООП, то давайте не смешивать два подхода и удивляться, что что-то пошло не так, по вашему мнению
К чему я веду, да к тому что принцип подстановки Барбары Лисков не раскрыт чуть больше чем полностью. Вы говорите вообще не о том.


Если бы я создал класс, такой как пример ниже, то что-то принципиально бы изменилось?
class Calculator
{
	private $width;
	private $height;

	public function setWidth($width)
	{
		$this->width = $width;
	}

	public function setHeight($height)
	{
		$this->height = $height;
	}

	public function calculate(Rectangle $rect)
	{
		$rect->setWidth($this->width);
		$rect->setHeight($this->height);
		return $rect->calculateSquare();
	}
}


Весь принцип сводится к интерфейсу класса.

Не понял, что вы имеете ввиду
Если бы я создал класс, такой как пример ниже, то что-то принципиально бы изменилось?

Да, тут явно видны две ошибки.
1. Нарушен Принцип единственной обязанности (класс изменяет объект и что-то вычисляет, используя его как параметр)
2. Нельзя менять входящие параметры в методе.
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.