Как сделать интерфейс в c
Перейти к содержимому

Как сделать интерфейс в c

  • автор:

Интерфейсы в C#

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

Можно встретить достаточно много различных определений, что такое интерфейс в программировании и, в частности, в языке C#. От самых общих, например, «интерфейс — это то по средствам чего что-то взаимодействует с чем-то». До более понятных и конкретных: интерфейс — это контракт о взаимодействии между различными частями системы. При этом, под «системой» понимается не только одна единственная программа, но и совокупность программных средств, разработанных, в том числе, и разными группами разработчиков. Тема интерфейсов в C# — это довольно интересная и, что главное, одна из наиболее важных тем в изучении основ программирования на C#. И сегодня мы начнем эту тему изучать.

Язык C# — это довольно динамично развивающийся язык программирования и очередные нововведения, например, в C# 8.0 достаточно серьезно меняют концепцию интерфейсов, в принципе, однако, мы будем разбирать тему постепенно — от самых основ и до последних нововведений. Начнем как полагается — с классики.

Интерфейсы в C#

До выхода C# 8.0, что такое интерфейс, с точки зрения программирования, можно было определить следующим образом — это именованная область кода, содержащая сигнатуры методов, свойств, индексаторов и событий. То есть, интерфейс, в его классическом понимании и представлении, не должен содержать никаких реализаций чего-либо — только сигнатуры.

Выше, мы определились с тем, что интерфейс — это также некий контракт о взаимодействии. Зачем такой «контракт» нужен и когда его следует использовать?

Рассмотрим следующую задачу: нам необходимо наделить несколько классов одними и теми же методами. Здесь мы можем пойти следующим путем: если эти классы наши и классы родственны по своей сути, то мы можем выделить для них класс-предок, в котором реализовать необходимые нам методы и, соответственно, все наследники эти методы унаследуют и, при необходимости перегрузят или переопределят. Вполне простой и понятный ход работы. Но как решать такую задачу, если: а) классы не родственны по своей сути; б) мы хотим, чтобы нашим кодом (например, библиотекой классов) могли воспользоваться другие разработчики и написать свои реализации методов? И именно с этих двух моментов и начинается практическая значимость интерфейсов в C#.

Объявление интерфейса в C#

Для того, чтобы объявить интерфейс в C# используется ключевое слово interface . В общем случае, объявление интерфейса выглядит следующим образом:

interface имя_интерфейса

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

Для примера работы с интерфейсами в C# рассмотрим следующую ситуацию: есть человек, магазин и банк. Три неродственные сущности. И у человека, и у магазина и у банка в распоряжении могут находиться деньги, например, у человека деньги хранятся в кошельке, у магазина — в кассе, у банка — в сейфе. Нам необходима сделать так, чтобы у всех трех классов были одинаковые методы работы с деньгами: метод, позволяющий положить деньги, например, в кошелек и метод, позволяющий изъять деньги из кошелька. Для этого объявим вот такой интерфейс:

interface IMoneyVault

