Pull to refresh

Comments 64

не имплементом а реализацией
и в С++ далеко не обязательно делать полностью виртуальный класс для реализации поведения интерфейса — множественное наследование зачастую решит все проблемы, при этом контроль типов не пострадает
Согласен, заменю американизм имплемент на реализацию. Не согласен, от этого код не только не станет чище, но и появятся новые зависимости, которые увеличат сложность вашей программы((
На хабре действительно есть люди, каким-либо образом связанные с программированием и не знающие чего-либо из описанного в статье?
Хабр читают не только люди с хабра. Это отличный источник статей для школьников/студентов. И да, я уверен не все люди на хабре знают то что описано в статье… Можно голосование замутить…
Я не знал, что такое SOLID. Да и зря узнал, мини-вытяжка из принципов проектирования, для тех, кто не осилил принципы проектирования.
Какие «принципы проектирования» вы имеете ввиду? Книга? На вики?
Эталона то нет. Сошлитесь на какой-нибудь источник информации…
Я говорил не о конкретном источнике, а в целом, про объектно-ориентированное проектирование, паттерны, хорошие решения в различных архитектурах.

Ну вот, к примеру, книга 'Приемы объектно-ориентированного проектирования / Паттерны проектирования' молчит про SOLID. Быть может, конечно, я не внимательно читал, и все же…
1994г — Приемы объектно-ориентированного проектирования. Паттерны проектирования
2000г — SOLID (Роберт Мартин) — link
Что-то я потерял нить разговора.
У вас есть вопросы, замечания к моему комментарию?

P. S. Я имел в виду книгу E. Гамма, Р. Хелм, Р. Джонсон, Д. Влиссилес (питер 2006). Find фоксит ридера не обнаружил там такого слова, по вашей ссылке тоже.
Это переиздание 2006 года. Книга была написана в 1994 году. link
А статья Роберта Мартина с аббревиатурой SOLID появилась в 2000 году.
Прошу прощения, не сразу въехал в 1994, 2000.

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

Аббревиатура солид уже стала популярной? Как часто вы ее видете в статьях, книгах?
Потому и написал целую статью)) Если не давать коротких, запоминающихся имён принципам — очень легко забыть часть и них… Аббревиатура широко известна в узких кругах))
Я вот дважды за последние пару дней столкнулся с этой аббревиатурой, до этого даже не слышал.

P.S. И, имхо, переводить «Single responsibility principle» как «Принцип единой ответственности» не вполне корректно, как-то смысл искажается до обратного, м.б. лучше «Принцип разделения ответственности»? «Трудности перевода»)
Имелось ввиду один объект — одна ответственность. Возможно стоило применить слово единичный
Действительно, замените, пожалуйста. «Единой» — это означает «каждый ответственен за всё». «Единичной» — нет такого оборота в русском языке. MaratGilyazov предложил, думаю, удачную формулировку. (Хотя такого перевода не встречается, чаще всего используют «принцип единственности ответственности», реже — «единственной», ещё реже — «персональной» (The Single Responsibility Principle) — подчёркивая, как в оригинале, что 1 ответственность — 1 объект.)
Вообще в первой главе GoF (книга Паттерны проектирования) имеются описание некоторых принципов. Но они там не все, конечно же. Иначе бы Роберту Мартину было бы нечего делать в 2000 году :)
SOLID — это как-раз таки попытка сформулировать базис для правильного дизайна. Это не столько паттерны, сколько принципы, на которых строится гибкая архитектура системы. Кроме того, на паттернах от GoF мир клином не сошелся) Помимо них, есть и другие, не менее известные. Те же паттерны enterprise приложений от Фаулера — ActiveRecord, Repository, QueryObject и т.п.
да есть. 20 лет увлечения программированием, 15 лет получаю деньги за свою работу, десятки успешных проектов. И с удовольствием прочитал статью.
Очень здорово расписал: подробно и достаточно понятно, чтобы можно было сообразить «с нуля». Разве что, я бы привел некоторые примеры к SOLID, потому что, например, LSP, по-моему, был бы понятнее, если на примере показать, что там, где ожидается базовый класс, можно подставить производный.
По моему мнению, получилась очень холиварная статья, с очень большим количеством пробелов в самых важных местах.

