Связывание со строками «более слабое», чем с методами класса?

18

Я начинаю проект школьной группы на Java, используя Swing. Это простой графический интерфейс для настольного приложения базы данных.

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

Я надеюсь найти причины, почему его система хороша или плоха. (Я спросил профессора, и он сказал, что я увижу позже, почему так лучше, что меня не устраивает.)

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

Когда событие инициируется, представление или модель отправляет строку всем своим подписчикам, которые решают, что делать для этого события. Например, в одном из методов слушателя действия views (я полагаю, это просто устанавливает bikeMakeField на постоянный объект):

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

Этот вызов в конечном итоге попадает в этот метод в модели Vehicle:

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 года, когда
наступит
3
Пожалуйста, держите нас в курсе, когда получите ответ.
JeffO
4
Независимо от качества кода или мудрости техники, вам следует исключить слово «магия» из описания строк, используемых в качестве идентификаторов. «Волшебные струны» подразумевают, что система не логична, и она будет окрашивать ваш взгляд и отравлять любые ваши обсуждения с вашим инструктором. Такие слова, как «ключи», «имена» или «идентификаторы», являются более нейтральными, возможно, более описательными и с большей вероятностью приведут к полезному разговору.
Калеб
1
Правда. Я не называл их волшебными струнами во время разговора с ним, но вы правы, не нужно, чтобы это звучало хуже, чем есть.
Филипп
2
Подход, который описывает ваш профессор (оповещение слушателей с помощью событий, названных в виде строк), может быть хорошим. На самом деле перечисления могут иметь больше смысла, но это не самая большая проблема. Реальная проблема, которую я вижу здесь, состоит в том, что объект, получающий уведомление, является самой моделью, и тогда вам нужно вручную отправлять в зависимости от типа события. На языке, где функции первого класса, вы регистрируете функцию , а не объект для события. В Java, я думаю, нужно использовать какой-то объект, обертывающий одну функцию
Andrea

Ответы:

32

Подход, предложенный вашим профессором, лучше всего описать как строго типизированный и неправильный почти на каждом уровне.

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

back2dos
источник
3
Вы пишете «почти на каждом уровне», но вы не написали, почему это (на ваш взгляд) неподходящий случай в этом примере. Было бы интересно узнать.
hakre
1
@hakre: Подходящих случаев нет, по крайней мере, на Java. Я сказал, что «это неправильно почти на каждом уровне», потому что это лучше, чем вообще не использовать косвенное обращение и просто бросать весь код в одном месте. Однако использование (глобальных) магических строковых констант в качестве основы для ручной отправки является просто сложным способом использования глобальных объектов в конце.
back2dos
Итак, ваш аргумент таков: подходящих случаев не бывает, так что этот тоже не один? Это вряд ли аргумент и на самом деле не очень полезно.
hakre
1
@hakre: Мой аргумент состоит в том, что этот подход плох по ряду причин (параграф 1 - достаточно причин, чтобы избежать этого, приведены в объяснении строкового типа) и не должен использоваться, потому что есть лучший (параграф 2 ).
back2dos
3
@hakre Я могу представить себе ситуацию, которая оправдывала бы такой подход, если бы серийный убийца приклеил вас к вашему стулу и приклеил к вашей голове бензопилу, маниакально рассмеявшись и приказав вам везде использовать строковые литералы для логики рассылки. Кроме того, полагаться на языковые возможности для такого рода вещей предпочтительнее. На самом деле, я могу вспомнить еще один случай, связанный с крокодилами и ниндзя, но это немного сложнее.
Роб
19

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

Чтобы быть несколько честным с профессором, он пытается создать в Java то, что выглядит очень похоже на широко распространенный интерфейс .NET INotifyPropertyChanged, который уведомляет подписчиков о событиях изменения посредством передачи строк, которые указывают, какое свойство изменилось. По крайней мере, в .NET этот интерфейс в основном используется для связи из модели данных для просмотра объектов и элементов графического интерфейса (привязка).

Если все сделано правильно , такой подход может иметь некоторые преимущества¹:

  1. Вид зависит от модели, но модель не обязательно должна зависеть от вида. Это соответствующая инверсия контроля .
  2. У вас есть единственное место в вашем коде View, которое управляет реагированием на уведомления об изменениях из модели, и только одно место для подписки / отмены подписки, что снижает вероятность утечки эталонной памяти.
  3. Когда вы хотите добавить или удалить событие из издателя событий, это значительно снижает накладные расходы. В .NET существует встроенный механизм публикации / подписки, eventsно в Java вам придется создавать классы или объекты и писать код подписки / отписки для каждого события.
  4. Самым большим недостатком этого подхода является то, что код подписки может не синхронизироваться с кодом публикации. Тем не менее, это также является преимуществом в некоторых средах разработки. Если в модели разработано новое событие, а представление еще не обновлено, чтобы подписаться на него, то оно, скорее всего, будет работать. Аналогично, если событие удалено, у вас есть мертвый код, но он все еще может скомпилироваться и запустить.
  5. По крайней мере, в .NET Reflection очень эффективно используется для автоматического вызова методов получения и установки в зависимости от строки, которая была передана подписчику.

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


¹ Поиск в Google, INotifyPropertyChangedчтобы найти миллион способов, которыми люди пытаются избежать использования магической строки при ее реализации.

Кевин Маккормик
источник
Похоже, вы описали SOAP здесь. : D… (но да, я понял вашу точку зрения, и вы правы)
Конрад Рудольф
11

enums (или public static final Strings) следует использовать вместо волшебных строк.

Ваш профессор не понимает ОО . Например, имея конкретный класс транспортных средств?
И чтобы этот класс понял марку велосипеда?

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