Как можно видеть, наш интерфейс, во-первых, не содержит никаких реализаций методов — только сигнатуры и, во-вторых, методы интерфейса не имеют никаких модификаторов доступа — все они по умолчанию публичны и имеют модификатор public (по крайней мере до версии C# 8.0). После того, как мы объявили интерфейс, классы его (интерфейс) реализующие должны в обязательном порядке реализовать всё, что есть у интерфейса. В нашем случае — это два метода: MonewAdd и MonewRemove .

Реализация интерфейсов в C#

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

Интерфейсы в C#

Обратите внимание, что Visual Studio сразу же, как только вы напишете имя реализуемого интерфейса, подчеркнет его, а в списке ошибок появятся одна или несколько ошибок следующего содержания:

Ошибка CS0535 «Person» не реализует член интерфейса « IMoneyVault.MoneyAdd(int) «.

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

public class Person : IMoneyVault < private int currentMoney; public int MoneyAdd(int count) < return currentMoney += count; >public int MoneyRemove(int count) < return currentMoney -= count; >>

Конечно, здесь немного теряется логика в том плане, что у нас человек, образно говоря, становится хранилищем денег (реализует интерфейс IMoneyVault ) и, по-хорошему, надо было бы назвать наш класс несколько иначе, но для примера оставим всё так как есть. После того, как интерфейс реализован, мы можем использовать его методы. Например:

Person Person = new Person();//создаем объект Console.WriteLine(Person.MoneyAdd(10)); //добавили деньги Console.WriteLine(Person.MoneyRemove(5));//изъяли деньги

Аналогичным образом мы можем реализовать этот же интерфейс и в другом классе и написать для методов MoneyAdd и MoneyRemove . Например, путь у класса магазина при добавлении денег будет считаться сразу и сумма НДС:

public class Shop : IMoneyVault < private int currentMoney; public int CurrentMoney < get=>currentMoney; > private double nalog; public double Nalog < get=>nalog; > public int MonewAdd(int count) < currentMoney += count; nalog += 0.13 * currentMoney; return currentMoney; >public int MonewRemove(int count) < currentMoney -= count; nalog -= 0.13 * currentMoney; return currentMoney; >>
Shop Shop = new Shop(); Shop.MoneyAdd(1000); Console.WriteLine($"Всего денег: "); Console.WriteLine($"В том числе НДС: ");

Всего денег: 1000
В том числе НДС: 130

Таким образом, как мы будем реализовывать интерфейс в нашем классе — это наше дело, но сигнатуры методов и других сущностей объявленных в интерфейсе должны в точности совпадать. Например, вот такая реализация метода MoneyAdd ошибочна:

public double MonewAdd(int count)

так как в интерфейсе этот метод имеет другую сигнатуру — возвращаемое значение должно быть int , а не double .

Использование интерфейсов в C#

В примерах выше, мы явно создавали класс Person или Shop и вызывали методы. Однако, это не единственный способ использования интерфейсов в C#. Рассмотрим такой пример:

Shop Shop = new Shop(); object data = Shop; //теперь просто так вызвать MoneyAdd нельзя IMoneyVault moneyVault = Shop; //создавать интерфейсные переменные - можно if (data is IMoneyVault) < int money = ((IMoneyVault)data).MoneyAdd(10); Console.WriteLine(money); >int money2 = moneyVault.MoneyAdd(100); Console.WriteLine(money2);

Здесь происходит следующее:

  • вначале мы создаем переменную типа Shop так как мы привыкли это делать;
  • далее, мы присваиваем полученное значение переменой типа object , т.е. переменной самого базового типа.
  • и, наконец, мы создаем переменную типа интерфейса. Сам интерфейс мы так создать не можем, т.к. конструктора у него нет никакого (даже по умолчанию), то есть вот такой код вызовет ошибку:
IMoneyVault vault = new IMoneyVault();

Ошибка CS0144 Не удается создать экземпляр абстрактного типа или интерфейса «IMoneyVault»

но, при этом, в C# мы можем завести переменную интерфейсного типа и спокойно присваивать такоё переменной значения, которое является любым объектом, реализующим указанный интерфейс, что мы и сделали. Далее, так как переменная data , хоть и хранит ссылку на Shop , но о методах интерфейса эта переменная уже ничего не знает, поэтому мы:

  1. используя ключевое слово is проверяем действительно ли в переменной дата содержится объект класса, реализующего интерфейс IMoneyVault
  2. вызываем метод MoneyAdd

Ну и, наконец, так как мы оперируем переменными ссылочных типов, то результат выполнения этого кода будет следующим:

так как переменные data и moneyVault по сути содержат ссылки на один и тот же объект Shop .

Итого

Сегодня мы рассмотрели основные моменты работы с интерфейсами в C#. Научились объявлять интерфейсы и реализовывать их в наших классах. Всё, что мы рассмотрели в этой части справедливо для всех версий .NET и C#, однако, возможности интерфейсов были значительно расширены в C# 8.0 и более новых версиях, о чем мы и поговорим в следующих частях учебника.

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

Как сделать интерфейс в c

Интерфейс — это особый вид класса. Если класс служит для создания объектов каких-либо типов, наделённых неким набором возможностей, то интерфейс предназначен для других целей.

Описание интерфейса во многом напоминает описание класса, но вместо ключевого слова class используется слово interface . По-умолчанию всякий интерфейс являются доступным в сборке (можно использовать слово internal ), а можно задать общий доступ ( public ).

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

Методы, объявленные в интерфейсе, всегда неявно имеют доступ public , но использовать это слово (или какое-нибудь другое, означающее доступ) в объявлении нельзя.

В классе, который наследует от интерфейса, даётся реализация метода, при этом метод объявляется открытым.

Методы, объявленные в интерфейсе, не могут быть виртуальными.

Формально интерфейс с одним методом можно записать таким образом:

Подразумевается, что этот интерфейс содержит один метод. Реализации здесь нет. Она должна быть в классе, который станет наследником интерфейса.

Что нам даёт применение интерфейсов? Это позволяет для разных классов реализовывать однотипный набор действий (методов) с одним видом вызова методов, но при этом реализация методов наверняка будет различной в разных классах.

Пример 1 . Пусть имеется два вида объектов: игроки в мяч и те, кто умеет играть на гитаре. И те, и другие играют, но, естественно, делают это по-разному. Создадим интерфейс, в котором дадим описание метода, характеризующее это действие, т.е. игру, а так же классы, базирующиеся на этом интерфейсе. И именно в этих классах дадим необходимую реализацию.

Возможная реализация программы:

public class Ball: IPlayer

public void play()

Console.WriteLine(«Игра в мяч»);

public class Gitara: IPlayer

public void play()

Console.WriteLine(«Игра на гитаре»);

public static void Main(string[] args)

Console.WriteLine(«Тест одного интерфейса для двух классов»);

Ball x = new Ball();

Gitara y = new Gitara();

Console.Write(«Press any key to continue . . . «);

В нашем примере в интерфейсе IPlayer имеется один метод play() , причём мы записали только заголовок метода. В каждом из классов-наследниках ( Ball и Gitara ) даём свою реализацию этого метода. В итоге однотипное действие ( играть ) в объектах разных классов вызывается одним и тем же способом.

Вторая причина использования интерфейсов — отсутствие множественного наследования в языке C#. В этом языке каждый класс может наследовать только от одного класса, а вот количество предков-интерфейсов ничем не ограничено. Поэтому за счёт использования интерфейсов можно создать некое подобие множественного наследования.

Пример 2 . Разовьём пример 1. Создадим для классов Ball и Gitara общего наследника — класс Man , при этом наши классы по-прежнему будут наследовать и от интерфейса IPlayer .

Возможная реализация программы:

public class Man

public Man(string n)

public void print()

public class Ball: Man, IPlayer

public Ball(): base()

public Ball(string n): base(n)

public void play()

Console.WriteLine(» — играет в мяч»);

public class Gitara: Man, IPlayer

public Gitara(): base()

public Gitara(string n): base(n)

public void play()

Console.WriteLine(«- играет на гитаре»);

public static void Main(string[] args)

//Наследование от одного класса и одного интерфейса для двух классов

Ball x = new Ball(«Иван»);

Gitara y = new Gitara(«Андрей»);

Console.Write(«Press any key to continue . . . «);

Результат работы программы:

Иван — играет в мяч

Андрей- играет на гитаре

Press any key to continue . . .

Во втором примере в классе Man имеется одно поле name и два варианта конструктора. Классы Ball и Gitara наследуют и от класса Man , и от интерфейса IPlayer , причём при описании классов-наследников вначале указывается класс-предок и только потом один или несколько интерфейсов. Здесь пришлось немного изменить тексты классов Ball и Gitara : добавились конструкторы, при этом в них организован вызов подходящего конструктора базового класса (без параметров или с одним параметром).

Как было сказано выше, класс может наследовать от нескольких интерфейсов. Если методы, определённые в интерфейсах, разные, то ни каких проблем быть не может. Но что будет, если в разных интерфейсах имеется один и тот же метод? Можно ли реализовать такое наследование? И если можно, то как? Рассмотрим это на конкретном примере.

Пример 3 . Создадим класс Student , который будет наследовать от класса Man и интерфейсов IBall и IGitara (классы Ball и Gitara переделаем в интерфейсы). В этих интерфейсах определим одинаковый метод play() . В общем, наделим объекты типа Student способностью играть в мяч и играть на гитаре (при этом наш «студент» ещё и учится(!!) — у класса Student есть собственный метод study() , отвечающий за этот процесс).

Возможная реализация программы:

Что есть интерфейсы на Си?

Доброго всем времени суток, интересует такой вопрос, как реализовать интерфейсы на Си(например как в С++ IApplication), как они вообще реализуются и что это будет, это будет какая-либо структура или просто набор функций? К примеру:

struct IApplication < void (*load)(void); void (*shutdown)(void); >; 

Объясните на пальцах 🙂

xterro ★★★★★
21.10.10 14:14:53 MSD

>это будет какая-либо структура или просто набор функций?

ну, как сделаешь, так и будет.
хочешь полиморфизм — структура, если нет — набор функций залепил, и все.

k0l0b0k ★★
( 21.10.10 14:19:32 MSD )
Ответ на: комментарий от k0l0b0k 21.10.10 14:19:32 MSD

А как тогда с объектом, который реализует этот интерфейс? Что ему делать? Если в С++ я наследуюсь от интерфейса и перегружаю его методы, то тут как быть, наследования же нет?

xterro ★★★★★
( 21.10.10 14:26:35 MSD ) автор топика

руки прочь от сишечки, проклятые шарперы!!

Интерфейсы — это защита от дурака. Мол, забыл какой-то метод определить — и бац, ошибка компиляции.

Никто не запрещает написать комментом

/* implements that interface */

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

yoghurt ★★★★★
( 21.10.10 14:29:24 MSD )
Ответ на: комментарий от xterro 21.10.10 14:26:35 MSD

Есть тут наследование, вы просто не умеете его готовить (с)

yoghurt ★★★★★
( 21.10.10 14:30:03 MSD )
Ответ на: комментарий от xterro 21.10.10 14:26:35 MSD

struct Base < // your pointers >; struct Derived < struct Base myPrivateBase; // + заполняешь myPrivateBase своими указателями >;

если для десктопа, то лучше возьми GLib. а вообще городить ООП на C — моветон (щас меня будут бить, но это имхо)

k0l0b0k ★★
( 21.10.10 14:31:30 MSD )
Ответ на: комментарий от xterro 21.10.10 14:26:35 MSD

static void my_load(void) < . >static void my_shutdown(void) < . >struct IApplication < .load = my_load, .shutdown = my_shutdown, >MyObject;

в лучших традициях ядра линукса же 🙂

arsi ★★★★★
( 21.10.10 14:31:30 MSD )
Ответ на: комментарий от xterro 21.10.10 14:26:35 MSD

struct IApplication < void (*load)(void); void (*shutdown)(void); >; struct MyApplication < struct IApplication; void (*myFunc)(void); >; 

kulti ★★
( 21.10.10 14:32:00 MSD )
Ответ на: комментарий от yoghurt 21.10.10 14:29:24 MSD

>и поналепить тот набор функций, который нужен для реализации того или иного интерфейса. Компилироваться будет так же

будет лучше. накладники на indirect call никто не отменял еще.

k0l0b0k ★★
( 21.10.10 14:32:22 MSD )
Ответ на: комментарий от k0l0b0k 21.10.10 14:31:30 MSD

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

xterro ★★★★★
( 21.10.10 14:36:01 MSD ) автор топика
Ответ на: комментарий от arsi 21.10.10 14:31:30 MSD

Наверно так и получится. а ядре линукса так сделано?

xterro ★★★★★
( 21.10.10 14:37:45 MSD ) автор топика
Ответ на: комментарий от k0l0b0k 21.10.10 14:32:22 MSD

Лучше, да не то же самое. Если функция принимает неизвестный тип с известными методами, то без описания интерфейса (способа доступа к методам) не обойтись.

unsigned ★★★★
( 21.10.10 14:39:46 MSD )
Ответ на: комментарий от xterro 21.10.10 14:37:45 MSD

> а ядре линукса так сделано?

да, местами. файловый в/в, например, так реализован (struct file_operations выступает как интерфейс).

arsi ★★★★★
( 21.10.10 14:44:25 MSD )
Ответ на: комментарий от xterro 21.10.10 14:36:01 MSD

не, вопрос верный, годно.
просто почему не C++? делать руками ту работу, которую делает за тебя компилятор — не самое лучшее решение.

k0l0b0k ★★
( 21.10.10 14:45:19 MSD )
Ответ на: комментарий от unsigned 21.10.10 14:39:46 MSD

>Лучше, да не то же самое. Если функция принимает неизвестный тип с известными методами, то без описания интерфейса (способа доступа к методам) не обойтись.

ну само собой. но ТС спрашивает же про оба варианта?

k0l0b0k ★★
( 21.10.10 14:45:58 MSD )
Ответ на: комментарий от k0l0b0k 21.10.10 14:45:58 MSD

А в C++ нет интерфейсов %

но ТС спрашивает же про оба варианта?

Для плагинов выбора вроде и нет.

unsigned ★★★★
( 21.10.10 14:53:32 MSD )
Ответ на: комментарий от unsigned 21.10.10 14:53:32 MSD

>А в C++ нет интерфейсов %

Руками сделать можно

yoghurt ★★★★★
( 21.10.10 14:56:33 MSD )
Ответ на: комментарий от yoghurt 21.10.10 14:56:33 MSD

Руками и в си можно, еще и красивее выйдет.

unsigned ★★★★
( 21.10.10 15:03:20 MSD )
Ответ на: комментарий от unsigned 21.10.10 14:53:32 MSD

вам видимо ключевого слова interface недостает?
абстрактные классы вполне подходят для понятия интерфейса.

k0l0b0k ★★
( 21.10.10 15:04:05 MSD )
Ответ на: комментарий от k0l0b0k 21.10.10 15:04:05 MSD

Недостает. Интерфейс — не то же, что и класс, пусть даже абстрактный, а реализация — не то же, что наследование. Это работает на практике, но криво в теории )