Тот материал, который вы здесь пытаетесь подать для начинающих, для этого слишком сухой. И ссылки на сторонние ресурсы(включая википедию) мало спасают положение. скорее еще больше запутывают.
Гораздо проще понять принципы ООП, на конкретных примерах проектирования, как это сделано у GOF. Но GOF все таки больше ориентирован уже на людей с опытом.
Я не пытался разъяснить все принципы подробно. Я пытался направить людей в правильное русло. Везде, где необходимо приёл ссылки для глубокого изучения. Возможно в следующих статьях я проедусь отдельно по SOLID, по наследованию и т.д.))
На мой взгляд ООП, само по себе, без применения паттернов, вообще мало оправдано, разве что на выходе чуть менее густая каша из кода.
Если ООП использовано правильно, то как минимум в проекте должна была получиться слабая связанность компонентов между собой. Это уже неплохо, даже без паттернов. А если еще и паттерны используются, то ваще комильфо))
Чуть ниже ответил…
В дной из книжек по программированию под J2ME в главе про ООП была такая фраза: «Если для вас ООП — Организация Освобождения Палестины, обратитесь к другой более фундаментальной дисциплине». Теперь эта фраза ассоциируется у меня с неверным пониманием ООП.
Весто «дисциплина» подставьте слово «книга». Отвлекся немного.
Теперь понятно. Просто если делать такие заголовки, то объяснять обязательно нужно, что к чему
Дописал к статье)
Вот в примерах все так всегда радостно, легко и просто, только вот в реальности все далеко не однозначно. Не всегда можно точно определить иерархию (хорошо, когда это физические объекты физического мира, а если это какие-то процессы), да и не всегда стоит разбивать все идеально (в смысле может оказаться, что кода стало много больше, но не сильно проще). Вообще, разработчик должен думать и понимать, для чего все это придумано. Все эти подходы, принципы, паттерны, фреймворки, <подставьте любое другое модное слово сюда> — это инструменты для того, чтобы сделать жизнь проще но даже хорошие инструменты не во всех случаях применимы. Только вот об этом как-то всегда забывают писать. Но это так, мое личное мнение.
bestPractices и паттерны применимы всегда. И про их неприменимость пишут те, кто их применял не совсем правильно или нет тот шаблон выбирал. Попробуйте решить проблемы в старом коде с помощью рефаторинга + паттернов. Возможно многое прояснится.
Шаблоны — это суха выжимка, опыт программистов по уже решённым задачам, имеющим общие основы. И вы в своих задачах лишь уточняете конкретную реализацию шаблона. Словно вам дали трафарет, вы буквы обвели, а выбор цвета и составление слов — это уже ваше дело.
Почитайте уж что ли про «серебряные пули», паттерны и прочее — это как раз они и есть. До них были и после них будут.
Так в том то и дело, что нет универсальных подходов, которые работают всегда и всюду. Где-то работает то, где-то это, а где-то лучше и без них в принципе. Плюс многие подходы — это компромис между чем-то и чем-то. Выбираем Х — отдаем предпочтение объему кода, который следует написать простоте поддержки. Выбираем Y — отдаем предпочтение простоте понимая кода, но теряем производительность. Выбираем Z — получаем решение, которое легко использовать, но сложно расширить. Причем для каждого проекта эти компромисы отличаются и применение одного и того же подхода будет давать разные результаты.
Я не говорю, что шаблоны — это плохо, но надо всегда понимать, что мы получаем при их использовании и чем мы за это платим.

Приведу пример (пример достаточно абстрактный, но право на жизнь имеет):
Есть всем известный и понятный singleton и применили мы его например к классу, который отвечает за вывод отрендеренного изображения на монитор. Допустим, что у нас есть проект, где что-то программно рендерится и показывается юзверю в каком-то окне. Т.е. этот класс получает результат рендеринга или какой-то текст для оверлея и показывает его в основном окне программы. Мы знаем, что юзверь всегда использует полноэкранный режим и окно всегда одно, поэтому и выбрали синглтон. На какой компромис мы при этом пошли? Мы сделали нашу жизнь немного проще: теперь из любого места программы мы можем отправить в главное окно какие-то данные вроде финального кадра, или счетчика производительности или какую-нибудь иконку и оно там появится. Но, с другой стороны, мы сильно ограничили возможность расширения нашего приложения. Если через год мы вдруг захотим показывать два главных окна (для многопользовательской игры или с разными вариантами рендеринга), нам может понадобиться менять довольно много кода. И это не плохо, это просто та цена, которую мы заплатили за простоту использования синглтона в первый год разработки.

