|
Java форум JavaTalks форум программистов
|
|
|
|
| Предыдущая тема :: Следующая тема |
| Автор |
Сообщение |
Taky_ : 491 Бывалый
|
Авг 13, 2011 15:43 |
|
|
mpoSimba, я вас не совсем понял.
Когда я пишу:
| Код: |
| List<SomeClass> list = new ArrayList<SomeClass>(); |
я имею возможность изменить реализацию на связанный список и поменять производительность программы например.
Принцип подстановки Лисков(в честь Барбары Лисков, которая в 70 еще язык CLU создала) гласит: Пусть q(x) является свойством, верным относительно объектов x некоторого типа T. Тогда q(y) также должно быть верным для объектов y типа S, где S является подтипом типа T. см. http://ru.wikipedia.org/wiki/Принцип_подстановки_Лисков
Пример неверного использования: если в метод который использует public E set(int index, E element) через List передать List, который вернет функция: Array.asList(...) или Collections.nCopies(int n, T o). Т.к. будет вызвана реализация из AbstractList и кинется исключение: UnsupportedOperationException.
Соответсвенно передавая разные реализации List в такой метод, мы будем получать разное поведение.
Кажется так. |
|
|
|
 |
Tricks`Ter : 224 Новичок
|
Авг 13, 2011 15:49 |
|
|
| mpoSimba писал(а): |
...
Верный контракт aList не утерян. |
А можно чуть подробнее? Не понял, почему при первом варианте контракт теряется. Мы же потеряли только объявленный, а не фактический тип. |
|
|
|
 |
mpoSimba : 124 Новичок
|
Авг 13, 2011 16:37 |
|
|
Taky_
Если вы НЕ ЗАВИСИТЕ от непосредственной реализации, то СЛЕДУЕТ инвертировать управление и передать создание самой реализации кому-либо ещё, а в конструкторе декоратора компонента требовать контракт List.
| Код: |
public SomeClass<T> {
public SomeClass(List<T> list_);
}
//...
ArrayList<AnotherClass> aList = new ArrayList<AnotherClass>();
//do something with aList as the AraayList entity
SomeClass<AnotherClass> SomeClassObj = new SomeClass<AnotherClass>(aList);
//do something as the List entity
aList = null; //return to the Force
|
Смотрите. Читать правильно ООП код следует так:
SomeClass(List<T> list_);
Я, SomeClass, торжественно клянусь, что буду клиентом list_ и обязуюсь использовать только методы List. Я доверяю интерфейсу List. При этом поставщик list_ может держать ещё тысячу контрактов с другими клиентами. Если SomeClass попытается использовать метод не по контракту с List, компилятор привлечёт его к суду.
Принцип подстановки гласит, что никто не может "врать". Если я утверждаю, что я "подтип", то я автоматически "принимаю" контракт того, кому наследую, и обязуюсь его чтить и соблюдать.
UnsupportedOperationException -- это нарушение контракта. Вероятно, это Java Fail, связанный с отсутствием контракта типа C++ const.
Если бы был контракт, какой-нибудь final List, который не содержал бы операции set, то уже на этапе компиляции всё разрешилось бы, исключение не потребовалось бы. А в каком-нибудь языке без строгой типизации операция, вероятнее всего, просто тихо не сработала бы. Контракт автоматически изменён.
Tricks`Ter
Зачем нужна статическая типизация?
Ответив на этот вопрос, вы поймёте, что именно потеряли. |
|
|
|
 |
mpoSimba : 124 Новичок
|
Авг 13, 2011 17:05 |
|
|
Я согласен, что то, о чём веду речь -- сильно теоретизированно. Университетского типа вопрос, который к практике имеет мало отношения.
Если работать в рамках концепции, поддерживаемых ООП, то проектирование затянется на тысячи человеко*часов. Для практики ужасная роскошь.
Но... Не следует некоторые быстрые практические обходы выдавать за "правила программирования".
В том роде, что: всегда пишите интерфейс List, а инстанцируйте чем хотите.
----
Если ваша программа инстанцирует ArrayList, значит она хочет произвольного доступа. Зачем кастовать в List? Это только лишает возможности работы с ArrayList.
Если ваша программа инстанцирует LinkedList, значит достаточно связанного списка. Зачем сжимать его до List?
Если вам действительно нужен только List, заверните ваш класс в контейнер, который даст вам реализацию и будет её менять в зависимости от нужд других кусков программы, которые могут захотеть LinkedList или ArrayList. |
|
|
|
 |
Dagdamor : 406 Бывалый
|
Авг 14, 2011 13:17 |
|
|
| Цитата: |
Зачем кастовать в List? Это только лишает возможности работы с ArrayList.
Если ваша программа инстанцирует LinkedList, значит достаточно связанного списка. Зачем сжимать его до List? |
У типа List все же есть оно преимущество - он пишется короче
Хотя Джава - увы, не тот язык, в котором можно избежать бессмысленной писанины... как же мне в нем после PHP не хватает родной поддержки ассоциативных массивов (они же хеши)... $item["field"] куда красивее и быстрее, чем item.getField() или item.get("field"). После PHP и его легкости, все эти неизбежные интерфейсы, классы и дженерики - и все это только чтобы хранить десяток строк - выглядит как... не скажу что  _________________ Java и трассировка лучей |
|
|
|
 |
