Об'єктно-орієнтований дизайн класу


12

Мені було цікаво хороший об’єктно-орієнтований дизайн класу. Зокрема, мені важко вирішувати такі варіанти:

  1. метод статичного проти екземпляра
  2. метод без параметрів або поверненого значення проти методу з параметрами і значення повернення
  3. перекриття та окрема функціональність методу
  4. приватний проти публічний метод

Приклад 1:

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

XmlReader reader = new XmlReader(url);
reader.openUrl();
reader.readXml();
Document result = reader.getDocument();

Приклад 2:

Ця реалізація використовує статичні методи, із поверненими значеннями та параметрами, з функцією перекриття та приватними методами

Document result = XmlReader.readXml(url); 

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

У другому прикладі лише один метод є загальнодоступним, решта - приватними статичними, що робить їх важким для одиничного тестування. Методи перекриваються в тому, що readXml () викликає openUrl (). Немає полів, всі дані передаються як параметри методів, і результат повертається негайно.

Яких принципів я повинен дотримуватися, щоб виконати правильне об'єктно-орієнтоване програмування?


3
Статичні речі погані, коли ви робите багаторізкові нитки. Днями у мене з’явився статичний XMLWriter, як XMLWriter.write (дані, fileurl). Однак, оскільки у нього був приватний статичний FileStream, використання цього класу з декількох потоків одночасно спричинило перезапис другого потоку першими потоками FileStream, викликаючи помилку, яку було б дуже важко знайти. Статичні заняття зі статичними членами + багатопоточна передача - це рецепт катастрофи.
Per Alexandersson

1
@Paxinum. Описана вами проблема - це проблема держави, а не "статична" проблема. Якщо ви використовували синглтон з нестатичними членами, у вас виникли б ті ж проблеми з багатопотоковою ниткою.
mike30

2
@Per Alexandersson Статичні методи непогані щодо одночасності. Статичний стан поганий. Ось чому функціональне програмування, в якому всі методи є статичними, дуже добре працює в паралельних ситуаціях.
Юлі Боннер

Відповіді:


11

Приклад 2 дуже поганий для тестування ... і я не маю на увазі, що ви не можете перевірити внутрішні. Ви також не можете замінити XmlReaderоб'єкт макетом, оскільки у вас його немає.

Приклад 1 зайво важкий у використанні. Як щодо

XmlReader reader = new XmlReader(url);
Document result = reader.getDocument();

який не є складніше використовувати, ніж ваш статичний метод.

Такі речі, як відкриття URL-адреси, читання XML, перетворення байтів у рядки, розбір, закриття сокетів і все інше, нецікаві. Створення об’єкта та його використання є важливим.

Тож IMHO належним дизайном OO є оприлюднення лише двох речей (якщо тільки вам чомусь не потрібні проміжні кроки з якоїсь причини). Статичне - це зло.


-1. Ви насправді МОЖЕТЕ замінити XmlReader макетом. Не з мозковим процесом з відкритим вихідним кодом moick, але з хорошим промисловим рівнем ви можете;) Коштує пару сотень доларів США на розробника, але творить чудеса для перевірки функціональних можливостей, що закупорюються, в API, який ви публікуєте.
TomTom

2
+1 за те, що не змащується на рівні продажу TomTom. Коли я хочу один лайнер, я краще напишу щось на кшталт Document result = new XmlReader(url).getDocument();Чому? Щоб я міг її модернізувати Document result = whateverReader.getDocument();і whateverReaderпередав мені щось інше.
candied_orange

6

Ось у чому річ - немає правильної відповіді, і немає абсолютного визначення "належного об'єктно-орієнтованого дизайну" (деякі люди запропонують вам один, але вони наївні ... дайте їм час).

Все зводиться до ваших цілей.

Ви художник, а папір порожній. Можна намалювати ніжний тонко олівцем чорно-білий боковий портрет або абстрактну картину з величезними газами змішаних неонів. Або щось середнє.

Отже, що ВИПУСКУЄ ПРАВО для проблеми, яку ви вирішите? Які скарги людей, яким потрібно використовувати ваші заняття для роботи з xml? Що важко в їх роботі? Який код вони намагаються записати, що оточує дзвінки до вашої бібліотеки, і як ви можете допомогти цьому протікати краще для них?

