January
2006
Анти-паттерн проектирования (Java): интерфейс для констант
Posted in: Java technologies, J2SE, Паттерны проектирования |
Ошибка, которая будет описана в данной статье, иногда привлекает Java-программистов своей многообещающей простой (я знаю, о чем говорю, так как сам ее допускал).
При программировании очень часто приходится использовать константы. Константа, описанная в некотором классе, иногда может понадобится и в другом классе.
Если описать константу в интерфейсе, назовем его символично Unacceptable, то затем можно декларировать любой класс как реализующий (implements) этот интерфейс (interface) и использовать нашу полезную константу:
public interface Unacceptable {
public static final int USEFUL_CONSTANT = 0;
...
}
public class First implements Unacceptable {
public void firstMethod() {
int key = USEFUL_CONSTANT;
...
}
...
}
public class Second implements Unacceptable {
public void secondMethod() {
int value = USEFUL_CONSTANT;
...
}
...
}
Не нужно портить зрение: в этом коде все синтаксически правильно. Но вот идеологически…
Конечно, идея создать огромную кучу констант в одном интерфейсе с гибкой и прозрачной системой префиксования подкупает своей новизной. Но это ни что иное как антипаттерн проектирования. Ведь в объектно-ориентированном программировании на Java интерфейсам была уготована более важная роль, чем просто хранить константы.
Updated (27.02.07):
Поэтому, используйте следующий код:
public final class First {
public static final int USEFUL_CONSTANT = 0;
...
}
public class Second {
public void usefulMethod() {
int value = First.USEFUL_CONSTANT;
...
}
...
}
Используйте даже тогда, когда кроме констант там больше ничего не будет. Главное, чтобы имя класса четко давало понять, что за смысловую нагрузку он несет (так что можете смело переименовать класс First из второго примера в Unacceptable).
Всем удачи.
23 Comments »
RSS feed for comments on this post. TrackBack URI
А можно просто обьявить константы в одном файле, а потом его инклудить?
Я просто незнаю как в java (да и ещё забыл, что программирование бывает отличное от web
), но в php не нужно делать определение константы в классе. Прото define и всё, хватит. Насколько я помню, в С тоже аналогично…
А я вообще, даже когда читал по джава, не могу понять методологию интерфейсов. Может я далёк? или тупой? ![]()
(сажусь за книшки)
Так а почему собственно так не надо делать? Кроме того что это не соответствует роли интерфейсов в яве, здесь есть еще какието недостатки?
Ну на сколько я понимаю хранить константы в интерфейсах удобно с той точки зрения, что не стоит писать public static final, оно там подразумевается по умолчанию. Экономия, стало быть.
Но если уж писать константы в классах, то стоит упомянуть про соответствующий паттерн для безопасной работы с ними. Или использовать Enumeration.
имхо, все эти паттерны - это что-то из области мракобесия: опишут несколько очевидных вещей и назовут это словом “паттерн”, типа, во как круто!
любой здравомыслящий (человек?) программер понимает, что интерфейс служит для описания средств, с помощью которых можно взаимодействовать с объектом, его реализующим. И константы, ну ни каким боком не могут быть повязаны с интерфейсом. Разве это не очевидно?
Да ну гоните ![]()
Смысл статьи не в том.
Тут рассказывают о том, что если вдруг вам надо одна константа во всех классах, то вы, скорее всего, перенесёте её в интерфейс какой-нить и везде этот интерфейс заимплементируете.
Чтобы не писать в каждом классе
public static final int USEFUL_CONSTANT = 0;
а написать это в том самом единственном интерфейсе и, если что, там её и менять.
Так вот тут говорят, что этого делать нельзя. Что это АНТИПАТТЕРН.
Епта. А не проще забубенить класс типа:
public class UsefulConstants{
public static final int USEFUL_CONSTANT_0 = 0;
public static final int USEFUL_CONSTANT_1 = 1;
}
а потом обращаться к ним вот так:
int useful_variable = UsefulConstants.USEFUL_CONSTANT1;
предварительно добавив класс в import?
В конце концов существуют RAD GUI, которые имеют смартендинг и динамически выводят варианты написания выражений, если лень такие конструкции вручную писать. ))
Обсуждая паттерны или анти-паттерны проектирования надо иметь ввиду что мы говорим об объектно-ориентированном проектировании. А у ООП есть свои принципы,которым рекомендуется следовать, иначе вы не почувствуете эффекта от его использования. Итак, по порядку:
1.Анти-паттерн IMHO это решение типовой задачи, которое действительно решает эту задачу (на самом деле только на первый взгляд), но при этом не соответствует принципам ООП.
2.Сравнивать java с такими языками программирования как PHP или С довольно странно (PHP использует оба подхода к программирования, а С вообще не поддерживает ОО-подход).
3.Дело не в ролях тех или иных элементов языка, а в нарушении принципов ООП.
Вот пример:
public class UsefulConstants{
public static final int USEFUL_CONSTANT_0 = 0;
public static final int USEFUL_CONSTANT_1 = 1;
}
Почему так делать нельзя? Потому что этот подход нарушает принцип инкапсуляции. Следуя логике такого подхода необходимо выделить класс(ы) переменных и класс(ы) методов. Т.е. в данном случае константы является сущностью смоделированной посредством класса. А теперь надо задуматься: а есть ли такие предметные области в которых можно было бы выделить сущность “константы”?
Использование анти-паттернов или говоря проще “неверных с точки зрения ООП решений” приводит к запутыванию и необоснованному усложнению кода.
Что касается использования констант в интерфейсах то (опять же IMHO) это вообще говоря лишнее (а может и вредное) действие. Здесь есть проблемы как принципиального, так и чисто практического характера.
А, черт побери! Я неправильно выразил мысль… Естественно, эти константы входят в класс, который имеет кроме констант еще методы и переменные поля.
Специально заглянул в исходный код интерфейса java.sql.ResultSet (просто часто его использую) и увидел следующее:
public interface ResultSet extends Wrapper {
...
/**
* The constant indicating the type for a ResultSet object
* whose cursor may move only forward.
* @since 1.2
*/
int TYPE_FORWARD_ONLY = 1003;
/**
* The constant indicating the type for a ResultSet object
* that is scrollable but generally not sensitive to changes to the data
* that underlies the ResultSet.
* @since 1.2
*/
int TYPE_SCROLL_INSENSITIVE = 1004;
/**
* The constant indicating the type for a ResultSet object
* that is scrollable and generally sensitive to changes to the data
* that underlies the ResultSet.
* @since 1.2
*/
int TYPE_SCROLL_SENSITIVE = 1005;
...
}
В интерфейсе определены константы (говоря терминами из исходников), которые используются, например, при создании объекта класса java.sql.Statement. Они, правда, определены без модификаторов static final. Фактически это не константы.
Это антипаттерн? В исходных кодах java? Это бред.
Может дело именно в модификаторах, а не в самом принципе?
з.ы. вечер… может я что-то глючу..?
Написано ведь — антипаттерн “идиологический”…
public interface MagicInterface {
public static final int MAGIC_VALUE = 777;
}
public class Dick implements MagicInterface {
// …
}
public class Finger implements MagicInterface {
// …
}
А теперь сравним х@# c пальцем:
…
void doSomethingWith(MagicInterface impl) {
// делаем что-нибудь с impl …
}
…
doSomethingWith(new Dick());
doSomethingWith(new Finger());
Понятно?
Все. Я понял. (Тормоз я). Имеется в виду интерфейс не объявляющий “собственно интерфейс”, а содержащий только константы и ничего более.
/me бьется головой в стену
Если у ж пользоваться именно таким паттерном для объявления констант, но неплохо было б еще и объявить private конструктор.
А если не реализовывать интерфейс, а обращаться к его полям через имя интерфейса? Это тоже анти-паттерн?
А как вы относитесь к:
import static myInterface;
Реализовывать интерфейс не надо, обращаемся сразу к полю.
наследование констант в теории ООП не запрещено
см. например Бертран Мейер “Object-Oriented Software Construction”, тут перевод
http://www.intuit.ru/department/se/ooad/6/ooad_6.html, “льготное наследование”
если расмматривать интерфейсы в Джаве как ограниченный случай множественного наследования - наследование констант допустимо. Не обязательно упихивать все константы в один интерфейс - можно создать несколько и включать в заголовок класса только те списки констант, которые ему требуются.
Спасибо, Человечищи! Из Ваших разговоров узнал много нового! Ещё раз спасибо.
2Leledinn:
На счет ResultSet. Интерфейс не может содержать полей-членов. Поэтому нет смысла писать public static final. Кстати, модификатор public перед именем метода тоже писать не нужно.
2AlexeyEgorov:
Интерфейс - это контракт, соглашение между разработчиком класса и пользователем класса. Реализуя интерфейс, класс становится частью API, которое доступно пользователю. В итоге получается, что разные константы, несущие порой абсолютно разную смысловую нагрузку и часто являющиеся деталями реализации, становятся частью API. Непорядок…
2stan: Спасибо большое за отличный комментарий.
2Leledinn
нельзя считать библиотеку классов java чем-то сверъестественным и на 100% правилным. несмотря на то, что над ней уже много лет работают, в ней есть оплошности, неадекватные решения и т.п. Эккель в своей “Философии Java” приводит с пяток примеров того, как не надо делать, на основе кода библиотек классов java 1.1-1.3 например.
Прочитав, хочется лишь поддержать вовку (vovka)..
Ибо говорящий не знает, а знающий не говорит :).
ООП и паттерны будоражат наши умы как красная тряпка возбуждает быка, или модный яркий магазин влечет блондинку. Как… эээ… ну вобщем понятно я думаю.
И что самое интересное, мы теперь начинаем рассуждать: “Это противоречит правилам ООП”. Здрасте приехали… Боже мой! Если всю жизнь следовать правилам - можно никогда ничему не научиться. Пишите как вам взбредет в голову и со временем все встанет на свои места. Ошибки и эти все “антипатерны” проявятся сами собой. А может быть вы выработаете свой, совершенно уникальный стиль, и откроете новые “паттерны”. А заранее вы все равно соломку не подстелите в нужном месте. А все эти “паттерны” - ПРОСТО ЖИЗНЕННЫЕ ВЕЩИ, которые новичкам кажутся чем-то магическим, что (каким-то волшебным образом) они спасут их в будущем, избавят от страданий и разочарований, лишней работы и т.п.
А вот еще..
Ключевой момент - страх написать что-то что создат проблемы в будущем. Конечно не нужно их создавать, никто не спорит, но когда человек действует руководствуясь страхом - он пишет как раз не лучший код, тем самым как раз и создавая эти проблемы. И вот его опасения оправдываются - он начинает еще сильней боятся… опять действует из страха. Вечный кайф получается ![]()
А если уж выносить константы в интерфейс, то лучше выносить их в отдельный класс с приватным конструктом.
По поводу ResultSet. Эти константы как раз являются косвенно частью контракта класса, реализующего РизалтСет. Метод getFetchDirection, допустим, возвращает одну из этих констант. Правда, такие вещи лучше делать Перечислениями (enum), но в версии 1.2 их еще не было).
Соглашусь с Oleg’ом. Видно сразу, человек заканчивал программирование с филосовским уклоном :).
Ко всему им сказанному, хотел бы добавить. Причина многих затруднений кроется в наскакивании на идеи, до которых люди еще не доросли. Поясню. Человек только начинает учить программирование и на него сразу сваливается ООП, которое не родилось сразу из ничего, а люди пришли к нему в процессе взросления своих идей.
Я считаю, что начинающего нужно знакомить с простым линейным программированием, дать ему саму упереться в стенку и задать вопрос: “А нет ли способа реализовать задачу вывода всех чисел от 0 до 100 проще, чем писать:
Writeln(”0″);
Writeln(”1″);
…
Writeln(”99″);
?”, и тогда скормить ему циклы, затем функции. Так же и классы нужны тогда, когда человек уже созрел для них. Так же и интерфейсы, и все остальные концепции. Я, конечно, пояснил сильно утрированно, но, надеюсь, донес свою мысль.
Если во время спора о необходимости использования того или иного подхода (паттерна) на вопрос какие преимущества в нем заключаются, оппонент дает заученную формулировку. А после вопроса, как он к этому пришел, говорит, одну из следующих фраз:
1. Мне лид сказал так делать.
2. Мы так всегда дели.
3. Так в книжке “Н” написано.
Сразу становится понятно, насколько человек понимает то, о чем говорит. Понятно, что он следует “своему” любимому паттерну, даже если тот совершенно не подходит для его языка или противоречит структуре приложения. И он сделает все, чтобы соблюсти чистоту этого паттерна, даже если для этого приходится идти на неоправданные жертвы.
Кто-то разумный сказал примерно следующее: паттерн существует ради облегчения реализации некоторой идеи, а не ради умещения идеи в рамки паттерна, но об этом часто забывают и реализовывают сам паттерн.
Блин, даже мысли никогда не возникало засунуть в интерфейс константу.