2014-01-16 3 views
0

У меня следующая ситуация:Почему это не нормально, чтобы использовать интерфейс в этом случае

interface IBaseReader{ 
    string Header {get; set;} 
    string CurrentRow {get; set;}} 

class BaseReader: IBaseReader{ 
    ......} 

interface ICustomReader{ 
    string Header {get; set;} 
    string Delimiter {get; set;}} 

class CustomReader :BaseReader, ICustomReader{ 
    ......} 

В классе файл обрабатывающий Я использую этот ICustomReader знать, если полученный экземпляр из BaseReader является ICustomReader и если это, чем я знайте, что он содержит разделитель, и я могу его проверить.

IBaseReader _reader; 
if(_reader is ICustomReader) 
    { 
     var items = (_reader as ICustomReader).Header.Split((_reader as ICustomReader).Delimiter); 
     //split the current row and header by delimiter and check if it is ok 
    } 

Мои друзья говорят, что его не нормально, чтобы написать такой код, потому что мы проверяем, если экземпляр, который в настоящее время представлен в виде IBaseReader является ICustomReaderthat появляется в производном классе, и это означает, что базовый класс должен знать кое-что о производном классе. Я генерал Мы разработали лучшее решение для этого. Но мне просто интересно, что именно он ломается и что будет правильным решением в этом случае, если мы просто не можем переместить свойство между классами?

Спасибо!

+1

Пожалуйста, укажите правильное название для своего вопроса. –

+0

Вы имеете в виду 'IDerived' вместо' ICustomReader'? – SWeko

+0

Спасибо за комментарии Я исправил пример и заголовок –

ответ

2

Нет ничего плохого в проверке наличия дополнительного интерфейса, чтобы узнать, доступно ли свойство. Проблема в том, что у вас есть два отдельных интерфейса с свойством Header, которые не представляют собой два отдельных свойства. Было бы лучше, если бы IDerived унаследовали IBaseReader:

interface IDerived : IBaseReader { 
    string Delimiter {get; set;}} 

Или, если он был полностью независимый интерфейс, который касается только с разделителем:

interface IHasDelimiter { 
    string Delimiter {get; set;}} 

Тогда нет необходимости подавать на новый интерфейс чтобы получить доступ к Header собственности, потому что у вас уже есть на текущем типе _reader:

var items = _reader.Header.Split((_reader as ICustomReader).Delimiter); 
1

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

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

+0

Вы имеете в виду «включить рефери в« ICustomReader », не так ли? Мне потребовалось несколько раз прочитать это предложение, чтобы понять, что вы имели в виду (я сначала подумал, что вы подразумеваете, что для нового интерфейса потребуется поле «ICustomReader» или что-то еще). – Chris

+0

Mybe Я не понимаю вас правильно, но для меня мы используем иерархию для добавления новых фьючерсов. В этом случае класс dericed добавляет некоторые фьючерсы, но также содержит дополнительный контракт (ICustomReader), который позволяет нам идентифицировать его и использовать на нем некоторую дополнительную логику. –

+0

OP сказал, что код, связанный с ICustomReader, находится в «классе обработки файлов», который, как я полагаю, отделен от «BaseReader», поэтому связь между базой и производным интерфейсом не будет. Это также условная проверка, поэтому она не нужна - код будет по-прежнему функционировать с учетом экземпляра 'IBaseReader', который не реализует' ICustomReader'. – nmclean

1

Одна из основных проблем, с которой этот тип кода будет иметь проблемы, - это хрупкость.

Если вы меняете ICustomReader, вам нужно только перекомпилировать этот класс и все, что имеет к нему ссылки. Это связано с тем, что только он и все, что использует его явно, должны действительно знать что-либо о его внутренних действиях (таких как его свойства).

Если вы думаете, что ваш BaseReader находится в одной сборке и ICustomReader в другой, то любое изменение к ICustomReader (например, он больше не имеет ограничитель), то это приведет к тому, BaseReader ломаться без причины.

Если вместо этого у вас был виртуальный метод на BaseReader, называемый Validate или аналогичный, его можно было бы назвать там, где вы сейчас выполняете проверку подлинности типа. В базовом классе этот метод не может ничего сделать, если не будет проверки. Затем в конкретной реализации ICustomReader вы можете переопределить проверку, чтобы выполнить любую конкретную проверку, которую вы хотите сделать.Таким образом, базовый класс знает только, что это такое и каков его интерфейс и каково его поведение, и что-то еще беспокоит конкретные реализации дочерних классов, которые знают, что они делают, и что нужно проверить на них.

Это добавочное преимущество в том, что если вы добавите ICustomReader2, вам просто нужно будет написать этот класс и не нужно идти и добавлять дополнительную логику в BaseReader. Представьте, если бы у вас было 50 пользовательских читателей, насколько беспорядочно этот код был бы во всех случаях, если утверждения, проверяющие конкретные типы. Решение этого беспорядочного кода было бы реорганизацией вашей длинной строки ifs в отдельные методы, по одному для каждого типа. Тогда легко подумать, почему бы не поместить эти методы на дочерние классы.

Другой основной причиной для этого является упрощение обслуживания. Кто-то новый для вашего кода может задаться вопросом, где валидация для ICustomReader? Ну, это на том классе, конечно. Базовый класс не там, где вы ожидаете его, поэтому он будет путать людей, если он есть.

Хорошая структура кода - довольно большая и веская тема, и я попытался применить некоторые неофициальные аргументы, которые могут помочь вам понять, почему это плохая идея. Если вы хотите получить дополнительную информацию о Google, полезный термин будет «тесно связан», а http://en.wikipedia.org/wiki/Coupling_(computer_programming)#Disadvantages перечисляет некоторые другие недостатки.

+0

Основание на вашем комментарии Я предполагаю, что вы говорите о случае, когда валидация находится в базовом классе, но я упомянул в вопросе, что логика проверки находится в совершенно другом классе. Поэтому добавление новых экземпляров IBaseRedaer вообще ничего не знает о валидации. –

+0

Я думал об этом с этой точки зрения, но он так же легко применим, если вы должны были вызвать '_reader.Validate()' из любого внешнего класса, которым он мог бы быть. Я не уверен, что та же логика не применяется повсюду. Если у некоторых читателей есть понятие о том, что они действительны, пусть все читатели имеют эту концепцию, и если на самом деле невозможно, чтобы они были недействительными, просто ничего не делайте. – Chris

Смежные вопросы