дилемма разработчика: базовый
класс или интерфейс?
Меня часто спрашивают, что лучше выбирать для проектировании типа — базовый
тип или интерфейс? Ответ не всегда очевиден. Вот несколько правил, которые
могут помочь вам сделать выбор.
Связь потомка с предком.
Любой тип может наследовать только одну реа-
лизацию. Если производный тип не может ограничиваться отношением типа
«является частным случаем» с базовым типом, нужно применять интерфейс,
а не базовый тип. Интерфейс подразумевает отношение «поддерживает функ-
циональность». Например, тип может преобразовывать экземпляры самого
себя в другой тип (
IConvertible
), может создать набор экземпляров самого
себя (
ISerializable
) и т. д. Заметьте, что значимые типы должны наследовать
от типа
System.ValueType
и поэтому не могут наследовать от произвольного
базового класса. В этом случае нужно определять интерфейс.
352
Глава.13 .Интерфейсы
Простота использования.
Разработчику проще определить новый тип, произ-
водный от базового, чем создать интерфейс. Базовый тип может предоставлять
массу функций, и в производном типе потребуется внести лишь незначительные
изменения, чтобы изменить его поведение. При создании интерфейса в новом
типе придется реализовывать все члены.
Четкая реализация.
Как бы хорошо ни был документирован контракт, вряд
ли будет реализован абсолютно корректно. По сути, проблемы COM связаны
именно с этим — вот почему некоторые COM-объекты нормально работают
только с Microsoft Word или Microsoft Internet Explorer. Базовый тип с хорошей
реализацией основных функций — прекрасная отправная точка, вам останется
изменить лишь отдельные части.
Управление версиями.
Когда вы добавляете метод к базовому типу, производ-
ный тип наследует стандартную реализацию этого метода без всяких затрат.
Пользовательский исходный код даже не нужно перекомпилировать. Добавление
нового члена к интерфейсу требует изменения пользовательского исходного
кода и его перекомпиляции.
В FCL классы, связанные с потоками данных, построены по принципу насле-
дования реализации.
System.IO.Stream
— это абстрактный базовый класс, предо-
ставляющий множество методов, в том числе
Read
и
Write
. Другие классы (
System.
IO.FileStream
,
System.IO.MemoryStream
и
System.Net.Sockets.NetworkStream
)
являются производными от
Stream
. В Microsoft выбрали такой вид отношений
между этими тремя классами и
Stream
по той причине, что так проще реализовывать
конкретные классы. Так, производные классы должны самостоятельно реализовать
только операции синхронного ввода-вывода, а способность выполнять асинхронные
операции наследуется от базового класса
Stream
.
Возможно, выбор наследования реализации для классов, работающих с пото-
ками, не совсем очевиден: ведь базовый класс
Stream
на самом деле предоставляет
лишь ограниченную готовую функциональность. Однако если взглянуть на классы
элементов управления Windows Forms, где
Button
,
CheckBox
,
ListBox
и все прочие
элементы управления порождаются от
System.Windows.Forms.Control
, легко пред-
ставить объем кода, реализованного в
Control
; весь этот код просто наследуется
классами элементов управления, позволяя им правильно функционировать.
Что касается
коллекций
(collections), то их специалисты Microsoft реализовали
в FCL на основе интерфейсов. В пространстве имен
System.Collections.Generic
определено несколько интерфейсов для работы с коллекциями:
IEnumerable
,
ICollection
,
IList
и
IDictionary,
TValue>
. Кроме того, Microsoft
предлагает несколько конкретных классов (таких, как
List
,
Dictionary,
TValue>
,
Queue
,
Stack
и пр.), которые реализуют комбинации этих интер-
фейсов. Такой подход объясняется тем, что реализация всех классов-коллекций
существенно различается. Иначе говоря, у
List
,
Dictionary,
TValue>
и
Queue
найдется не так много общего кода.
353
Дилемма.разработчика:.базовый.класс.или.интерфейс?
И все же операции, предлагаемые всеми этими классами, вполне согласованы.
Например, все они поддерживают подмножество элементов с возможностью пере-
бора, все они позволяют добавлять и удалять элементы. Если есть ссылка на объ-
ект, тип которого реализует интерфейс
IList
, можно написать код, способный
добавлять, удалять и искать элементы, не зная конкретный тип коллекции. Это
очень мощный механизм.
Наконец, нужно сказать, что на самом деле можно определить интерфейс
и
соз-
дать базовый класс, который реализует интерфейс. Например, в FCL определен
интерфейс
IComparer
, и любой тип может реализовать этот интерфейс. Кроме
того, FCL предоставляет абстрактный базовый класс
Comparer
, который реа-
лизует этот интерфейс (абстрактно) и предлагает реализацию по умолчанию для
необобщенного метода
Compare
интерфейса
IComparer
. Применение обеих возмож-
ностей дает большую гибкость, поскольку разработчики теперь могут выбрать из
двух вариантов наиболее предпочтительный.
Do'stlaringiz bilan baham: |