Причем такой пример можно привести и для практически любого другого метода, все эти методы позволяют находить какой-то компромис между какими-то свойствами. Если одно из этих свойств не важно для проекта, значит мы получили улучшение почти бесплатно, но надо понимать, что мы все-таки чем-то пожертвовали.
Отличный комментарий. Побольше бы таких)
Да, я вас понимаю. И я писал о том, что каждому шаблону свое место и время.
Но боюсь что некоторые понимают ваши слова иначе:
«Я не говорю, что шаблоны — это плохо, но надо всегда понимать, что мы получаем при их использовании и чем мы за это платим.»
== да ну их нафиг, эти шаблоны. Знаю я про синглтон и фабрику и с меня хватит. Жили без них и дальше проживём, «лишь, были б жёлуди, ведь я от них жирею...»

Одна из главных проблем нашего обучения — это то, что на практике нам разрешают использовать переменные с именем tmp,tmp1,tmp2 (это еще хорошо будет, что не только один tmp), а примеры нам показывают с именами методов Sum1, Sum2.
На уроках русского языка нам не разрешают писать слова с ошибками, когда мы учим правила пунктуации. А на уроках физики нам прививают использовать правильные имена переменных для формул.
Таки правда. А еще есть радостные переменные a, b,…
А потом пишутся длиннющие комментарии, чтобы пояснить, что же такое делается там, где достаточно было бы просто нормальные названия использовать…
Статья неплохая, но уж больно маленькая. Тут можно книгу написать, а не статью. Кстати, аббревиатуру я эту не знаю при том, что каждый в отдельности принцип мне известен и я лично их повторяю всем вновь прибывшим на мою работу. Они больше касаются дизайна с использовнием наследования, но можно привести и другие концепции проектирования ООП-программ, которые тоже следует учитывать. Вообще не хватает строгой теории, которая, например, описывает функциональное программирование.
Вот я тоже впервые услышал данную аббревиатуру недавно на собеседовании в одну компанию. Думал, что что-то упустил серьезное в обучении. Пришел домой — посмотрел и расстроился, что знаю каждый принцип, но просто не знал, что это так называется. Мне кажется, что не есть хорошо, когда на собеседовании спрашивают аббревиатуры — ведь в мире множество определений под одну и ту же структуру. Конечно, надо знать бОльшую часть, но ведь не это самое важное, как мне кажется
Да, особенно по телефону. Там часто сидят девочки с бумажкой вопрос-ответ.
Только мне кажется что принципы Single responsibility и Interface segregation — почти синонимы? Я пытаюсь представить себе ситуацию, когда все классы такие из себя модные «single responsible», но вот зато интерфейсы у них такие страшные — все в одном. У меня как-то плохо выходит…
Видно вставили чтоб аббревиатуру поправить, ато вначале у них выходило SOLD ;)
О нет! Ведь из этого бы следовало что уважаемый Robert C. Martin выбирал свои «five basic principles» исходя из красочности получаемой аббревиатуры, а вовсе не из «реальной базисности» этих принципов. Мы же не можем допустить такой кощунственной мысли, правда?
Немного разные вещи. ISP заключается в предоставлении конкретным клиентам минимального интерфейса. Т.е. предположим, что есть класс Rectangle, который умеет возвращать свой периметр и свою площадь. Пусть эти два метода находятся в интерфейсе IShape. Принцип SRP здесь соблюдается, так как зона ответственности этого класса — некие математические операции с данными этого класса. С точки зрения ISP, если у нас есть класс B, который выполняет операции с площадью некоторого объекта, при этом ему не требуется периметр этого объекта, то неплохо было бы разделить интерфейс IShape на какой-нибудь IShapePerimeterProvider и IShapeSquareProvider.

Пример, конечно, надуманный и названия интерфейсов не самые удачные) Суть в том, что SRP — это не одна операция на класс, это скорее операции одного контекста. Т.е. когда Rectangle не только умеет считать свою площадь, но и ещё умеет себя отрисовывать, и при этом используется в двух подсистемах — в одной только для получения площади, а в другой — для отрисовки — это уже нарушение SRP.
«Area», конечно, не «Square». Кстати, у самого Роберта Мартина в его «Design Principles and
Design Patterns» 2000 года не было SRP. С другой стороны, DIP он описывал ещё в 96ом)
Хм… на самом деле, после вашего примера, разница между ISP и SRP стала более различима — спасибо!
>Принцип подстановки Лисков вообщем говорит о том, что наследование правильно использовать…
Принцип подстановки Лисков говорит, что там, где используется наследуемый класс, может быть использован и наследник. И ничего не говорит о правильном/неправильном использованиие наследования.