unsigned ★★★★
( 21.10.10 15:09:26 MSD )
Ответ на: комментарий от yoghurt 21.10.10 14:56:33 MSD

> просто почему не C++?

Начал уж на си, переделывать не хочется. Да и С++ вроде как не предлагает ничего такого чего нельзя сделать на си 🙂

xterro ★★★★★
( 21.10.10 15:10:01 MSD ) автор топика

Нормально сделать нельзя. В кривом^WЪ-Си стиле — см. ядро. Твоему «интерфейсу» не хватает указателя на объект, с которым он оперирует.

tailgunner ★★★★★
( 21.10.10 15:11:47 MSD )
Ответ на: комментарий от unsigned 21.10.10 15:09:26 MSD

>Недостает. Интерфейс — не то же, что и класс, пусть даже абстрактный, а реализация — не то же, что наследование. Это работает на практике, но криво в теории )

не то же. но и не хуже. тот же indirect call. короче мужики пишут, и не смущаются 🙂

k0l0b0k ★★
( 21.10.10 15:13:15 MSD )
Ответ на: комментарий от xterro 21.10.10 15:10:01 MSD

> Да и С++ вроде как не предлагает ничего такого чего нельзя сделать на си 🙂

Попытка троллинга не засчитана — твоя квалификация очевидно слишком низка, чтобы троллить на такую тему 😉