mpoSimba : 124 Новичок
|
Авг 14, 2011 14:59 |
|
|
Не помню, отмечал ли я это в теме PHP vs. Java, но сравнение Java и PHP по скорости разработки -- бессмысленно. Потому что из проекта перейти в конструирование в PHP практически всегда можно без "швов". В Java -- нет.
Нестрогая типизация, как показала практика, действительно ускоряет разработку. В то же время, в современном PHP есть возможность определять и требовать пользовательские контракты.
Если подойти к PHP с умом, то можно писать коды, которые легко переиспользовать, на порядок легче, чем C++ коды, легко модифицировать даже если внутренние контракты повреждаются, гораздо легче писать самоизменяющиеся коды (например самовосстанавливающиеся или самообновляемые) и т.д.
Только, глядя на тенденции в PHP, складывается впечатление, что он неизбежно скатится в "настоящий ООП-язык со строгой типизацией", потому что "средний менеджер проектов" полагает, что это "классно!". |
|
|
|
 |
Dagdamor : 406 Бывалый
|
Авг 14, 2011 16:56 |
|
|
Строгая типизация - это полбеды, в конце концов, все новшества, те же типы в аргументах функции - всегда вещи опциональные, кто-то пишет по-новому, кто-то пишет legacy.
Но у Джавы еще столько подводных камней... например, я до сих пор не понимаю, почему в ней 5/2=2. Мне страшно подумать, сколько людей за все эти годы лишились работы из-за этой подлянки.
Или крайне странные приоритеты операций... например, побитовые операции с числами имеют низший приоритет, чем арифметические. Хотя во всех языках, что я знаю, все наоборот.
Вчера изучал компараторы - меня совершенно убил абзац про запрет на использование унарного минуса в компараторах... причина: -MININT = +MININT. Хоть смейся, хоть плачь... _________________ Java и трассировка лучей |
|
|
|
 |
Taky_ : 491 Бывалый
|
Авг 15, 2011 11:58 |
|
|
mpoSimba, спасибо за интересный ответ.
С LSV вроде бы разобрались. По поводу "никто не должен врать" очень доступно
| Цитата: |
| Если вы НЕ ЗАВИСИТЕ от непосредственной реализации, то СЛЕДУЕТ инвертировать управление и передать создание самой реализации кому-либо ещё, а в конструкторе декоратора компонента требовать контракт List. |
Ну да, вы правы, в теории есть несколько средств реализации IOC. Тем не менее на практике это делать дорого. Иногда мне не надо даже фабрику плодить, но очень часто достаточно простого контракта List(а если уже о практике, то можно понянуть Map).
По поводу советую почитать http://java.sun.com/docs/books/effective/toc.html Refer to objects by their interfaces. Когда я пишу:
| Код: |
| List<Some> l = new ArrayList<Some>(); |
я не хочу делать попусту фабрику, но я оставляю себе возможность сменить реализацию в одном месте.
| Цитата: |
Если ваша программа инстанцирует ArrayList, значит она хочет произвольного доступа. Зачем кастовать в List? Это только лишает возможности работы с ArrayList.
Если ваша программа инстанцирует LinkedList, значит достаточно связанного списка. Зачем сжимать его до List?
Если вам действительно нужен только List, заверните ваш класс в контейнер, который даст вам реализацию и будет её менять в зависимости от нужд других кусков программы, которые могут захотеть LinkedList или ArrayList. |
Делать контейнер - дорого. Если логики у сущности не достаточно чтобы она была сущностью? Не надо делать ее тогда. Если программа использует List, значит ей достаточно List. Если она использует LinkedList, значит ей не достаточно List, ей надо что-то попикантнее. Если мы используем Vector, значит нам нужна линейная память с произвольным доступом и реботой в многопточной среде. А потом бах, рефакторинг и у каждого треда свой список, тогда можно поднять perfomance и заюзать ArrayList. |
|
|
|
 |
mpoSimba : 124 Новичок
|
Авг 15, 2011 17:13 |
|
|
Taky_
| Цитата: |
| По поводу "никто не должен врать" очень доступно |
Это попытка выразить мысли "клиента" внутри статического контракта человеческим языком.
Очень помогает, когда мыслишь, как программа, только люди не понимают, приходится "переводить".
| Цитата: |
| Тем не менее на практике это делать дорого. |
Возможно. Хотя казалось бы, что поставщик будет сравнительно редко выполнять свою работу. Потому не должен влиять на производительность. Я не претендую на "инженерную эффективность".
| Цитата: |
| я оставляю себе возможность |
себе -- это ключевое слово.
При использовании невозможно сменить реализацию. То есть, вы подразумеваете, что это не будет нужно. Вы спроектировали компонент, но оставили себе возможность изменить что-то. Зачем? Если компонент уже спроектирован и выпущен?
Как-то концы с концами не сходятся. |
|
|
|
 |