Чи хотіли б вони більшої лаконічності? Чи хотіли б вони, щоб це було дуже розумно при з'ясуванні параметрів за замовчуванням для параметрів, тому їм не потрібно вказувати багато (або що-небудь), і це правильно здогадується? Чи можете ви автоматизувати завдання налаштування та очищення, необхідні вашій бібліотеці, щоб неможливо забути ці кроки? Що ще можна зробити для них?

Пекло, що вам, мабуть, потрібно зробити, - це кодувати це 4 або 5 різних способів, потім надіньте шапку споживача і напишіть код, який використовує всі 5, і подивіться, що почуває себе краще. Якщо ви не можете зробити це для всієї своєї бібліотеки, тоді це зробити для підмножини. І вам також потрібно додати кілька додаткових альтернатив до свого списку - як щодо вільного інтерфейсу, або більш функціонального підходу, або названих параметрів, або чогось на основі DynamicObject, щоб ви могли скласти значущі "псевдометоди", які їм допомагають з?

Чому саме jQuery король саме зараз? Оскільки Резіг і команда дотримувались цього процесу, поки вони не натрапили на синтаксичний принцип, який неймовірно зменшив кількість коду JS, необхідного для роботи з домом та подіями. Цей синтаксичний принцип не був зрозумілий ні їм, ні комусь іншому, коли вони починали. Вони її знайшли.

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


1
Це означає, що між найкращою та іншою практикою немає відмінностей, крім того, як це "відчуває". Ось як ви обмотаєте незбагненними кулями грязі - адже багато розробників, простягаючись через межі класу тощо, "відчуває себе" просто чудово.
Емі Бланкенсіп

@Amy Blankenship, я б однозначно сказав, що немає жодного "найкращого способу" зробити вибір, про який задається ОП. Це залежить від мільйона речей, і є мільйон градусів свободи. Однак я думаю, що є місце для «Кращих практик», і це в командному середовищі, де певні рішення вже зроблені, і нам потрібна решта команди, щоб дотримуватися цих попередніх виборів. Іншими словами, в конкретному контексті певні речі можуть бути маркованими як "найкращі практики". Але ОП не дало жодного контексту. Він щось будує і ...
Charlie Flowers

... він стикається з усіма цими можливими варіантами. На цей вибір немає жодної "правильної відповіді". Це рухається цілями та больовими точками системи. Я гарантую вам, що програмісти Haskell не вважають, що всі методи повинні бути методами екземплярів. А програмісти ядра Linux не вважають, що зробити доступними речі для TDD взагалі дуже важливо. І ігрові програмісти на C ++ часто радше зв'язують свої дані в щільну структуру даних в пам'яті, ніж інкапсулюють все в об'єкти. Кожна «Найкраща практика» є лише «найкращою практикою» в даному контексті і є антидіаграмою в іншому контексті.
Charlie Flowers

@AmyBlankenship Ще одне: я не погоджуюсь з тим, що простягатися через межі класу "почувається просто чудово". Це призводить до незбагненних кульок грязі, які відчувають жахливість . Я думаю, ви намагаєтеся вирішити проблему, що деякі працівники неохайні / немотивовані / дуже недосвідчені. У такому випадку той, хто є обережним, вмотивованим та досвідченим, повинен зробити ключовий вибір і назвати їх "найкращими практиками". Але людина, що вибирає ці "Кращі практики", все ж робить вибір, виходячи з того, що "вважає правильно", і немає правильних відповідей. Ви просто контролюєте, хто робить вибір.
Charlie Flowers

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

3

Другий варіант кращий, оскільки людям простіше користуватися (навіть якщо це тільки ви), набагато простіше.

Для тестування одиниць я б просто перевірив інтерфейс, а не внутрішній, якщо ви дійсно хочете перенести внутрішні дані з приватних на захищені.


2
Ви також можете зробити ваш метод методів приватним (за замовчуванням), якщо ваші тестові випадки в одному пакеті для цілей одиничного тестування.

Хороший момент - це краще.

3

1. Статична проти екземпляра

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

І найгірша практика, яку я можу придумати, - це Глобальний штат, в тому числі статики, про які ви згадали, і всіма улюблений синглтон. Деякі уривки з класичної статті Місько Гевери на цю тему .

