Модераторы: Partizan, gambit
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> интерфейсы или абстрактные классы, интерфейсы или абстрактные классы 
:(
    Опции темы
rew
Дата 14.5.2012, 18:06 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 50
Регистрация: 25.9.2007

Репутация: нет
Всего: нет



привет!
У меня вопрос такой, когда следует применять интерфейсы а когда абстрактные классы?
Оба инструмента очень похоже.
PM MAIL   Вверх
Экскалупатор
Дата 14.5.2012, 21:27 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1746
Регистрация: 1.4.2009
Где: г. Минск

Репутация: 5
Всего: 24



Цитата(rew @  14.5.2012,  17:06 Найти цитируемый пост)
Оба инструмента очень похоже. 

да ладно!?! возможно стоит почитать про них более детально? попробуй например добавить в интерфейс реализацию какого нибудь метода...
PM MAIL ICQ   Вверх
erm0l0v
Дата 15.5.2012, 12:19 (ссылка)  | (голосов:5) Загрузка ... Загрузка ... Быстрая цитата Цитата


Бывалый
*


Профиль
Группа: Участник
Сообщений: 157
Регистрация: 11.1.2011

Репутация: нет
Всего: 1



Решить что применять иногда не просто.
Вы должны понимать главные минусы и того и другово.

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

Есть даже правило которое возможно поможет вам выбрать что использовать:
Если можно сказать что класс я вляется базовым классом - то в качестве базового класса стоит использовать абстактный
Если можно сказать что класс обладает свойствами базового - то в качестве базового класса стоит использовать интерфейс

Например если вы реализовали базовый класс Человек и унаследовали от него класс Работник - то логичнее будет сделать класс Человек абстрактным, т.к. Работник является человеком
А если вы реализовали класс ПолучатьЗарплату и унаследовали опять же от него класс Работник - то логичнее будет использовать интерфейс.

А вообще старайтесь везде где можно использовать абстрактные классы, они более гибкие.
PM MAIL   Вверх
rew
Дата 16.5.2012, 09:55 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 50
Регистрация: 25.9.2007

Репутация: нет
Всего: нет



erm0l0v   спасибо за ответ,  теперь стало более менее все прояснятся.



PM MAIL   Вверх
k0rvin
Дата 17.5.2012, 10:29 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 442
Регистрация: 24.1.2010

Репутация: нет
Всего: 5



Цитата(erm0l0v @ 15.5.2012,  12:19)
А главный минус что интерфейс нельзя изменять, например если вы добавите новый метод в интерфейс у вас упадет весь код который использовал этот интерфейс, и изменений ради одного метода может быть очень много.

"Минус"? А как по-вашему должен отреагировать код, использующий новый метод интерфейса, при получении объекта, класс которого формально интерфейс поддерживает, но новый метод в нем не реализован. Это называется не "минус", а статическая типизация.

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

Это сообщение отредактировал(а) k0rvin - 17.5.2012, 10:30


--------------------
“Object-oriented design is the roman numerals of computing.” — Rob Pike
All software sucks
PM MAIL   Вверх
erm0l0v
Дата 17.5.2012, 11:04 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Бывалый
*


Профиль
Группа: Участник
Сообщений: 157
Регистрация: 11.1.2011

Репутация: нет
Всего: 1



k0rvin, Это минус по сравнению с абстрактным классом.

Интерфейс формально является контрактом, и идеале написанный однажды интерфейс не должен изменяться. По этому при добавлении новых методов в контракт приходится добавлять новые интерфейсы с приставкой Ex или с номером.

В случае абстрактного класса мы можем добавить виртуальной метод с реализацией по умолчанию.

В случае с добавлением нового метода в абстрактный класс все что нужно для того чтобы использовать его достаточно только выполнить вызов нового метода.

В случае с интерфейсами кроме вызова метода необходимо менять объявление классов или перед вызовом нового метода пытаться приводить объект к интерфейсу который этот метод содержит.

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

Именно по этому рекомендуют везде где можно делать выбор в пользу абстрактных классов, а к интерфейсам прибегать только в том случае когда использование абстрактных классов просто невозможно.
PM MAIL   Вверх
k0rvin
Дата 17.5.2012, 11:55 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 442
Регистрация: 24.1.2010

Репутация: нет
Всего: 5



Цитата(erm0l0v @ 17.5.2012,  11:04)
В случае с интерфейсами кроме вызова метода необходимо менять объявление классов или перед вызовом нового метода пытаться приводить объект к интерфейсу который этот метод содержит.

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

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

Что значит "пытаться приводить"? Написав расширеный интерфейс ты его и используешь там, где нужно, и для нужных классов реализуешь этот интерфейс. Никакой разницы с абстрактными классами в данном случае.


--------------------
“Object-oriented design is the roman numerals of computing.” — Rob Pike
All software sucks
PM MAIL   Вверх
erm0l0v
Дата 17.5.2012, 12:29 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Бывалый
*


Профиль
Группа: Участник
Сообщений: 157
Регистрация: 11.1.2011

Репутация: нет
Всего: 1



представите что у вас есть интерфейс:
Код

interface IOperation
    {
        void Operation1();
        void Operation2();
    }


и некоторый код который использует ваш интерфейс (возможно написанный вашими коллегами или вообще сторонними разработчиками)

Код

class MyClass : IOperation
    {

        public void Operation1()
        {
        }

        public void Operation2()
        {
        }
    }

static void DoWork(IOperation obj)
        {
            obj.Operation1();
            obj.Operation2();
        }


у вас есть классы реализующие этот интерфейс, и код который его использует в данном случае это функция которая принимает IOperation

и тут вы в ужасе вспоминаете что забыли добавить в ваш интерфейс метод Operation3().

IOperation вы уже изменить не можете, так как вашим коллегам и сторонним разработчикам придется менять свой код, не смотря на то что им может и не нужен новый метод Operation3(), и по этому как вы предлагали ранее создаете новый интерфейс:

Код

interface IOperation2 : IOperation
    {
        void Operation3();
    }


все замечательно теперь всем кому потребуется новый метод нужно будет просто выполнить наследование от нового интерфейса, но что делать с методом DoWork? этот метод принимает IOperation а соответственно ничего не знает Operation3().

Вариант первый изменить тип параметра:
Код

static void DoWork(IOperation2 obj)
        {
            obj.Operation1();
            obj.Operation2();
                        obj.Operation3();
        }

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

Вариант второй, привести принимаемое значение к нужному интерфейсу:
Код

static void DoWork(IOperation obj)
        {
            obj.Operation1();
            obj.Operation2();
            if (obj is IOperation2)
                (obj as IOperation2).Operation3();
        }

В этом случае кроме небольшого снижения производительности так же снижается читабельность кода. Человек который посмотрит на метод DoWork не заглядывая в его код вряд ли сразу догадается что в методе используется Operation3(). Кроме того вам нужно будет стараться сохранить совместимость на случай если метод получит объект унаследованный от IOperation но не унаследованный от IOperation2 и поэтому сам код метода будет разрастаться.

Если бы вы использовали абстрактный класс всего этого можно было избежать.
PM MAIL   Вверх
k0rvin
Дата 17.5.2012, 13:25 (ссылка) |    (голосов:1) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 442
Регистрация: 24.1.2010

Репутация: нет
Всего: 5



Цитата(erm0l0v @  17.5.2012,  12:29 Найти цитируемый пост)
но в таком случае нужно понимать что метод DoWork тоже кто то использует, и наверняка продолжает передавать интерфейс IOperation

и получит ошибку компиляции, что правильно и логично

Добавлено через 8 минут и 7 секунд
Цитата(erm0l0v @ 17.5.2012,  12:29)
Человек который посмотрит на метод DoWork не заглядывая в его код вряд ли сразу догадается что в методе используется Operation3(). Кроме того вам нужно будет стараться сохранить совместимость на случай если метод получит объект унаследованный от IOperation но не унаследованный от IOperation2 и поэтому сам код метода будет разрастаться.

Как буд-то, если я изменю IOperation на IOperation2 в сигнатуре DoWork, этот человек сразу догадается, что там используется Operation3

Цитата(erm0l0v @ 17.5.2012,  12:29)
Если бы вы использовали абстрактный класс всего этого можно было избежать.

И часами пытаться узнать, почему вывод на печать не соответствует ожиданиям, хотя программа нормально скомпилировалась, а оказывается для каких-то классов DoWork не был должным образом определен и соответственно вызывался DoWork из абстрактного класса.

Еще любопытно каким образом вы собираетесь расширить какой-то чужой класс чужим же абстрактным (т.к. вам нужны методы из этого абстрактного).

Добавлено через 13 минут и 53 секунды
Вариант правильный: использовать статическую перегрузку метода DoWork:
Код

static void DoWork (IOperation obj)
{
    obj.Operation1();
    obj.Operation2();
}

static void DoWork (IOperation2 obj)
{
    DoWork(obj as IOperation);
    obj.Operation3();
}



--------------------
“Object-oriented design is the roman numerals of computing.” — Rob Pike
All software sucks
PM MAIL   Вверх
Экскалупатор
Дата 17.5.2012, 15:10 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1746
Регистрация: 1.4.2009
Где: г. Минск

Репутация: 5
Всего: 24



Цитата(erm0l0v @  17.5.2012,  10:04 Найти цитируемый пост)
Именно по этому рекомендуют везде где можно делать выбор в пользу абстрактных классов, а к интерфейсам прибегать только в том случае когда использование абстрактных классов просто невозможно. 

впервые слушу о такой рекомендации. а как же "проектируйте на уровне интерфейсов, а не реализаций"?
PM MAIL ICQ   Вверх
erm0l0v
Дата 17.5.2012, 15:16 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Бывалый
*


Профиль
Группа: Участник
Сообщений: 157
Регистрация: 11.1.2011

Репутация: нет
Всего: 1



Цитата(k0rvin @ 17.5.2012,  13:25)
Вариант правильный: использовать статическую перегрузку метода DoWork:
Код

static void DoWork (IOperation obj)
{
    obj.Operation1();
    obj.Operation2();
}

static void DoWork (IOperation2 obj)
{
    DoWork(obj as IOperation);
    obj.Operation3();
}

Приведенный вами код не избавляет от необходимости менять вызов функции 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#
PM MAIL   Вверх
Экскалупатор
Дата 17.5.2012, 15:40 (ссылка) |    (голосов:1) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1746
Регистрация: 1.4.2009
Где: г. Минск

Репутация: 5
Всего: 24



вообще холивар безосновательный изначально. это просто разные инструменты. и нужно просто научиться ими пользоваться. если все так страшно и описанный интерфейс или абстратный класс использует третья сторона то при любых изменениях ожидай проблем. единственный вариант не изменять изначальный код а расширять его дальше. ни что не мешает унаследовать первый интерфейс во втором(ровно как и абстратный класс) и добавить туда все что нужно, и соответственно изменить реализации. при любом раскладе надеяться что виртуальные методы решат все проблемы - это просто глупо. а делать виртуальные методы которые ничего не делают или кидаю исключения это похоже в итоге на тот же интерфейс. разницы никакой.
абстратный класс содержит реализацию
интерфейс определяет... как бы банальным это не показалось, но... интерфейс класса, наследующего интерфейс(даже из названия должна быть очевидна разница). если вы вносите измение в интерфейс класса то вполне ожидаема волна изменений. что тут критичного? если к интерфейсу вилки добавить возможность отправлять смс то соответственно это уже совершенно другая вилка, а значит и использование ее изменится. все логично. интерфейс просто более независим, потому что изначально не содержит и намека на реализацию. так что иногда интерфейсы будут лучше, потому что можно написать так как тебе надо.
PM MAIL ICQ   Вверх
k0rvin
Дата 17.5.2012, 15:56 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 442
Регистрация: 24.1.2010

Репутация: нет
Всего: 5



Цитата(erm0l0v @ 17.5.2012,  15:16)
единственное будет ошибка компиляции если до добавления в BaseOperation метода Operation3 кто то добавил в наследуемый класс свой метод Operation3

Не будет никакой ошибки. Так как там с аналогом реализации двух интерфейсов одним классом?


--------------------
“Object-oriented design is the roman numerals of computing.” — Rob Pike
All software sucks
PM MAIL   Вверх
agitprop
Дата 17.5.2012, 17:14 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 9
Регистрация: 17.3.2012

Репутация: нет
Всего: нет



1. Интерфейс - это контракт. Вывод: его надо использовать, для того, чтобы сказать потребителям: "я реализую такие-то методы. Вывод 2: не надо скидывать много методов в один интерфейс, разделяем.
2. Наследовать можно один класс, а реализовывать интерфейсы - много (речь про C#). Значит, наследование менее доступно, и эту возможность надо экономить. НО:
3. Когда вы уверены, что потомок обязательно будет реализовывать такие-то методы - наследуейте смело. Чтобы не было сходных классов, каждый из которых наследуется от оbject. 
PM MAIL   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Прежде чем создать тему, посмотрите сюда:
Partizan
PashaPash

Используйте теги [code=csharp][/code] для подсветки кода. Используйтe чекбокс "транслит" если у Вас нет русских шрифтов.
Что делать если Вам помогли, но отблагодарить помощника плюсом в репутацию Вы не можете(не хватает сообщений)? Пишите сюда, или отправляйте репорт. Поставим :)
Так же не забывайте отмечать свой вопрос решенным, если он таковым является :)


Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, mr.DUDA, Partizan, PashaPash.

 
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | .NET для новичков | Следующая тема »


 




[ Время генерации скрипта: 0.0880 ]   [ Использовано запросов: 21 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.