tailgunner ★★★★★
( 21.10.10 15:13:16 MSD )

Сторонники ООП меня заплюют сейчас, но я скажу, что интерфейсы нужны только в 10% случаев, в которых их применяют. Так что если тебе хочется их сделать, подумай ещё раз.

Может быть они тебе таки не нужны.

P.S. хорошая подробная докуменация к написанию плагина (с указанием того, что и как надо писать, чтобы работало) гораздо эффективнее, чем ругань компилятора. Всё равно без документации/просмотра кода никто не полезет писать эти плагины. А если и напишет, то у него оно работать не будет.

anonymous
( 21.10.10 15:15:34 MSD )
Ответ на: комментарий от tailgunner 21.10.10 15:13:16 MSD

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

Нормально сделать нельзя. В кривом^WЪ-Си стиле — см. ядро. Твоему «интерфейсу» не хватает указателя на объект, с которым он оперирует.

Я думал что наоборот структура объекта должна содержать указатель на интерфейс, который реализует. хммм .

xterro ★★★★★
( 21.10.10 15:18:47 MSD ) автор топика

Давно волнует вопрос, как на си сделать реалиацию двух и более интерфейсов в одном типе. Кто-нибудь извра^W занимался?

unsigned ★★★★
( 21.10.10 15:19:58 MSD )
Ответ на: комментарий от xterro 21.10.10 15:18:47 MSD

да и имел ввиду реализацию в рамках моей задачи.

Неважно, в рамках чего. Си++ позволяет решить задачу прозрачным и типобезопасным способом.

Я думал что наоборот структура объекта должна содержать указатель на интерфейс, который реализует. хммм .

У тебя и его нет. Но вообще, если уж хочешь ООП на Си, то это делается так:

struct ops < void (*foo)(void *obj, int arg1 /* итд */); >; struct obj < void *data; struct ops *ops; >; obj->ops->foo(obj->data); 

tailgunner ★★★★★
( 21.10.10 15:23:40 MSD )
Ответ на: комментарий от anonymous 21.10.10 15:15:34 MSD

Как же реализовать модульную структуру без «интерфейсов»? Думаю здесь они как раз нужны.

xterro ★★★★★
( 21.10.10 15:32:41 MSD ) автор топика

Говорят, что интерфейсы это *.h файлы, тогда как в *.c — реализация. Получается КИС модель с деревом включения вроде:

 act.h -- общий итерфейс / \ / \ anim.h \ -- интерфейс между / \ \ cat & dog / \ \ cat.c dog.c voice.c -- реализация 

quasimoto ★★★★
( 21.10.10 15:33:19 MSD )
Ответ на: комментарий от xterro 21.10.10 15:32:41 MSD

см. NPAPI к примеру.

k0l0b0k ★★
( 21.10.10 15:33:56 MSD )
Ответ на: комментарий от xterro 21.10.10 15:32:41 MSD

Если ABI сломать боишься ты и инкапсуляцию нарушить страшно тебе, не пиши код на C

ACR
( 21.10.10 15:52:24 MSD )
Ответ на: комментарий от quasimoto 21.10.10 15:33:19 MSD

Наверно эта модель узкоспецифична, либо обобщена, например у нас кошка не может лаять, а собака наоборот, мяукать, тогда если в anim.h определить два метода: bark() и mew() к примеру, тогда получается что их надо реализовать и в cat.c и в dog.c получится что собака сможет мяукать. хотя модель интересная 🙂

xterro ★★★★★
( 21.10.10 15:53:15 MSD ) автор топика
Ответ на: комментарий от xterro 21.10.10 15:53:15 MSD

Наоборот, это то что играет роль интерфейсов в си. Структуры содержащие функции в качестве полей это немного другое — интерфейс в свете наследования. У меня был абстрактный пример на тему КИС (клиент-интерфейс-сервер), если его немного переделать, то получится так:

 main.h // системный интерфейс / \ / main.c // entry point / animals.h // звериный интерфейс, объекты, аккесоры, алокаторы / | \ / | dog.c // реализация - конструкторы, / cat.c // статические методы / actions.h // интерфейс действий | voice.c // полиморфный метод 

Опять дерево — узлы это файлы интерфейса, листы — файлы реализаций, в каждый файл включается (include) только один предок. Получается модель проектирования сверху вниз — если сразу сделать эту схему неправильно, то как ни старайся — не поедет (т.к. нужно следовать правилам включения интерфейсов — будут ошибки), а если сделать правильно и следовать правилу включения — всё будет нормально. В принципе тут даже header guards не нужен — но чтобы привести пример работы я подключил в main.c интерфейсы (т.е. уже граф), ну и сделал #pragma once.

/* * animals.h -- interface for animals */ #pragma once #include "main.h" typedef char* String; /* objects */ typedef enum < CAT, DOG >Animals; typedef struct < Animals type; String name; >Animal; /* cats */ typedef enum < WHITE, BLACK, GRAY >Colors; typedef struct < Animals type; String name; Colors color; >Cat; /* dogs */ typedef enum < MASTIFF, BULLTERRIER, MONGREL >Breeds; typedef struct < Animals type; String name; Breeds breed; >Dog; /* accessors */ #define type(X) (((Animal*) (X))->type) #define name(X) (((Animal*) (X))->name) #define color(X) (((Cat*) (X))->color) #define breed(X) (((Dog*) (X))->breed) /* macro routins for constructors */ #define ALLOCATE_IT_ANIMAL(TYPE) \ TYPE *IT; \ IT = (TYPE*)malloc(sizeof(TYPE)) #define RETURN_IT_ANIMAL \ return (Animal*) IT /* predefinitions */ extern Animal *make_cat(String name, Colors color); extern Animal *make_dog(String name, Breeds breed); extern String breed_string(Dog *dog); extern String color_string(Cat *cat); 
/* * actions.h -- actions interface */ #pragma once #include "animals.h" extern void voice(Animal *animal); 
/* * main.h -- system interface */ #pragma once #include #include #include
/* * cat.c -- cat's close friends */ #include "animals.h" Animal* make_cat(String name, Colors color) < ALLOCATE_IT_ANIMAL(Cat); IT->type = CAT; IT->name = name; IT->color = color; RETURN_IT_ANIMAL; > String color_string(Cat *cat) < switch (color(cat)) < case WHITE: return "White"; case BLACK: return "Black"; case GRAY: return "Gray"; >error(-1, 1, "Bad Cat in color_string"); return NULL; > 
/* * dog.c -- dog's close personal friends */ #include "animals.h" Animal* make_dog(String name, Breeds breed) < ALLOCATE_IT_ANIMAL(Dog); IT->type = DOG; IT->name = name; IT->breed = breed; RETURN_IT_ANIMAL; > String breed_string(Dog *dog) < switch (breed(dog)) < case MASTIFF: return "Mastiff"; case BULLTERRIER: return "Bullterrier"; case MONGREL: return "Mongrel"; >error(-1, 2, "Bad Dog in breed_string"); return NULL; > 
/* * voice.c -- voice (poly) method */ #include "actions.h" void voice(Animal *animal) < switch (type(animal)) < case CAT: printf("%s cat %s say mew ^_^\n", color_string((Cat*)animal), name(animal)); break; case DOG: printf("%s dog %s say bark >. > 
/* * main.c -- the `main' entry point */ #include "main.h" #include "animals.h" #include "actions.h" int main()
# # Makefile for the `c-iface' example # .PHONY: all clean TARGET=c-iface CC = gcc LINKFLAGS = -g CFLAGS = -c -g -Wall -O3 SRCS = cat.c dog.c voice.c main.c OBJS = $(SRCS:.c=.o) all: $(TARGET) $(TARGET): $(OBJS) $(CC) $ -o $@ $^ %.o: %.c $(CC) $(CFLAGS) $^ clean: -rm -f *.o *~ $(TARGET) 
$ make all gcc -c -g -Wall -O3 cat.c gcc -c -g -Wall -O3 dog.c gcc -c -g -Wall -O3 voice.c gcc -c -g -Wall -O3 main.c gcc -g -o c-iface cat.o dog.o voice.o main.o $ ./c-iface Gray cat Barz say mew ^_^ Mastiff dog Barboz say bark >. 