Щоб реально зрозуміти залежності, розробники повинні прочитати кожен рядок коду. Це спричиняє Spooky Дію на відстані: під час запуску тестових наборів, глобальний стан, мутований в одному тесті, може призвести до несподіваного подальшого або паралельного тестування. Розбийте статичну залежність, використовуючи ручне введення або залежність від Гейса.

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

Ви, можливо, раніше не думали про це так, але щоразу, коли ви використовуєте статичний стан, ви створюєте таємні канали зв’язку і не чітко пояснюєте їх в API. Spooky Action at a Distance змушує розробників читати кожен рядок коду, щоб зрозуміти потенційну взаємодію, знижує продуктивність розробника та плутає нових членів команди.

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

2. Методи із вхідними параметрами та поверненими значеннями порівняно з методами без жодних

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

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

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

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

3. Перекриття проти чіткості

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

4. Приватний проти громадського

Не піддавайте нічого, що вам не потрібно виставляти. Однак і я не є великим прихильником приватного. Я не розробник C #, а розробник ActionScript. Я провів багато часу в коді Adobe Flex Framework, який був написаний приблизно в 2007 році. І вони зробили кілька по-справжньому поганих варіантів того, що робити приватним, що робить його таким, як кошмар, намагаючись розширити свої класи.

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


У ваших прикладах коду є деякі проблеми, які означають, що вони не добре сконструйовані, тому вибрати A або B. неможливо.

З одного боку, ви, ймовірно, повинні відокремити створення об'єкта від його використання . Таким чином, ви зазвичай не мали б свого new XMLReader()права поруч із тим, де воно використовується.

Крім того, як говорить @djna, ви повинні інкапсулювати методи, які використовуються у вашому XML Reader, щоб ваш API (приклад прикладу) міг бути спрощений до:

_document Document = reader.read(info);

Я не знаю, як працює C #, але, оскільки я працював з низкою веб-технологій, я б підозрював, що ви не завжди зможете повернути XML-документ негайно (за винятком, можливо, як обіцянки чи майбутнього типу об'єкта), але я не можу дати вам поради щодо обробки асинхронного навантаження в C #.

Зауважте, що при такому підході ви можете створити кілька реалізацій, які можуть приймати параметр, який вказує їм, де / що слід читати та повертати XML-об’єкт, і поміняти їх на основі потреб вашого проекту. Наприклад, ви можете читати безпосередньо з бази даних, з локального магазину або, як у вашому оригінальному прикладі, з URL-адреси. Ви не можете цього зробити, якщо використовуєте статичний метод.


2

Зосередьтеся на перспективі клієнта.

IReader reader = new XmlReader.readXml(url);  // or injection, or factory or ...
Document document = reader.read();

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

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

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


Чи видно методи з видимістю за замовчуванням, якщо тестовий набір є в іншому проекті?
siamii

Java не знає про проекти. Проекти - це конструкція IDE. Компілятор і JVM дивляться на пакунки, в яких перебувають тестовані та тестер-класи - той же пакет, видимість за замовчуванням дозволена. У Eclipse я використовую єдиний проект з двома різними джерелами. Я щойно спробував з двома проектами, це працює.

2

метод статичного проти екземпляра

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

метод без параметрів або поверненого значення проти методу з параметрами і значення повернення

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

перекриття та окрема функціональність методу

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

приватний проти публічний метод

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


1

Я не відповім на ваше запитання, але я думаю, що питання створюється за допомогою використаних вами термінів. Напр

XmlReader.read => twice "read"

Я думаю, що вам потрібен XML, тому я буду створювати XML-об'єкт, який можна створити з текстового типу (я не знаю C # ... в Java це називається String). Напр

class XML {
    XML(String text) { [...] }
}

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

class XML {
    XML(String text) { [...] }

    static XML fromUrl(url) { [...] }

}

0

Ви можете дотримуватися деяких простих правил. Це допомагає, якщо ви розумієте причини правил.

метод статичного проти екземпляра

Для методів не потрібно приймати це рішення свідомо. Якщо виявляється, що ваш метод не використовує членів поля (ваш улюблений аналізатор повинен вам це сказати), слід додати статичне ключове слово.

метод без параметрів або поверненого значення проти методу з параметрами і значення повернення

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

перекриття та окрема функціональність методу

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

приватний проти публічний метод

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.