Taky_ : 491 Бывалый
|
Авг 16, 2011 9:31 |
|
|
| Цитата: |
себе -- это ключевое слово.
При использовании невозможно сменить реализацию. То есть, вы подразумеваете, что это не будет нужно. Вы спроектировали компонент, но оставили себе возможность изменить что-то. Зачем? Если компонент уже спроектирован и выпущен?
Как-то концы с концами не сходятся. |
Это где-то в недрах моего компонента. В версии 2.0 я беру и меняю логику... в остальном коде я опираюсь на абстракции.
Когда же я пишу как вы рекомендуете:
| Код: |
| ArrayList arrayList = new ArrayList(); |
я привязываюсь к реализации в коде. Когда я ссылаюсь как на интерфейс, то я оставляю возможность легко сменить реализацию в одном месте. А не в двух как минимум) Я заставляю человека, который будет работать с этим кодом, понимать, что я привязываюсь только к линейной структуре данных.
| Код: |
| List arrayList = new ArrayList(); |
В идеале можно бы вынести это в IOC контейнер, или как минимум сделать фабричный метод:
| Код: |
List arrayList = createList();
private List createList(){ return new ArrayList(); } |
Далее компонент с методом createList() выделяем в класс и поднимаем уровень абсракциидо класса. Класс делаем как плагин. Получаем еще более мощный дизайн.
Но не всегда такую гибкость иметь целесообразно. На каждый чих создавать по классу или как минимум по методу - не правильно.
Поэтому ссылка на интерфейс, а не на класс в момент создания мне кажется компромисом:
| Код: |
| List arrayList = new ArrayList(); |
P.S. Где вы набрались идей по поводу потеряного контракта? Можно и мне почитать? Или сами дошли? |
|
|
|
 |
mpoSimba : 124 Новичок
|
Авг 16, 2011 11:39 |
|
|
Относительно версий.
Если вы закрыли компонент и выпустили как готовый, то версия 2.0 -- это логическое противоречие тому, что компонент выпущен.
Если вы знали, что компонент будет по различным причинам изменяться, тогда
| Цитата: |
| поднимаем уровень абсракциидо класса. Класс делаем как плагин. |
Проектное решение отражает возможность изменения внутреннего "неготового" компонента, отделяя его от клиента слоем.
| Цитата: |
| На каждый чих создавать по классу или как минимум по методу - не правильно. |
Для каждого случая этот вопрос нужно решать отдельно. Если вы создаёте интерфейс "чихать", только потому, что половина ваших компонентов реализует метод "чихать", но больше никаких посылок нет -- это не есть хорошо, это будет code flood.
Если вы выделяете интерфейс "чихать", потому что предвидите, что вашим клиентам понадобиться неизвестная на данный момент реализация метода в будущем, то это без сомненья верный ход.
| Цитата: |
| Поэтому ссылка на интерфейс, а не на класс в момент создания мне кажется компромисом: |
Возможно, это действительно удачный обход логики построения решения. Вы опускаете логически ясный вам проектный ход и реализуете его "иначе".
Приходите вы в магазин и спрашиваете: "Хлеб свежий? -- Только что привезли", -- отвечает продавщица.
Вы не получили ответа на свой вопрос! Будьте внимательны. Хлеб могли привезти и чёрствым. Но... Любой "вменяемый" человек понимает, что хлеб свежий, делая логический доверительный скачок, что хлеб всегда привозят свежим. Но это не укладывается в рамки "формальной логики", а для человека нормально.
| Код: |
| List<SomeClass> list = new ArrayList<SomeClass>(); |
Не является логичным, но принимается, с оговоркой на логический скачок, что никогда не будет использован контракт ArrayList, который будет утерян.
Полезно думать самому, вам тоже советую.
http://download.oracle.com/javase/6/docs/api/java/util/ArrayList.html
http://download.oracle.com/javase/6/docs/api/java/util/List.html
Найдите отличия этих двух интерфейсов. Поймёте, какие именно части контракта вы потеряли.
Ожидаю разного рода возражения на основании рефлекции.
Упреждаю.
Рефлекция сама по себе уже связана с нарушением контракта и очень-очень опасны. |
|
|
|
 |
Taky_ : 491 Бывалый
|
Авг 16, 2011 12:05 |
|
|
| Цитата: |
| Полезно думать самому, вам тоже советую. |
? Мои рассуждения основаны на опыте, произведенном рефакторинге + нашел из подтверждение в мыслях одного из ведущих экпертов по Java в мире.
| Цитата: |
| Ожидаю разного рода возражения на основании рефлекции. |
Глупое ожидание. Мы с вами вроде бы про ООП и нормальные подходы говорим.
Повторю еще раз, если мне достаточно в данном участке кода List, то лучше юзать List. Меньше знаешь, крепче спишь. Меньше связаность, выше ортогональность, легче менять! А изменять придется, не стойкость требований - одна из самых важных проблем в разработке ПО.
Скатились до какого-то холивара. Ну что ж теперь жалко времени. |
|
|
|
 |
|
|
|