quasimoto ★★★★
( 21.10.10 18:30:29 MSD )
Ответ на: комментарий от quasimoto 21.10.10 18:30:29 MSD

Интересный пример, спасибо 🙂

xterro ★★★★★
( 21.10.10 18:52:38 MSD ) автор топика
Ответ на: комментарий от quasimoto 21.10.10 18:30:29 MSD

Надо делать примерно так, как предложил arsi: объект - экземпляр структуры с указателями на функции, принимающими указатель на этот объект в качестве аргумента.

Sorcerer ★★★★★
( 21.10.10 20:37:25 MSD )
Ответ на: комментарий от quasimoto 21.10.10 18:30:29 MSD

Браво. Давно не встречал такого хорошего примера, как не надо делать.

tailgunner ★★★★★
( 21.10.10 20:44:01 MSD )
Ответ на: комментарий от quasimoto 21.10.10 18:30:29 MSD

> это то что играет роль интерфейсов в си.

тот фейспалм, что вы привели, представляет собой самую неудачную реализацию классов в Си, которые я встречал. а интерфейсы — это более абстрактная сущность. в с++, например, «настоящих» интерфейсов нет. в c++/qt уже что-то похожее прорезается (но с++ удачно пихает палки в колёса qt, так что это ещё далеко не то, чего хотелось бы). более-менее юзабильные интерфейсы есть в жабе, но и там много чего не хватает «для полного счастья».

arsi ★★★★★
( 21.10.10 21:07:44 MSD )
Ответ на: комментарий от tailgunner 21.10.10 20:44:01 MSD

Давно не встречал такого хорошего примера, как не надо делать.

quasimoto ★★★★
( 21.10.10 23:23:01 MSD )
Ответ на: комментарий от arsi 21.10.10 21:07:44 MSD

тот фейспалм, что вы привели, представляет собой самую неудачную реализацию классов в Си

Велосипед не мой, но всё равно не понятно что тут страшного - ещё немного и будет tagged pointers, которые довольно часто используются. Не говоря уже о том, что если не следовать правилу включения заголовков - вообще нифига не получится написать. В общем, это другие интерфейсы (тоже, не я их так назвал).

quasimoto ★★★★
( 21.10.10 23:25:52 MSD )

На лоре куча тредов «на C сделать как в [язык более высокого уровня]». Читаю и поражаюсь что с людьми современное образование делает. Вдолбили вам ООП, но забыли сказать что это набор ОГРАНИЧЕНИЙ которые повышают(иногда) производительность труда програмиста. Эти ограничени придумали другие програмисты, до вас, и воплотили в виде всяких языков. Чтоб язык говорил error когда вы идеологию нарушаете. Все программерские техники/идеологии это договоренность програмиста с самим собой/коллективом/сообщесвом. C дает свободу. Вы работаете один? Выдумайте свою идеологию/набор ограничений и, для простоты, запихайте ее в имена функций/переменных. Конечно компилятор за вас ваши штаны поддерживать не будет когда вы свои ограничения нарушите. Ну напишите препроцессор как Qt'шники. Пока вы этим будете заниматься поймете зачем нужны интерфейсы, чтоб не говорить «хочу как там» и поймете что реализация интерфейсов, для вашего проекта не нужна и что они хороши(иногда) когда их за вас сочинили.

Плагин - вот ведь мерзкое слово. Все кругом бегают «хотим плагинов, хотим плагинов». А зачем они? Чтоб придать расширяемость закрытому коду. А что они такое? Какоето API реализованое в ста местах сотней способов. Слово плагин ничего не говорит, кроме того, что продукт будет продаваться, и покупатель будет иметь возможность продукт расширять не видя кода. Плагины не нужны(вредны) если есть исходники и документация. Судя по тому что вы пишете "(для своей небольшой системки)" .

Структуры в C надо применять только в одном случае - когда надо кудато передать кучу переменных скопом. Когда вы спрашиваете «может из структур залудить как в том ООП фреймворке» вам дают советы как. Советы правильные. Ну теперь залудите чтонибудь вроде QString/GString и быстро поймете что stdlib рулит.

На C чем проще тем лучше. Если можно избежать longjump'а, указателя на функцию, malloc'а, пойнтерной арифметики, структуры - избегать.

