Тверді принципи та структура коду


150

На недавньому співбесіді з роботою я не зміг відповісти на запитання про SOLID - окрім того, щоб надати основне значення різних принципів. Це мене справді клопоче. Я зробив копання за кілька днів і ще не придумав задовільного резюме.

Питання для інтерв'ю:

Якби ви подивилися на проект .Net, який я вам сказав, суворо дотримувався принципів SOLID, що б ви могли очікувати від проекту та структури коду?

Я трохи блукав, не відповідав на запитання, а потім вибухнув.

Як я міг краще вирішити це питання?


3
Мені було цікаво, що незрозуміло на сторінці вікі для SOLID
BЈович

Розширювані абстрактні будівельні блоки.
rwong

Дотримуючись принципів SOLID Принципи об'єктно-орієнтованого дизайну, ваші класи, звичайно, мають тенденцію бути невеликими, добре обгрунтованими та легко перевіреними. Джерело: docs.asp.net/uk/latest/fundamentals/…
whileTrueSleep

Відповіді:


188

S = єдиний принцип відповідальності

Тож я очікую побачити добре організовану структуру папок / файлів та ієрархію об’єктів. Кожен клас / фрагмент функціональності повинен бути названий таким, що його функціональність дуже очевидна, і він повинен містити лише логіку для виконання цього завдання.

Якщо ви побачили величезні класи менеджерів із тисячею рядків коду, це було б ознакою того, що єдиної відповідальності не дотримувалися.

O = відкритий / закритий принцип

В основному це ідея про те, що нові функції повинні бути додані через нові класи, які мають мінімальний вплив на / потребують модифікації існуючої функціональності.

Я б очікував, що ви побачите багато використання спадкового об'єкта, підтипу, інтерфейсів та абстрактних класів, щоб відокремити дизайн певного функціоналу від фактичного втілення, що дозволить іншим придумати та впровадити інші версії поряд, не впливаючи на це. оригінальний.

L = принцип заміщення Ліскова

Це пов'язано з можливістю трактувати підтипи як їх батьківський тип. Це виходить із поля в C #, якщо ви реалізуєте належну ієрархію об'єктів.

Я очікував би побачити код, що розглядає загальні об'єкти як їх базовий тип та методи виклику на базових / абстрактних класах, а не інстанціювати та працювати над самими підтипами.

I = Принцип поділу інтерфейсу

Це схоже на SRP. В основному, ви визначаєте менші підмножини функціональності як інтерфейси і працюєте з тими, щоб система не була розв'язана (наприклад, FileManagerможе мати єдину відповідальність за роботу з File I / O, але це могло б реалізувати a IFileReaderі IFileWriterмістило конкретні визначення методу для читання і написання файлів).

D = Принцип інверсії залежності.

Знову ж це стосується збереження системи, яка не є роз'єднаною. Можливо, ви будете шукати для використання бібліотеки .NET Dependency Injection, яка використовується в такому рішенні, як система Unityабо NinjectServiceLocator, наприклад AutoFacServiceLocator.


36
Я бачив безліч порушень LSP в C #, кожен раз, коли хтось вирішить, що їх конкретний підтип є спеціалізованим, тому не потрібно реалізовувати фрагмент інтерфейсу, а просто накидає виняток на цю частину замість цього ... Це звичайний підхід для молодших для вирішення проблеми нерозуміння реалізації та дизайну інтерфейсу
Джиммі Хоффа

2
@JimmyHoffa Це одна з головних причин, які я наполягаю на використанні кодових контрактів; проходження продуманого процесу розробки договорів дуже допомагає виводити людей із цієї шкідливої ​​звички.
Енді

12
Мені не подобається, що "LSP виходить з коробки в C #" і прирівнюють DIP до практики введення залежностей.
Ейфорія

3
+1, але інверсія залежності -> введення залежності. Вони добре грають разом, але інверсія залежності набагато більше, ніж просто введення залежності. Довідково: DIP в дикій природі
Мар'ян Венема

