![]() |
Модераторы: Partizan, gambit |
![]() ![]() ![]() |
|
rew |
|
|||
Шустрый ![]() Профиль Группа: Участник Сообщений: 50 Регистрация: 25.9.2007 Репутация: нет Всего: нет |
привет!
У меня вопрос такой, когда следует применять интерфейсы а когда абстрактные классы? Оба инструмента очень похоже. |
|||
|
||||
Экскалупатор |
|
|||
![]() Эксперт ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 1746 Регистрация: 1.4.2009 Где: г. Минск Репутация: 5 Всего: 24 |
||||
|
||||
erm0l0v |
|
|||
Бывалый ![]() Профиль Группа: Участник Сообщений: 157 Регистрация: 11.1.2011 Репутация: нет Всего: 1 |
Решить что применять иногда не просто.
Вы должны понимать главные минусы и того и другово. Интерфейс - это просто определения контракта никакой логики включить невозможно. А главный минус что интерфейс нельзя изменять, например если вы добавите новый метод в интерфейс у вас упадет весь код который использовал этот интерфейс, и изменений ради одного метода может быть очень много. Абстрактный класс - классы унаследованные от абстрактного класса используют свой единственный билет на наследование, и лишаться возможности унаследования от другого класса. Есть даже правило которое возможно поможет вам выбрать что использовать: Если можно сказать что класс я вляется базовым классом - то в качестве базового класса стоит использовать абстактный Если можно сказать что класс обладает свойствами базового - то в качестве базового класса стоит использовать интерфейс Например если вы реализовали базовый класс Человек и унаследовали от него класс Работник - то логичнее будет сделать класс Человек абстрактным, т.к. Работник является человеком А если вы реализовали класс ПолучатьЗарплату и унаследовали опять же от него класс Работник - то логичнее будет использовать интерфейс. А вообще старайтесь везде где можно использовать абстрактные классы, они более гибкие. |
|||
|
||||
rew |
|
|||
Шустрый ![]() Профиль Группа: Участник Сообщений: 50 Регистрация: 25.9.2007 Репутация: нет Всего: нет |
erm0l0v спасибо за ответ, теперь стало более менее все прояснятся.
|
|||
|
||||
k0rvin |
|
|||
![]() Опытный ![]() ![]() Профиль Группа: Участник Сообщений: 442 Регистрация: 24.1.2010 Репутация: нет Всего: 5 |
"Минус"? А как по-вашему должен отреагировать код, использующий новый метод интерфейса, при получении объекта, класс которого формально интерфейс поддерживает, но новый метод в нем не реализован. Это называется не "минус", а статическая типизация. А если вам так нужно расширить какой-то интерфейс, то и напишите новый, наследуемый от него. Это сообщение отредактировал(а) k0rvin - 17.5.2012, 10:30 -------------------- “Object-oriented design is the roman numerals of computing.” — Rob Pike All software sucks |
|||
|
||||
erm0l0v |
|
|||
Бывалый ![]() Профиль Группа: Участник Сообщений: 157 Регистрация: 11.1.2011 Репутация: нет Всего: 1 |
k0rvin, Это минус по сравнению с абстрактным классом.
Интерфейс формально является контрактом, и идеале написанный однажды интерфейс не должен изменяться. По этому при добавлении новых методов в контракт приходится добавлять новые интерфейсы с приставкой Ex или с номером. В случае абстрактного класса мы можем добавить виртуальной метод с реализацией по умолчанию. В случае с добавлением нового метода в абстрактный класс все что нужно для того чтобы использовать его достаточно только выполнить вызов нового метода. В случае с интерфейсами кроме вызова метода необходимо менять объявление классов или перед вызовом нового метода пытаться приводить объект к интерфейсу который этот метод содержит. То есть минус не в том что в интерфейс крайне не желательно добавлять новые методы, а в том что при добавление новых методов в контракт описанный с помощью интерфейсов нам приходится менять больше кода чем при совершении того же действия с абстрактными классами. Именно по этому рекомендуют везде где можно делать выбор в пользу абстрактных классов, а к интерфейсам прибегать только в том случае когда использование абстрактных классов просто невозможно. |
|||
|
||||
k0rvin |
|
|||
![]() Опытный ![]() ![]() Профиль Группа: Участник Сообщений: 442 Регистрация: 24.1.2010 Репутация: нет Всего: 5 |
Что значит "пытаться приводить"? Написав расширеный интерфейс ты его и используешь там, где нужно, и для нужных классов реализуешь этот интерфейс. Никакой разницы с абстрактными классами в данном случае. -------------------- “Object-oriented design is the roman numerals of computing.” — Rob Pike All software sucks |
|||
|
||||
erm0l0v |
|
||||||||||
Бывалый ![]() Профиль Группа: Участник Сообщений: 157 Регистрация: 11.1.2011 Репутация: нет Всего: 1 |
представите что у вас есть интерфейс:
и некоторый код который использует ваш интерфейс (возможно написанный вашими коллегами или вообще сторонними разработчиками)
у вас есть классы реализующие этот интерфейс, и код который его использует в данном случае это функция которая принимает IOperation и тут вы в ужасе вспоминаете что забыли добавить в ваш интерфейс метод Operation3(). IOperation вы уже изменить не можете, так как вашим коллегам и сторонним разработчикам придется менять свой код, не смотря на то что им может и не нужен новый метод Operation3(), и по этому как вы предлагали ранее создаете новый интерфейс:
все замечательно теперь всем кому потребуется новый метод нужно будет просто выполнить наследование от нового интерфейса, но что делать с методом DoWork? этот метод принимает IOperation а соответственно ничего не знает Operation3(). Вариант первый изменить тип параметра:
но в таком случае нужно понимать что метод DoWork тоже кто то использует, и наверняка продолжает передавать интерфейс IOperation и нам придется менять код который использует метод DoWork и таких изменений может быть очень много, и они могут затронуть тот код который не нуждается в методе Operation3(). Например DoWork принимает результат выполнения функции CreateClass(), вы переписываете эту функцию и вместе с этим вам приходится переписывать и другие методы которые принимаю результат выполнения CreateClass() даже не смотря на то что им не нужен метод Operation3(). Вариант второй, привести принимаемое значение к нужному интерфейсу:
В этом случае кроме небольшого снижения производительности так же снижается читабельность кода. Человек который посмотрит на метод DoWork не заглядывая в его код вряд ли сразу догадается что в методе используется Operation3(). Кроме того вам нужно будет стараться сохранить совместимость на случай если метод получит объект унаследованный от IOperation но не унаследованный от IOperation2 и поэтому сам код метода будет разрастаться. Если бы вы использовали абстрактный класс всего этого можно было избежать. |
||||||||||
|
|||||||||||
k0rvin |
|
||||||||
![]() Опытный ![]() ![]() Профиль Группа: Участник Сообщений: 442 Регистрация: 24.1.2010 Репутация: нет Всего: 5 |
и получит ошибку компиляции, что правильно и логично Добавлено через 8 минут и 7 секунд
Как буд-то, если я изменю IOperation на IOperation2 в сигнатуре DoWork, этот человек сразу догадается, что там используется Operation3
И часами пытаться узнать, почему вывод на печать не соответствует ожиданиям, хотя программа нормально скомпилировалась, а оказывается для каких-то классов DoWork не был должным образом определен и соответственно вызывался DoWork из абстрактного класса. Еще любопытно каким образом вы собираетесь расширить какой-то чужой класс чужим же абстрактным (т.к. вам нужны методы из этого абстрактного). Добавлено через 13 минут и 53 секунды Вариант правильный: использовать статическую перегрузку метода DoWork:
-------------------- “Object-oriented design is the roman numerals of computing.” — Rob Pike All software sucks |
||||||||
|
|||||||||
Экскалупатор |
|
|||
![]() Эксперт ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 1746 Регистрация: 1.4.2009 Где: г. Минск Репутация: 5 Всего: 24 |
впервые слушу о такой рекомендации. а как же "проектируйте на уровне интерфейсов, а не реализаций"? |
|||
|
||||
erm0l0v |
|
||||
Бывалый ![]() Профиль Группа: Участник Сообщений: 157 Регистрация: 11.1.2011 Репутация: нет Всего: 1 |
Приведенный вами код не избавляет от необходимости менять вызов функции DoWork. Если DoWork получит объект приведенный к интерфейсу IOperation выполнится метод static void DoWork (IOperation obj) вне зависимости от того реализует объект класс IOperation2 или нет. Насколько это логично вопрос спорный, по этому возможна путаница. Например у вас есть фабрика, которая создает интерфейс IOperation. Для минимизации изменений вы настраиваете фабрику чтобы она возвращала объект унаследованный от IOperation2 с реализованным методом Operation3, но DoWork будет игнорировать ваш метод Operation3. Для того чтобы это избежать нужно будет переделывать фабрику, чтобы она возвращала не IOperation а IOperation2 а это может привести к изменениям кода который использует фабрику но не нуждается в Operation3. Или создавать в фабрике методы которые бы возвращали и IOperation и IOperation2, кроме захламления кода в сложных системах это тоже может привести к дорогостоящим изменениям. И даже если мы заменим в фабрике IOperation на IOperation2, нам придется следить что нигде в коде IOperation2 не приводится к IOperation. Если бы IOperation был не интерфейсом а абстрактным классом (BaseOperation), мы бы добавили виртуальный метод Operation3 с реализацией по умолчанию. В итоге мы меняем только DoWork и код создания объекта который принимает DoWork. Например настраиваем фабрику чтобы она возвращала новую реализацию, в итоге: Если у нас в проекте есть метод DoWork2 который принимает BaseOperation но не использует Operation3 он отработает корректно. DoWork - принимая наследника BaseOperation с переопределенным методом Operation3 выполнится корректно DoWork - принимая наследника BaseOperation с не переопределенным методом Operation3 выполнит реализацию по умолчанию, которая не должна повлиять на ход выполнения программы, в итоге метод выполнится корректно не знаю что здесь может не соответствовать ожиданиям, единственное будет ошибка компиляции если до добавления в BaseOperation метода Operation3 кто то добавил в наследуемый класс свой метод Operation3 Добавлено через 10 минут и 45 секунд Экскалупатор, Да действительно рекомендуют разделять абстракции и реализации. Под абстракцией можно понимать как интерфейсы так и абстрактные классы и просто базовые классы. Главное чтобы была возможность изменить реализацию без внесения (внесения минимальных) изменений абстрактной логики. Просто в эпоху COM царило (как мне кажется ошибочное) утверждение что для описания контрактов можно использовать только интерфейсы. Абстрактные классы для этого тоже подходят за исключением тех моментов когда абстрактные классы просто невозможно использовать из-за отсутствия множественного наследования в C# |
||||
|
|||||
Экскалупатор |
|
|||
![]() Эксперт ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 1746 Регистрация: 1.4.2009 Где: г. Минск Репутация: 5 Всего: 24 |
вообще холивар безосновательный изначально. это просто разные инструменты. и нужно просто научиться ими пользоваться. если все так страшно и описанный интерфейс или абстратный класс использует третья сторона то при любых изменениях ожидай проблем. единственный вариант не изменять изначальный код а расширять его дальше. ни что не мешает унаследовать первый интерфейс во втором(ровно как и абстратный класс) и добавить туда все что нужно, и соответственно изменить реализации. при любом раскладе надеяться что виртуальные методы решат все проблемы - это просто глупо. а делать виртуальные методы которые ничего не делают или кидаю исключения это похоже в итоге на тот же интерфейс. разницы никакой.
абстратный класс содержит реализацию интерфейс определяет... как бы банальным это не показалось, но... интерфейс класса, наследующего интерфейс(даже из названия должна быть очевидна разница). если вы вносите измение в интерфейс класса то вполне ожидаема волна изменений. что тут критичного? если к интерфейсу вилки добавить возможность отправлять смс то соответственно это уже совершенно другая вилка, а значит и использование ее изменится. все логично. интерфейс просто более независим, потому что изначально не содержит и намека на реализацию. так что иногда интерфейсы будут лучше, потому что можно написать так как тебе надо. |
|||
|
||||
k0rvin |
|
|||
![]() Опытный ![]() ![]() Профиль Группа: Участник Сообщений: 442 Регистрация: 24.1.2010 Репутация: нет Всего: 5 |
Не будет никакой ошибки. Так как там с аналогом реализации двух интерфейсов одним классом? -------------------- “Object-oriented design is the roman numerals of computing.” — Rob Pike All software sucks |
|||
|
||||
agitprop |
|
|||
Новичок Профиль Группа: Участник Сообщений: 9 Регистрация: 17.3.2012 Репутация: нет Всего: нет |
1. Интерфейс - это контракт. Вывод: его надо использовать, для того, чтобы сказать потребителям: "я реализую такие-то методы. Вывод 2: не надо скидывать много методов в один интерфейс, разделяем.
2. Наследовать можно один класс, а реализовывать интерфейсы - много (речь про C#). Значит, наследование менее доступно, и эту возможность надо экономить. НО: 3. Когда вы уверены, что потомок обязательно будет реализовывать такие-то методы - наследуейте смело. Чтобы не было сходных классов, каждый из которых наследуется от оbject. |
|||
|
||||
![]() ![]() ![]() |
Прежде чем создать тему, посмотрите сюда: | |
|
Используйте теги [code=csharp][/code] для подсветки кода. Используйтe чекбокс "транслит" если у Вас нет русских шрифтов. Что делать если Вам помогли, но отблагодарить помощника плюсом в репутацию Вы не можете(не хватает сообщений)? Пишите сюда, или отправляйте репорт. Поставим :) Так же не забывайте отмечать свой вопрос решенным, если он таковым является :) Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, mr.DUDA, Partizan, PashaPash. |
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей) | |
0 Пользователей: | |
« Предыдущая тема | .NET для новичков | Следующая тема » |
|
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности Powered by Invision Power Board(R) 1.3 © 2003 IPS, Inc. |