Чи поєднання з рядками "втрачає", ніж із класовими методами?


18

Я починаю проект шкільної групи на Java, використовуючи Swing. Це просто графічний графічний інтерфейс на настільному додатку Database.

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

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

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

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

    else if(evt.getSource() == bicycleMakeField)
    {
        myRegistry.updateSubscribers("BicycleMake", bicycleMakeField.getText());
    }

Цей виклик врешті-решт потрапляє до цього методу в моделі транспортного засобу:

public void stateChangeRequest(String key, Object value) {

            ... bunch of else ifs ...

    else
    if (key.equals("BicycleMake") == true)
    {
                ... do stuff ...

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

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

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


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

vehicle.make = bicycleMakeField.getText();

Це зменшило б 15 рядків коду, які в даний час використовуються ТОЛЬКО для встановлення марки транспортного засобу в одному місці, до одного читаного рядка коду. (Оскільки ця операція робиться сотні разів у всьому додатку, я думаю, це було б великим виграшем для читабельності, а також безпеки.)


Оновлення

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


12
OT. Я думаю, що ваш професор повинен оновлювати себе, а не використовувати старий код. В академії немає причин використовувати версію JAVA з 2006 року, коли це 2012 рік.
Farmor

3
Будь ласка, повідомте нас, коли ви нарешті отримаєте його відповідь.
JeffO

4
Незалежно від якості коду чи розумності методики, ви повинні залишити слово "магія" з вашого опису рядків, що використовуються як ідентифікатори. "Чарівні струни" означають, що система не логічна, і вона забарвить ваш погляд і отруїть будь-яку дискусію, яку ви маєте зі своїм інструктором. Такі слова, як "клавіші", "імена" або "ідентифікатори", є більш нейтральними, можливо більш описовими та швидше спричинили корисну розмову.
Калеб

1
Правда. Я не називав їх чарівними струнами під час розмови з ним, але ти маєш рацію, не потрібно робити це гірше, ніж це.
Філіп

2
Підхід, який описує ваш професор (сповіщення слухачів через події, названі як рядки), може бути хорошим. Насправді перерахунки можуть мати більше сенсу, але це не найбільша проблема. Справжня проблема, яку я бачу тут, полягає в тому, що об’єктом, який отримує сповіщення, є сама модель, і тоді вам доведеться вручну відправляти на основі виду події. Мовою, де функції є першим класом, ви реєструєте функцію , а не об’єкт події. У Яві, мабуть, слід використовувати якийсь об’єкт, що охоплює одну функцію
Андреа

Відповіді:


32

Підхід, який пропонує ваш професор, найкраще описується як строго набраний і є неправильним майже на кожному рівні.

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


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

1
@hakre: Немає відповідних випадків, принаймні, не на Java. Я сказав, що це "неправильно майже на кожному рівні", тому що це краще, ніж взагалі не використовувати опосередкованість, а просто кидати весь код в одне місце. Однак використання (глобальних) магічних струнних констант в якості основи для ручного відправлення - це лише звичний спосіб використання глобальних об'єктів врешті-решт.
back2dos

Тож ваш аргумент такий: Ніколи не буває придатних випадків, тож цей теж не один? Це навряд чи аргумент і насправді не дуже корисний.
хакре

1
@hakre: Мій аргумент полягає в тому, що такий підхід з ряду причин поганий (абзац 1 - достатньо причин, щоб уникнути цього, наведено в поясненні строго набраних) і його не слід використовувати, тому що є кращий (параграф 2 ).
back2dos

3
@hakre Я можу собі уявити ситуацію, яка б вимагала такого підходу, якби серійний вбивця привів вас до свого стільця і ​​тримав бензопилу над головою, маніпулюючи сміхом, і наказав вам використовувати всюди рядкові букви для логіки відправки. Окрім цього, покладатися на мовні особливості краще, ніж бажано. Насправді я можу придумати ще один випадок, пов’язаний із крокодилами та ніндзями, але це дещо більше.
Роб

19

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

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

Якщо зробити все правильно , то цей підхід може мати певні переваги¹:

  1. Вид залежить від моделі, але модель не повинна залежати від виду. Це доречно Інверсія управління .
  2. У вашому коді перегляду є одне місце, яке керує реагуванням на зміни сповіщень від моделі, і лише одне місце для підписки / відписки, що знижує ймовірність вивішування опорної пам'яті.
  3. Існує спосіб менше кодування накладних витрат, коли ви хочете додати або видалити подію з видавця події. У .NET є вбудований механізм публікації / підписки, який називається, eventsале в Java вам доведеться створювати класи або об'єкти і писати код підписки / скасування підписки для кожної події.
  4. Найбільший недолік такого підходу полягає в тому, що код підписки може не синхронізуватися з кодом публікації. Однак це також є перевагою в деяких середовищах розвитку. Якщо в моделі розроблено нову подію, а представлення ще не оновлено, щоб підписатись на неї, то перегляд все одно може працювати. Так само якщо подія буде видалена, у вас є якийсь мертвий код, але він все одно може компілюватись і запускатися.
  5. Принаймні, у .NET, Reflection використовується тут дуже ефективно для автоматичного виклику одержувачів та сетерів залежно від рядка, який був переданий абоненту.

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


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


Це здається, що ви описали SOAP тут. : D… (але так, я розумію, і ти маєш рацію)
Конрад Рудольф

11

enums (або public static final Strings) слід використовувати замість магічних струн.

Ваш професор не розуміє ОО . Наприклад, маючи конкретний клас транспортного засобу?
І щоб цей клас зрозумів марку велосипеда?

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


3
Якщо не суть цієї вправи - вдосконалити код, використовуючи кращі принципи ОО. У такому випадку рефактор подалі.
joshin4colours

2
@ joshin4colours Якщо б StackExchange оцінив це, ви б мали рацію; але оскільки грейдер - це та сама людина, яка придумала думку, він би дав усім нам погані оцінки, бо знає, що його реалізація була кращою.
Ден Нілі

7

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

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

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


Я погоджуюся, що перерахунки кращі за рядкові букви. Але навіть тоді, чи справді краще викликати "stateChangeRequest" ключем enum або просто викликати метод на моделі безпосередньо? У будь-якому випадку модель і вигляд поєднуються в одному місці.
Філіп

@Philip: Ну тоді я думаю, щоб роз'єднати модель і переглянути в цей момент, вам може знадобитися якийсь клас контролера, щоб це зробити ...
FrustratedWithFormsDesigner

@FrustratedWithFormsDesigner - Так. Архітектура MVC є найбільш відокремленою
cdeszaq

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

6

Використання "галузевого досвіду" є поганим аргументом. Ваш професор повинен придумати кращий аргумент, ніж це :-).

Як зазначають інші, використання магічних струн, безумовно, нахмуриться, використання переписок вважається набагато безпечнішим. Енум має значення та обсяг , магічні струни - ні. Крім цього, спосіб, яким ваш професор розв'язує питання, вважається старішою і крихкою технікою, ніж сьогодні.

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


2
Повністю згоден, академії повинні мати набагато вищі вимоги, ніж галузеві. Академія == найкращий теоретичний шлях; Промисловість == найкращий практичний спосіб
Farmor

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

4

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

Стратегія пом'якшення / компромісу для Java може використовувати типізований інтерфейс, але там, де цей введений інтерфейс знаходиться в окремому пакеті. Він повинен складатися лише interfaceз типів Java та основних значень (наприклад, перерахування, винятки). У цьому пакеті не повинно бути реалізацій інтерфейсів. Тоді всі реалізують проти цього, і якщо необхідне передати повідомлення складно, певна реалізація делегата Java може використовувати магічні рядки за необхідності. Це також значно полегшує переміщення коду до більш професійного середовища, такого як JEE або OSGi, і значно допомагає таким дрібницям, як тестування ...


4

Те, що пропонує ваш професор, - це використання "магічних струн". У той час як він "вільно поєднує" класи між собою, що дозволяє простіше змінювати, це анти-шаблон; рядки повинні дуже, ДУЖЕ рідко використовуються для того, щоб містити або керувати кодовими інструкціями, і коли це абсолютно має відбуватися, значення рядків, які ви використовуєте для керування логікою, має бути централізовано та постійно визначатись, щоб ОДНО було повноваження на очікуване значення будь-який конкретний рядок.

Причина, чому це дуже просто; рядки не перевіряються компілятором на наявність синтаксису чи узгодженості з іншими значеннями (в кращому випадку вони перевіряються на належну форму щодо символів втечі та іншого форматування, характерного для мови в рядку). Ви можете помістити все, що завгодно, в рядок. Таким чином, ви можете отримати безнадійно зламаний алгоритм / програму для компіляції, і лише до запуску не виникає помилка. Розглянемо наступний код (C #):

private Dictionary<string, Func<string>> methods;

private void InitDictionary() //called elsewhere
{
   methods = new Dictionary<string, Func<string>> 
      {{"Method1", ()=>doSomething()},
       {"MEthod2", ()=>doSomethingElse()}} //oops, fat-fingered it
}

public string RunMethod(string methodName)
{
   //very naive, but even a check for the key would generate a runtime error, 
   //not a compile-time one.
   return methods[methodName](); 
}

...

//this is what we thought we entered back in InitDictionary for this method...
var result = RunMethod("Method2"); //error; no such key

... весь цей код складається, спочатку спробуйте, але помилка очевидна з другого погляду. Існує чимало прикладів подібного роду програмувань, і всі вони піддаються подібній помилці, НАКІЛЬКО, якщо ви визначаєте рядки як константи (адже константи в .NET записуються в маніфест кожної бібліотеки, в якій вони використовуються, яка повинна то всі будуть перекомпільовані, коли визначене значення буде змінено для того, щоб значення змінилося "глобально"). Оскільки помилка не виявляється під час компіляції, вона повинна бути виявлена ​​під час тестування часу виконання, і це гарантується лише при 100% покритті коду в одиничних тестах з адекватним кодовим вправою, яке, як правило, зустрічається лише в найбільш абсолютній відмовостійці системи в режимі реального часу.


2

Насправді ці заняття ще щільно з'єднані. За винятком того, що вони щільно з'єднані таким чином, що компілятор не може сказати вам, коли все порушено!

Якщо хтось змінить "BicycleMake" на "BicyclesMake", тоді ніхто не збирається з'ясувати, що все порушено до моменту виконання.

Помилки під час виконання - це значно дорожче виправити, ніж помилки компіляції часу - це просто всі види зла.

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