3
@Andy: що також допомагає - тестові одиниці, визначені на інтерфейсах, на яких тестуються всі реалізатори (будь-який клас, який може / інстанціюється).
Мар'ян Венема

17

Дуже багато невеликих класів та інтерфейсів з ін'єкцією залежності. Ймовірно, у великому проекті ви також використовуєте рамку IoC, щоб допомогти вам сконструювати та керувати життями всіх цих малих об'єктів. Дивіться https://stackoverflow.com/questions/21288/which-net-dependency-injection-frameworks-are-worth-looking-into

Зауважте, що великий .NET проект, який суворо дотримується принципів SOLID, не обов'язково означає хорошу кодову базу для роботи з усіма. Залежно від того, ким був інтерв'юер, він / вона, можливо, хотів, щоб ви показали, що ви розумієте, що означає SOLID, та / або перевірили, наскільки догматично ви дотримуєтесь принципів дизайну.

Розумієте, щоб бути ТОЛИМИ, вам потрібно слідувати:

S Ingle принцип відповідальності, так що ви будете мати багато невеликих класів кожен з них робить одне тільки

O принцип закритої ручки, який у .NET зазвичай реалізується з ін'єкцією залежності, що також вимагає I та D нижче ...

Принцип заміщення L- skov, ймовірно, неможливо пояснити в c # за допомогою одного вкладиша. На щастя, є й інші питання, що стосуються цього, наприклад, https://stackoverflow.com/questions/4428725/can-you-explain-liskov-substitution-principle-with-a-good-c-sharp-example

Я nterface Сегрегація Принцип роботи в тандемі з відкритим закритим принципом. Якщо дотримуватися буквально, це означатиме перевагу великої кількості дуже малих інтерфейсів, а не декількох «великих» інтерфейсів

D ependency принцип інверсії класи високого рівня не повинні залежати від класів низького рівня, і повинен залежати від абстракцій.


SRP не означає "робити тільки одне".
Роберт Харві

13