Farmor
источник
3
Если цель этого упражнения не состоит в том, чтобы улучшить код, используя лучшие принципы ОО. В этом случае рефакторинг отсутствует.
joshin4colours
2
@ joshin4colours Если бы StackExchange оценивал его, вы были бы правы; но так как грейдер - тот же человек, который выдвинул идею, он поставит всем нам плохие оценки, потому что он знает, что его реализация была лучше.
Дэн Нили
7

Я думаю, у вас есть хорошая точка зрения, магические струны плохие. Кто-то из моей команды должен был отладить проблему, вызванную магическими строками пару недель назад (но они написали собственный код, поэтому я держал рот на замке). И это случилось ... в промышленности !

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

В этом контексте выглядит, что Enum может быть лучше, чем магические строки или даже глобальные строковые константы. Кроме того, интегрированные среды разработки часто предоставляют некоторую помощь при кодировании (автозаполнение, рефакторинг, поиск всех ссылок и т. Д.) При использовании константных строк или перечислений. IDE не окажет особой помощи, если вы используете волшебные строки.

FrustratedWithFormsDesigner
источник
Я согласен, что перечисления лучше строковых литералов. Но даже тогда, действительно ли лучше вызывать «stateChangeRequest» с ключом enum или просто вызывать метод в модели напрямую? В любом случае, модель и вид связаны в одном месте.
Филипп
@Philip: Ну, тогда, я думаю, чтобы отделить модель и представление в этот момент, вам может понадобиться некоторый класс контроллера для этого ...
FrustratedWithFormsDesigner
@FrustratedWithFormsDesigner - Да. Архитектура MVC является наиболее
изолированной
Правильно, если другой слой поможет отделить их, я бы не стал спорить с этим. Но этот слой все еще может быть статически типизирован и использовать реальные методы, а не строковые или перечисляющие сообщения для их соединения.
Филипп
6

Использование «отраслевого опыта» - плохой аргумент. Ваш профессор должен придумать лучший аргумент, чем этот :-).

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

Я вижу, что @backtodos покрывал это, пока я отвечал на это, поэтому я просто добавлю свое мнение о том, что с помощью Inversion of Control (формой которого является Dependency Injection), вы столкнетесь в среде Spring в отрасли очень долго. ..) считается более современным способом борьбы с этим типом развязки.

Мартейн Вербург
источник
2
Полностью согласен, что научные круги должны предъявлять гораздо более высокие требования, чем отрасль. Академия == лучший теоретический путь; Промышленность == лучший практический путь
Farmor
3
К сожалению, некоторые из тех, кто преподает этот материал в научных кругах, делают это, потому что они не могут сократить его в промышленности. С положительной стороны, некоторые из тех, кто в академических кругах, великолепны, и их не следует прятать в корпоративном офисе.
FrustratedWithFormsDesigner
4

Давайте будем совершенно ясны; там неизбежно какая-то связь. Тот факт, что существует такая подписываемая тема, является точкой связи, как и характер остальной части сообщения (например, «единственная строка»). Учитывая это, тогда возникает вопрос, является ли связь большей, когда это делается через строки или типы. Ответ на этот вопрос зависит от того, обеспокоены ли вы связью, которая распределена по времени или пространству: будут ли сообщения сохраняться в файле перед последующим чтением или будут отправлены другому процессу (особенно если это не написано на одном языке), использование строк может сделать вещи намного проще. Недостатком является то, что нет проверки типов; грубые ошибки не будут обнаружены до времени выполнения (а иногда даже тогда).

Стратегия смягчения / компрометации для Java может заключаться в использовании типизированного интерфейса, но там, где этот типизированный интерфейс находится в отдельном пакете. Он должен состоять только из Java interfaceи базовых типов значений (например, перечисления, исключения). В этом пакете не должно быть реализаций интерфейсов. Тогда все реализуют это, и, если необходима сложная передача сообщений, конкретная реализация делегата Java может использовать магические строки по мере необходимости. Это также значительно облегчает миграцию кода в более профессиональную среду, такую ​​как JEE или OSGi, и значительно облегчает такие мелочи, как тестирование…

Donal Fellows
источник
4

Ваш профессор предлагает использовать «магические струны». Хотя он «слабо связывает» классы друг с другом, позволяя вносить более простые изменения, он является анти-паттерном; строки должны очень, ОЧЕНЬ редко использоваться, чтобы содержать или контролировать команды кода, и когда это абсолютно необходимо, значение строк, которые вы используете для управления логикой, должно быть централизованно и постоянно определено, так что есть ОДИН авторитет в отношении ожидаемого значения любая конкретная строка.

Причина очень проста; Строки не проверяются компилятором на синтаксис или согласованность с другими значениями (в лучшем случае они проверяются на правильную форму относительно escape-символов и другого языкового форматирования в строке). Вы можете поместить все, что вы хотите в строку. Таким образом, вы можете получить безнадежно испорченный алгоритм / программу для компиляции, и только когда она запускается, возникает ошибка. Рассмотрим следующий код (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% покрытии кода в модульных тестах с адекватным упражнением кода, что обычно встречается только в наиболее абсолютной отказоустойчивости. , системы реального времени.

Keiths
источник
2

На самом деле, эти классы все еще тесно связаны. За исключением того, что теперь они тесно связаны таким образом, что компилятор не может сказать вам, когда что-то сломалось!

Если кто-то изменит «BicycleMake» на «BicyclesMake», то никто не узнает, что все сломалось до времени выполнения.

Ошибки во время выполнения обходятся гораздо дороже, чем ошибки во время компиляции - это просто зло.

17 из 26
источник