Этюды в тональности C#(продолжение).Введение.В статьях этого цикла, я постараюсь рассказать вам о тех конструкция языка C#, что ранее были оставлены мной за рамками повествования. На тот случай если вы не читали предыдущие статьи цикла, публиковавшиеся в "Компьютерной газете", рекомендую посетить мою авторскую веб-страничку на сайте издательства (короткий путь к ней http://german2004. da. ru). На этой страничке вы найдете все публиковавшиеся мной в газете статьи по тематике Net Framework, да и на другие темы. Сегодня мы с вами поговорим о понятии интерфейса. Интерфейсы.Интерфейсы это вершина, "пик" объектно ориентированного программирования. ООП всегда тяготела к абстрагированию используемых в программном проекте "сущностей". Интерфейс же - это полная абстракция, дальше идти уже некуда (хотя кто знает?). Описывая его, вы не вводите ни одной строчки реального кода, подлежащего выполнению, а лишь описываете то, как этот код должен выглядеть. Так сказать, строите предварительный макет, по модели которого, впоследствии, будете формировать реальный код. С точки зрения синтаксиса языка C# интерфейс описывается следующим образом:
Внешне описание интерфейса очень похоже на описание обычного объекта, но на этом все сходство между ними и заканчивается. Интерфейс, по своей сути, не имеет ничего общего с объектом. Начнем с того, что это вообще не тип данных, а скорее чисто лексическая конструкция. Ее предназначение, подобно делегату, описать некий набор функциональности, обязательно присутствующий в некоем объекте. В понятие "функциональности" входят такие стандартные для объекта члены, как методы, свойства, индексаторы и события. Описывать поля в интерфейсах нельзя, они не являются голой абстракцией, и этим будут нарушать идеологию интерфейса, как чистой абстрактной конструкции. Подобно объектам, интерфейсы могут наследовать функциональность друг от друга. Мы можем описать некий несложный базовый интерфейс, а затем добавить нужную функциональность в его потомках.
В этом примере мы описываем интерфейс IMyNewInterface, являющегося потомком интерфейса ImyInterface из моего предыдущего примера. Этот новый интерфейс добавляет своему предку описание доступного только для чтения булевского свойства HasPivo и метода Drink, не имеющего параметров и не возвращающего каких либо параметров. Главным достоинством интерфейса является то, что реальный тип объекта, реализующего тот или иной интерфейс, никакой роли не играет. Так в нашем примере нет никаких ссылок на какие-либо объекты Net Framework. Если класс способен реализовать нужную нам функциональность, нам совершенно безразлично произошел этот класс, скажем, от System. Windows. Forms. Panel или, к примеру, от System. Data. OleDb. OleDbCommand. Для того чтобы класс мог реализовывать интерфейс IMyInterface, требуется лишь, чтобы в нем были реализованы указанные свойство и метод. Все остальное не суть важно, в том числе и место объекта в иерархии классов Net. Framework. Например, если вы просите зажигалку у прохожего на улице, для того чтобы прикурить сигарету, вам совершенно безразлично, где именно работает этот прохожий, кто его родственники и есть ли у него дети. Является ли этот прохожий главным инженером большого предприятия или продавцом слив на рынке, вам все равно. Для вас, на самом деле, важно только одно, есть ли у него с собой зажигалка, больше вам от него ничего не требуется. Интерфейс как раз и задумывался для моделирования подобных жизненных ситуаций. Если вы вызываете некий объект только для того, чтобы с его помощью вывести строку текста на экран, вам совершенно не интересно, что этот конкретный объект, возможно, умеет разбирать регулярные выражения. В данном фрагменте кода, вам эта его функциональность просто не нужна. Вы хотите получить от него конкретное простое действие, интерфейс помогает вам получить к этому действию доступ. Вот и все. Подобно тому, как делегат является не более чем "договором" о том, как должен выглядеть некий метод, так интерфейс это примерно такой же “договор” о том, как именно должен выглядеть некий объект. Описывая интерфейс, вы задаете набор свойств или методов, которые гарантировано должны быть в объекте, реализующем этот интерфейс. Предварительное знание об их существовании и позволяет компилятору, в дальнейшем, к ним спокойно обращаться. На самом деле сравнивать делегат и интерфейс неправильно, между ними есть много общего, но хватает и различий. Так метод, подставляемый на место делегата, должен полностью совпадать с ним по маске. Объект же, подставляемый на место делегата, должен совпадать с ним лишь в тех методах, которые описаны в этом интерфейсе. Наличие или напротив отсутствие других методов или свойств никакой роли не играет. Для того чтобы объект смог реализовать нужный интерфейс, важно лишь, чтобы у него были указанные в шапке интерфейса методы и свойства. Дополнительные методы никакой роли не играют, ну, как не играет роли во время прикуривания сигареты наличие детей у прохожего, предоставившего нам зажигалку. Для кода использующего делегат безразлично реальное название метода, его замещающего. В интерфейсе, напротив, все объявленные конструкции обязательно должны называться точно так же как указано в самом интерфейсе. Если вы описали, в интерфейсе, событие onCommand, будьте любезны создать в реальном классе событие называющееся onCommand и никак иначе. Еще одним требованием является то, что в объекте обязательно должны быть реализованы все методы объявленные в интерфейсе. Интерфейс нельзя реализовать лишь частично, тут дело обстоит как в известном анекдоте про беременность. Если вы забудете реализовать какой либо метод, вы получите ошибку времени компиляции. То есть просто не сможете собрать свой проект. Один и тот же объект может реализовывать сразу несколько интерфейсов. Почему бы и нет? Ведь мы можем спросить у человека одолжившего нам зажигалку, который час? Разумеется, если у него есть часы. А наличие часов, с точки зрения бытовой логики это уже другой интерфейс. Для того чтобы обыграть возможности, предоставляемые нам интерфейсами, давайте напишем несложное консольное приложение. В нем мы смоделируем вышеописанную ситуацию, при которой мы опрашиваем прохожих на улице, с целью прикурить сигарету. Для начала опишем сам объект зажигалки, обладающий методом Go, ответственным за процесс прикуривания.
Мы описываем, в этом куске кода, свойство, возвращающее или устанавливающее объект Lighter, то есть зажигалку. Благодаря возможности брать и отдавать зажигалку, мы сможем реализовать более естественную модель взаимоотношений между прохожим и нами. Если бы нам нужно было описать свойство только для чтения или только для записи, следовало бы просто опустить в объявлении тот или иной акцессор. Следующим шагом мы описываем интерфейс владельца часов. Он у нас будет иметь метод, называющийся GetTime и возвращающий, в теории, текущую дату и время.
Следуюущим шагом приступим к описанию классов, описывающих прохожих на нашей гипотетической улице. Первый вид прохожих нам не интересен. У нет ни часов, ни зажигалки. С точки зрения программирования это ничем не примечательный потомок класса Object, не обладающий какими-либо методами или свойствами.
Следующий класс намного любопытнее. Это прямой потомок нашего предыдущего класса, но уже обладающий реализацией интерфейса ILighterMan. Мы указываем этот факт, ссылаясь на название интерфейса, через запятую, в списке предков нашего объекта. Как только мы взяли на себя ответственность за реализацию интерфейса, нам придется добавить к нашему будующему объекту реализацию свойства lighter, объявленного в интерфейсе. Для этого я описал в теле класса private переменную класса Lighter (lighter1), и добавил публичное свойство для доступа к нему, по маске описанной в интерфейсе. Таким образом, мы выполнили "контракт" по реализации интерфейса ILighterMan. Объявленное в нем обязательное свойство теперь реализовано и мы можем смело утверждать о том, что данный класс реализует интерфейс ILighterMan.
Третий вид прохожих попадающихся на нашей улице выглядит еще вычурнее. Для того чтобы подчеркнуть вам достоинство от использования интерфейсов, я унаследовал его не от вновь создаваемой нами иерархии классов производных от Walker, а от класса System. Data. DataColumn. Я намеренно выбрал столь неественный для нашей программы класс. Надеюсь, вы еще не забыли о том, что понятие реализации интерфейса никак не связано с понятием наследования в иерархии классов Net. Framework? Благодаря этому обстоятельству мы можем оформлять реализацию нужного нам интерфейса в самых непредсказуемых, на первых взгляд, объектах. Также этот вид объекта реализует сразу два интерфейса, ставший привычным для нас интерфейс ILighterMan, а также интерфейс IHasTime, отвечающий за наличие часов. Наследование дополнительного интерфейса накладывает на нас обязательство реализовать, помимо свойства lighter, описанного в интерфейсе ILighterMan, еще и метод GetTime, описанный в интерфейсе IHasTime.
Описав всех действующих лиц, можем приступать к реализации основного потока программы, моделирующего поведение прохожих на улице. Основной поток, для простоты примера, мы реализуем прямо в методе Main нашей программы. Если бы я писал реальную программу, а не пример, я бы его оформил, скорее всего, в виде отдельного класса. Я намеренно подробно расписал в комментариях каждый шаг нашей программы. Обратите внимание на то, что, получая доступ к объекту, мы приводим его к типу нужного интерфейса, а не к типу какого-либо объекта. static void Main(string[] args){ Обратите свое внимание на довольно любопытный нюанс этой программы. Мы с вами добавляем в массив совершенно разнородные объекты. В дальнейшем мы вовсе не интересуемся их настоящим типом! Этот самый «настоящий тип объекта» нам просто неинтересен. Мы просто выясняем, с помощью команды is, реализован ли в этом объекте нужный нам интерфейс. Если такой интерфейс присутствует, мы приводим этот объект к виду эту интерфейса. Внешне все это выглядит как создание переменной типа интерфейса. Дальше мы совершенно спокойно работаем с объектом таким образом, как будто это экземпляр некоего гипотетического объекта ILighterMan. Но заметьте, в реальности это объект совершенно другого типа, а объекта типа ILigherMan так и вовсе не существует в природе! Тем не менее, мы пользуемся реализуемыми им методами и свойствами, в рамках объявленного нами интерфейса. Этот программный прием широко используется в иерархии классов Net. Framework, сошлюсь в качестве примера на пространство имен System. Collection. В нем имеется множество интерфейсов предназначенных для создания списков, коллекций, а также сравниваемых и клонируемых классов. (с) Герман Иванов [Продолжение следует.] | |
Ссылки:
|