imatveev13 ★
( 21.10.10 23:26:24 MSD )
Ответ на: комментарий от Sorcerer 21.10.10 20:37:25 MSD

Надо делать примерно так, как предложил arsi

Ну так это вообще простая репа - подумаешь, использовать сахар для инициализации структур, что-то вроде

struct Soo < int a; int b; >; int main () < . struct Soo foo = < .a = 1, .b = 2, >; . > 

только поля - указатели на функции, обычная пачка указателей. Конечно помогает абстрагироваться от байтикоф, но дальше что? Интерфейс это несколько более общая вещь (да, если говорить о Си - то чем является интерфейс в C++ или Java - несколько параллельно).

quasimoto ★★★★
( 21.10.10 23:34:19 MSD )

Вклинюсь в разговор.

Нашёл вот такую идею по реализации ООП на чистом Си (автор использовал там оператор . но его без проблем можно заменить сишным, в данном случае это непринципиально, важна идея).

Создаём описание структур-«классов», создаём внутри них ссылки на функции, которые будут «методами». «Методы» принимают в качестве аргумента ссылку на экземпляр класса-«объекта», с которым производится действие.

Далее делаем полиморфный «метод», выполняющий разные операции в зависимости от того, ссылку на «объект» какого типа он получил. А определяет он тип объекта следующим образом: сперва приводит объект по полученной ссылке к типу int, дальше смотрит что находится в int'е, в зависимости от этого снова приводит ссылку, уже к типу «объект_1», «объект_2» или «объект_N» и выполняет соответствующее действие.

Фишка в том, что каждая структура-«класс» первым членом обязательно должна содержать int, в котором записано уникальное значение, и по которому будет происходить преобразование ссылки, которая передаётся полиморфному «методу».

Внимание, вопрос - это будет работать? Т.е. если сделать int первым членом структуры, преобразование ссылки на структуру к типу int действительно всегда будет давать ссылку на этот член?

Внимание, вопрос 2: а если сделать первым членом указатель на функцию, присваивать этому члену адреса требуемых реализаций полиморфного «метода», после чего приводить ссылку на структуру к типу «указатель на функцию» и выполнять?

typedef void (*polymorphic)(*some_object p); void method(*some_object p) < polymorphic a = (polymorphic)p; (a)(p); // calling method at specific address >

Интерфейсы

В статье Абстрактные классы, методы и свойства был рассмотрен пример наследования от абстрактного класса классов-потомков и использования абстрактных методов, как шаблонов для реализации их в классах-потомках. Было отмечено, что другой вариант реализации — использование интерфейсов.
Они позволяют обойтись без абстрактных классов, но позволяют контролировать работу методов родственных классов.
Рассмотрим тот же пример — Правильный многоугольник. Разобрав его, вам станут понятны интерфейсы и их назначение в программировании в среде .Net Framework.

Пример использования интерфейса

В пространство ИнтерфейсФигур добавим новый элемент — интерфейс IFigureInfo. Это можно сделать через Меню Проект/Добавить новый элемент/Интерфейс или Ctrl+Shift+A/Интерфейс (тогда добавится файл Interface1.cs) со следующим содержанием или же вставить это объявление в текст основного кода приложения (см. ниже).

namespace ИнтерфейсФигур < interface IFigureInfo < double area(); double perimeter(); >>

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

Создадим три независимых класса: Circle, Square, Triangle. У каждого из этих классов есть свой набор полей и методов, связанных с их особенностями, в том числе — характерный размер — Length. Для круга это, например, его радиус, для квадрата и равностороннего треугольника — длина его стороны. Нам в каждый из классов необходимо гарантированно добавить методы, которые будут находить площадь (area) и периметр (perimeter) этих фигур.
Вот тут то и могут пригодиться интерфейсы!
Для этого поле имени класса через двоеточие укажем имя интерфейса. Сделаем это и для других классов Square и Triangle.

class Circle : IFigureInfo

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

Ошибка «ИнтерфейсФигур.Square» не содержит определения для «area» и не был найден метод расширения «area», принимающий тип «ИнтерфейсФигур.Square» в качестве первого аргумента.

В класс Program добавим статический метод InfoFigure для контроля работы методов класса. Тогда код файла Program.cs будет следующим:

using System; namespace ИнтерфейсФигур < interface IFigureInfo < double area(); double perimeter(); >class Circle : IFigureInfo < double Length; // радиус круга public Circle(double len) < Length = len; >public double area() < return Math.PI * Length * Length; >public double perimeter() < return 2 * Math.PI * Length; >> class Square : IFigureInfo < double Length; // сторона квадрата public Square(double len) < Length = len; >public double area() < return Length * Length; >public double perimeter() < return 4 * Length; >> class Triangle : IFigureInfo < double Length; // сторона треугольника public Triangle(double len) < Length = len; >public double area() < return Length * Length * Math.Sqrt(3) / 4; >public double perimeter() < return 3 * Length; >> class Program < static public void InfoFigure(string fig, double s, double p) < Console.WriteLine("площадь = , периметр = ",fig, s, p); > static void Main(string[] args) < Console.Write("Характерный размер фигуры = "); double len = Convert.ToDouble(Console.ReadLine()); Circle c = new Circle(len); InfoFigure("Круг: ", c.area(), c.perimeter()); Square q = new Square(len); InfoFigure("Квадрат: ", q.area(), q.perimeter()); Triangle t = new Triangle(len); InfoFigure("Треугольник: ", t.area(), t.perimeter()); Console.ReadKey(); >> >