Насчет DIP — это нужно использовать только там, где нужно. Тоесть там, где отсутствие инверсии не дает использовать компонент высшего уровня, так как он жестко завязан на компонент низшего уровня.
Новичок, прочитав статью, может подумать, что это нужно делать с каждым классом и будет плодить никому не нужные интерфейсы.
LSP, кстати в оригинале звучал как «functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it», а не про as-is, как это описано у автора поста.
в оригинале принцип звучал так: «What is wanted here is something like the following substitution property: If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T

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

кстати, принцип имеет импликативную форму (A => B), а это значит, что обратное утверждение в нём не сформулировано. следовательно, на основании данного принципа нельзя утверждать обратное: дескать, «правильное» наследование — когда S is a subtype of T — обеспечит неизменность поведения вашей программы при использовании объектов первого типа, вместо объектов второго типа. чем, кстати, в некоторых кейсах ООП активно пользуются — передавая программе разные объекты для того, чтобы изменить её поведение. образно говоря, хотя тип «колёса легкового автомобиля» являются конкретизацией типа «колёса», легковушка может вести себя на дороге по-разному, в зависимости от конкретной «резины».
Да, согласен, в качестве оригинала я имел в виду трактовку Роберта Мартина.
По смыслу своему, интерфейсы — это сущности, описывающие свойства чего-то. А классы — сущности, описывающие совокупности объектов — в частности, удовлетворяющих каким-то свойствам.

Поэтому вполне логично считать, что «родной» частью речи, которую надо использовать для именования классов, является существительное (Square, Circle, Human, Connection). А частью речи, которую надо использовать для именования интерфейсов, является причастие (Serializable, Sortable, Parseable), ну, или с натяжкой — прилагательное.

Я это к тому, что нарушение подобных «мнемоник», очень вероятно, может быть вызвано непониманием архитектуры или неправильным проектированием. Ну, например, создавать интерфейс ICanvas и класс CanvasImpl.
Собственно об этом и говорится в тексте по моей ссылке.
В идеале это конечно может и так, но как мне кажется, тут есть проблема. В дело вмешиваются DI и Mocks и оказывается, что без интерфейсов объектов именно в таком виде, как вы написали обойтись достаточно проблематично. Класс все-таки представляет больше, чем совокупность функциональности, которую можно представить такого рода интерфейсами-наречиями, там еще есть смысловая часть.
Вот это ядро тоже необходимо обернуть в интерфейс, для поддержки тестируемости, именно тогда и появляются такие интерфейсы типа ICanvas.
Вы совсем не поняли, о чем говорится в блогозаписи по ссылке. Речь идет о том, что если есть какой-то интерфейс, который содержит, например, методы по доставанию объекта по id и его реализации, одна из которых, например, ходит в базу, а вторая — получает доступ их через web-services, то нужно делать такую иерархию: ObjectLoadingService — интерфейс, JDBCObjectLoadingService — реализация которая ходит в базу, WebServicesObjectLoadingService — реализация через вебсервисы соответственно. А не IObjectLoadingService и ObjectLoadingServiceImpl и ObjectLoadingServiceWebImpl.

DI и моки тут вообще ни при чем.
Еще один copy-paste давно известных истин.
Плохо. Вы приводите примеры кода для того, чтобы показать разницу между классом и интерфейсом — для самой простой части. И не приводите примеры для куда более сложных вещей. Почему это я должен делать класс с единственной ответственностью? Приведите пример, покажите, как трудно поддерживать код, который нарушает этот принцип. Желательно сделать тоже самое и для остальных принципов. Гляньте в Code Complete Макконнелла. Этот человек ни единого правила без примера не оставляет, даже на предмет именованных констант.
В «чистом коде» SOLID рассматривается? Какой объем материала по сравнению с «быстрой разработкой»?
Стоит добавить, что в таком виде этот свод правил применим к статическим языкам. При программировании на том же JS принцип Open/Close часто нарушается ко всеобщей пользе — и на питоне, вроде, тоже…
Sign up to leave a comment.

Articles