Деякі основні речі, які я б очікував побачити в кодовій базі магазину, який підтримував SOLID у своїй щоденній роботі:

  • У багатьох файлах з невеликим кодом - один клас на файл, як найкраща практика в .NET, та Принцип єдиної відповідальності, що заохочує невеликі модульні структури класів, я очікую побачити багато файлів, що містять один невеликий, зосереджений клас.
  • Багато моделей адаптерів та складових - я б очікував використання багатьох моделей адаптерів (клас, що реалізує один інтерфейс, "переходячи" до функціональності іншого інтерфейсу), щоб упорядкувати підключення залежності, розробленої для однієї мети, трохи різні місця, де також потрібна її функціональність. Оновлення настільки ж просто, як заміна консольного реєстратора файловим реєстратором порушить LSP / ISP / DIP, якщо інтерфейс буде оновлено, відкривши засіб для визначення імені файлу, який слід використовувати; натомість клас реєстратора файлів викриє додаткових членів, і тоді адаптер зробить реєстратор файлів схожим на консольний реєстратор, приховавши новий матеріал, тому лише об'єкт, що зафіксував усе це разом, повинен знати різницю.

    Аналогічно, коли класу потрібно додати залежність аналогічного інтерфейсу як існуючого, щоб уникнути зміни об'єкта (OCP), звичайною відповіддю є реалізація структури Composite / Strategy (клас, що реалізує інтерфейс залежності та споживає кілька інших реалізацій цього інтерфейсу, з різною кількістю логіки, що дозволяє класу передавати виклик одному, деяким або всім реалізаціям).

  • Багато інтерфейсів і ABC - DIP обов'язково вимагає, щоб існували абстракції, і ISP заохочує їх до вузького діапазону. Отже, інтерфейси та абстрактні базові класи - це правило, і вам знадобиться багато їх для покриття функціональності спільної залежності вашої бази коду. Хоча строгий SOLID потребує введення всього , очевидно, що ви повинні десь створити, і тому, якщо форма GUI коли-небудь створюється як дитина однієї з батьківських форм, виконуючи якусь дію над вказаним батьком, я не маю жодних труднощів, доповнюючи дочірню форму з коду безпосередньо всередині батьків. Я просто роблю цей код своїм методом, тому якщо дві дії однієї форми коли-небудь відкривали вікно, я просто викликаю метод.
  • Багато проектів - Сенс усього цього полягає в обмеженні обсягу змін. Зміни включають потребу у перекомпіляції (порівняно тривіальна вправа вже є, але все ще важлива у багатьох критичних процесорах та пропускній здатності операцій, як-от розгортання оновлень у мобільному середовищі). Якщо один файл у проекті має бути відновлений, всі файли роблять. Це означає, що якщо ви розміщуєте інтерфейси в тих же бібліотеках, що і їх реалізація, вам не вистачає точки; вам доведеться перекомпілювати всі звичаї, якщо ви зміните реалізацію інтерфейсу, тому що ви також будете перекомпілювати саме визначення інтерфейсу, вимагаючи від звичаїв вказати на нове місце в отриманому бінарному файлі. Тому зберігаючи інтерфейси окремо від звичок та реалізація, додатково розділяючи їх за загальною сферою використання, є типовою найкращою практикою.
  • Багато уваги приділяється термінології "Банда з чотирьох" - Шаблони дизайну, визначені в дизайні "Шаблони дизайну" 1994 року, підкреслюють модульний дизайн коду, який SOLID прагне створити. Принцип інверсії залежності та принцип відкритого / закритого типу, наприклад, лежать в основі більшості виявлених моделей цієї книги. Як такий, я очікував, що магазин, який сильно дотримується принципів SOLID, також охоплюватиме термінологію в книзі «Банда четвірок» та класифікує імена відповідно до їх функції в таких напрямках, як «AbcFactory», «XyzRepository», «DefToXyzAdapter "," A1Command "тощо.
  • Загальний репозиторій - Відповідно до ISP, DIP та SRP, як прийнято розуміти, сховище є майже всюдисущим у дизайні SOLID, оскільки дозволяє використовувати код для запиту класів даних в абстрагованому вигляді, не потребуючи конкретних знань механізму пошуку / збереження, та він ставить код, який робить це в одному місці, на відміну від шаблону DAO (в якому, якби, наприклад, був клас даних рахунків-фактур, ви також мали б InvoiceDAO, який виробляв би гідратовані об'єкти цього типу тощо) для всі об'єкти / таблиці даних у кодовій базі / схемі).
  • Контейнер IoC - я вагаюся, щоб додати цей, оскільки я фактично не використовую рамки IoC, щоб зробити більшість моїх ін'єкцій залежностей. Це швидко стає анти-схемою об'єкта Бога, щоб кидати все в контейнер, струшувати його та виливати необхідну вам вертикально-гідратовану залежність за допомогою введеного фабричного методу. Звучить чудово, поки ви не зрозумієте, що структура стає досить монолітною, і проект з інформацією про реєстрацію, якщо "вільно володіє", тепер повинен знати все про ваше рішення. Це багато причин для зміни. Якщо вона не є вільною (запізнілі реєстрації з використанням конфігураційних файлів), то ключовий фрагмент вашої програми покладається на "магічні рядки", зовсім інші "черви".

1
чому низовини?
KeithS

Я думаю, що це хороша відповідь. Замість того, щоб бути схожим на багато публікацій у блогах щодо того, що це за умови, ви перерахували приклади та пояснення, які показують їх використання та значення
Crowie

10

Відволічіть їх на дискусію Джона Скіта про те, як "O" у SOLID є "недоброзичливою і погано зрозумілою", і змусьте їх говорити про "захищену варіацію" Алістера Кокберна та "дизайн Джоша Блоха на спадщину, або забороняйте це".

Короткий підсумок статті Скіта (хоча я б не рекомендував скидати його ім’я, не читаючи оригінальну публікацію в блозі!):

  • Більшість людей не знають, що означають "відкриті" та "закриті" за принципом "відкрито-закрито", навіть якщо вони думають, що так роблять.
  • Поширені тлумачення включають:
    • що модулі завжди слід розширювати шляхом успадкування реалізації, або
    • що вихідний код вихідного модуля ніколи не можна змінити.
  • Основний намір OCP та оригінальна формулювання Бертран Мейєра, це добре:
    • що модулі повинні мати чітко визначені інтерфейси (не обов'язково в технічному розумінні "інтерфейс"), від яких можуть залежати їх клієнти, але
    • повинно бути можливість розширити те, що вони можуть зробити, не порушуючи цих інтерфейсів.
  • Але слова "відкритий" і "закритий" просто плутають питання, навіть якщо вони справді викликають приємну вимову.

ОП запитала: "Як я міг краще вирішити це питання?" Як старший інженер, який проводить співбесіду, я був би незмірно більш зацікавлений у кандидата, який може розумно поговорити про плюси і мінуси різних стилів кодового дизайну, ніж у того, хто може розігрувати список балів.

Ще однією хорошою відповіддю буде: "Ну, це залежить від того, наскільки добре вони це зрозуміли. Якщо все, що вони знають, це мовленнєві слова SOLID, я очікував би зловживання спадщиною, надмірного використання систем введення залежності, мільйон малих інтерфейсів, жоден з яких" відображати словник домену, який використовується для спілкування з управлінням продуктами .... "


6

Напевно, існує ряд способів, на які можна відповісти з різною кількістю часу. Однак я вважаю, що це більше узгоджується з "Чи знаєте ви, що означає SOLID?" Тож відповідь на це питання, ймовірно, просто зводиться до потрапляння балів і пояснення його з точки зору проекту.

Отже, ви очікуєте побачити наступне:

  • Класи мають єдину відповідальність (наприклад, клас доступу до даних для клієнтів отримуватиме лише дані клієнтів із бази даних клієнтів).
  • Заняття легко розширюються, не впливаючи на існуючу поведінку. Мені не потрібно змінювати властивості чи інші методи, щоб додати додаткову функціональність.
  • Отримані класи можуть бути замінені базовими класами, а функції, які використовують ці базові класи, не повинні розгортати базовий клас на більш конкретний тип, щоб обробити їх.
  • Інтерфейси невеликі та їх легко зрозуміти. Якщо клас використовує інтерфейс, він не повинен залежати від кількох методів для виконання завдання.
  • Код достатньо абстрагується таким чином, що реалізація на високому рівні не конкретно залежить від конкретної низькорівневої реалізації. Я повинен мати можливість перемикати низькорівневу реалізацію, не впливаючи на код високого рівня. Наприклад, я можу переключити свій рівень доступу до SQL для веб-сервісу, не впливаючи на решту моєї програми.

4

Це відмінне запитання, хоча я думаю, що це складне питання інтерв'ю.

Принципи SOLID дійсно регулюють класи та інтерфейси та те, як вони пов’язані між собою.

Це питання справді пов'язане з файлами, а не обов'язково класами.

Коротке зауваження або відповідь, яку я б дав, полягає в тому, що зазвичай ви побачите файли, що містять лише інтерфейс, і часто умовою є те, що вони починаються з великої я. Крім того, я хотів би зазначити, що у файлах не було б повторного коду (особливо в модулі, додатку чи бібліотеці), і цей код був би ретельно розподілений через певні межі між модулями, програмами або бібліотеками.

Роберт Мартін обговорює цю тему в царині C ++ при розробці об'єктно-орієнтованих додатків C ++ за допомогою методу Booch (див. Розділи "Згуртованість, закриття та повторне використання") та в чистому коді .


.NET кодери IME зазвичай дотримуються правила "1 клас на файл", а також дзеркальні структури папок / простору імен; Visual Studio IDE заохочує обидві практики, і різні плагіни, такі як ReSharper, можуть їх застосовувати. Отже, я б очікував побачити структуру проекту / файлу, що відображає структуру класу / інтерфейсу.
KeithS
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.