Результат совпадает с ранее рассмотренным примером(проверьте!).

Теперь немного теории.

Зачем нужны интерфейсы?

В языке С++ есть возможность множественного наследования. Разработчики C# решили отказаться от этого и придумали интерфейсы. Получается, что класс не может быть унаследован от нескольких классов, но при этом он может унаследовать несколько интерфейсов.

Часто бывает необходимо реализовать несколько классов, при этом у них будут одинаковые методы (по названию!), но они по-разному должны быть реализованы.

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

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

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

Свойства интерфейсов

Интерфейс (interface) представляет собой не более чем просто именованный набор абстрактных членов. Абстрактные методы являются чистым протоколом, поскольку не имеют никакой стандартной реализации. Конкретные члены, определяемые интерфейсом, зависят от того, какое поведение моделируется с его помощью.

Интерфейс выражает поведение, которое данный класс или структура может избрать для поддержки. Более того, каждый класс (или структура) может поддерживать столько интерфейсов, сколько необходимо, и, следовательно, тем самым поддерживать множество поведений.

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

Для реализации интерфейса в классе должны быть предоставлены тела (т.е. конкретные реализации) методов, описанных в этом интерфейсе. Каждому классу предоставляется полная свобода для определения деталей своей собственной реализации интерфейса. Следовательно, один и тот же интерфейс может быть реализован в двух классах по-разному. Тем не менее, в каждом из них должен поддерживаться один и тот же набор методов данного интерфейса. А в том коде, где известен такой интерфейс, могут использоваться объекты любого из этих двух классов, поскольку интерфейс для всех этих объектов остается одинаковым.

Благодаря поддержке интерфейсов в C# может быть в полной мере реализован главный принцип полиморфизма: один интерфейс — множество методов.

Объявление интерфейсов

Интерфейсы объявляются с помощью ключевого слова interface. Ниже приведена упрощенная форма объявления интерфейса:

interface имя < возвращаемый_тип имя_метода_1 (список_параметров); возвращаемый_тип имя_метода_2 (список_параметров); // . возвращаемый_тип имя_метода_N (список_параметров); >

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

Как пояснялось выше, в интерфейсе не может быть никакой реализации. Поэтому все методы интерфейса должны быть реализованы в каждом классе, включающем в себя этот интерфейс. В самом же интерфейсе методы неявно считаются открытыми, поэтому доступ (public) к ним не нужно указывать явно.

Помимо методов, в интерфейсах можно также указывать свойства, индексаторы и события. Интерфейсы не могут содержать член-данные (кроме событий?). В них нельзя также определить конструкторы, деструкторы или операторные методы. Кроме того, ни один из членов интерфейса не может быть объявлен как static.

Реализация интерфейсов

Как только интерфейс будет определен, он может быть реализован в одном или нескольких классах. Для реализации интерфейса достаточно указать его имя после имени класса, аналогично базовому классу. Ниже приведена общая форма реализации интерфейса в классе:

class имя_класса : имя_интерфейса < // тело класса >

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

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

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

Методы, реализующие интерфейс, должны быть объявлены как public. Дело в том, что в самом интерфейсе эти методы неявно подразумеваются как открытые, поэтому их реализация также должна быть открытой.

Кроме того, возвращаемый тип и сигнатура реализуемого метода должны точно соответствовать возвращаемому типу и сигнатуре, указанным в определении интерфейса.

Наследование интерфейсов

Интерфейсы, как и классы, могут наследоваться:

interface IAction < void Move(); >interface IRunAction : IAction < void Run(); >class BaseAction : IRunAction < public void Move() < Console.WriteLine("Move"); >public void Run() < Console.WriteLine("Run"); >>

При применении этого интерфейса класс BaseAction должен будет реализовать как методы и свойства интерфейса IRunAction, так и методы и свойства базового интерфейса IAction.

Однако в отличие от классов мы не можем применять к интерфейсам модификатор sealed (а также к методам интерфейсов), чтобы запретить наследование интерфейсов.

Также мы не можем применять к интерфейсам модификатор abstract, поскольку интерфейс фактически и так предоставляет абстрактный функционал, который должен быть реализован в классе.

Однако методы интерфейсов могут использовать ключевое слово new для сокрытия методов из базового интерфейса:

class RunAction : IRunAction < public void Move() < Console.WriteLine("I am running"); >> interface IAction < void Move(); >interface IRunAction : IAction < new void Move(); >

Здесь метод Move из IRunAction скрывает метод Move из базового интерфейса IAction. Большого смысла в этом нет, так как в данном случае нечего скрывать, то тем не менее мы так можем делать. А класс RunAction реализует метод Move сразу для обоих интерфейсов.

Модификаторы доступа интерфейсов

Как и классы, интерфейсы по умолчанию имеют уровень доступа internal, то есть такой интерфейс доступен только в рамках текущего проекта. Но с помощью модификатора public мы можем сделать интерфейс общедоступным:

public interface IAction

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

public interface IAction < void Move(); >internal interface IRunAction : IAction

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

internal interface IAction < void Move(); >public interface IRunAction : IAction // ошибка IRunAction может быть только internal

Далее в примерах применение интерфейсов будет рассмотрено более подробно.

NEW: Наш Чат, в котором вы можете обсудить любые вопросы, идеи, поделиться опытом или связаться с администраторами.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *