Главная | Обратная связь | Поможем написать вашу работу!
МегаЛекции

Определение и реализация интерфейсов




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

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

Для начала потребуется определить интерфейс IBankAccount:

namespace Wrox.ProCSharp

{

public interface IBankAccount

{

void PayIn(decimal amount);

bool Withdraw(decimal amount);

decimal Balance

{

get;

}

}

)

Обратите внимание на имя интерфейса - IBankAccount. Существует соглашение, что имя интерфейса традиционно начинается с буквы I, чтобы сразу было понятно, что это интерфейс.

В рзделе 6.2 упоминалось, что в большинстве случаев руководство по.NET отвергает так называемую венгерскую нотацию, согласно которой имена предваряются бук­вой, указывающей на тип определяемого объекта. Интерфейсы — одно из исключе­ний из этого правила, в котором венгерская нотация как раз рекомендуется.

Идея заключается в том, что теперь имеется возможность писать классы, представляю­щие банковские счета. Эти классы не должны быть как-то связанными друг с другом; они могут быть полностью различными. Однако все они декларируют свое представление бан­ковских счетов тем, что реализуют интерфейс IBankAccount.

Начнем с первого класса, описывающего сберегательный счет в Королевском Банке Венеры:

namespace Wrox.ProCSharp.VenusBank

{

public class SaverAccount: IBankAccount

{

private decimal balance;

public void Payln(decimal amount)

{

balance += amount;

}

public bool Withdraw(decimal amount)

{

if (balance >= amount)

{

balance -= amount;

return true;

}

 

Console.WriteLine("Попытка перевода денег не удалась.");

return false;

}

public decimal Balance

{

get

{

return balance;

}

}

public override string ToStringO

{

return String.Format(

"Сберегательный Банк Венеры: Баланс = (0,6:С}", balance);

}

}

}

Достаточно очевидно, что делает реализация этого класса. Вы создаете приватное поле balanceи изменяете сумму остатка, указанную в нем, при съеме и зачислении денег на счет. Если предпринимается попытка снять больше денег, чем осталось на счету, выдается сообщение об ошибке. Обратите внимание, что для простоты кода здесь не реализуются дополнительные свойства, такие как имя владельца счета. В реальной жизни эта информа­ция совершенно необходима, но в данный пример это внесло бы излишнюю сложность.

Единственной интересной строкой в этом коде является объявление класса:

public class SaverAccount: IBankAccount

Она объявляет, что SaverAccountнаследуется от одного интерфейса, IBankAccount, и ’ никакого другого базового класса не указано (что, конечно же, означает, что SaverAccountнаследуется от System.Object ). Кстати говоря, наследование интерфейсов совершенно независимо от наследования классов.

То, что SaverAccountнаследуется от IBankAccount, означает, что у SaverAccountесть все члены IBankAccount. Но поскольку сам интерфейс не реализует ни одного из сво­их методов, SaverAccountдолжен предоставить для них собственную реализацию. Если реализация любого из них опущена, компилятор это заметит и выдаст соответствующее уведомление. Вспомните также о том, что интерфейс просто указывает на присутствие своих членов. Решение о том, объявлять их virtualили abstract, возложено на класс (хотя абстрактные функции, конечно же, допускаются только в абстрактных классах). Что касается конкретного примера, то здесь нет никаких причин объявлять любую из функций интерфейса виртуальной.

Чтобы проиллюстрировать, как различные классы могут реализовать один и тот же интерфейс, предположим, что Планетарный Банк Юпитера также реализует собственный класс, представляющий банковские счета - GoldAccount:

namespace Wrox.ProCSharp.JupiterBank

{

public class GoldAccount: IBankAccount

{

// и т.д.

}

}

Мы не будем здесь рассматривать детали класса GoldAccount; в коде примера он бу­дет практически идентичен реализации SaverAccount. Подчеркнем, что GoldAccountне имеет никакой связи с SaverAccountкроме того, что оба они реализуют один и тот же интерфейс.

Теперь, имея готовые классы, их можно протестировать. Первым делом, понадобится несколько операторов using:

 

using System;

using Wrox.ProCSharp;

using Wrox.ProCSharp.VenusBank;

using Wrox.ProCSharp.JupiterBank;

Теперь нужен метод Main():

namespace Wrox.ProCSharp

{

class MainEntryPoint

{

static void Main()

{

IBankAccount venusAccount = new SaverAccount();

IBankAccount jupiterAccount = new GoldAccount();

venusAccount.Payln(200);

venusAccount.Withdraw(100);

Console.WriteLine(venusAccount.ToString());

jupiterAccount.PayIn(500);

jupiterAccount.Withdraw(600);

jupiterAccount.Withdraw(100);

Console.WriteLine(jupiterAccount.ToString());

}

}

}

Этот код (в примерах кода для данной главы он находится в файле BankAccounts.cs) сгенерирует следующий вывод:

С:> BankAccounts

Сберегательный Банк Венеры: Баланс = £100.00

Попытка перевода денег не удалась.

Планетарный Банк Юпитера: Баланс = £400.00

Главный момент, который следует здесь отметить - способ объявления обеих перемен­ных как ссылок на IBankAccount. Это значит, что они могут указывать на любой экземп­ляр любого класса, реализующего интерфейс. Это также означает, что через эти ссылки можно вызывать только те методы, которые являются частью интерфейса. Если понадо­бится вызвать любые методы, реализованные классом, но не являющиеся частью интер­фейса, то придется выполнить приведение ссылки к соответствующему типу. В примере кода вызывается метод ToString() (не объявленный в IBankAccount)без какого-либо явного приведения, просто потому, что ToString()- это метод System.Object, поэтому компилятор C# знает о том, что он поддерживается любым классом (иначе говоря, приве­дение любого интерфейса к System. Object осуществляется‘неявно). Синтаксис приведе­ния типов описан в разделе 6.7.

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

IBankAccount!] accounts = new IBankAccount[2];

accounts[0] = new SaverAccount();

accounts [1] new GoldAccount();

Однако если попытаться сделать что-то вроде такого:

accounts[1] = new SomeOtherClass(); //SomeOtherClass не реализует

// IBankAccount: НЕВЕРНО!!

будет получена следующая ошибка компиляции:

Cannot implicitly convert type 'Wrox.ProCSharp.SomeOtherClass' to 'Wrox.

ProCSharp.IBankAccount'

Неявное преобразование типа 'Wrox.ProCSharp.SomeOtherClass' в 'Wrox.ProCSharp.

IBankAccount' невозможно

Производные интерфейсы

Интерфейсы могут быть унаследованы друг от друга - точно так же, как классы. Эта кон­цепция иллюстрируется ниже определением нового интерфейса ITransferBankAccount, который обладает теми же возможностями, что и IBankAccount, но также определяет метод для перевода денег непосредственно на другой счет:

namespace Wrox.ProCSharp

{

public interface ITransferBankAccount: IBankAccount

{

bool TransferTo(IBankAccount destination, decimal amount);

}

)

Поскольку ITransferBankAccountнаследуется от IBankAccount, наряду с собствен­ными методами он получает все методы-члены IBankAccount. Это значит, что любой класс, реализующий (унаследованный от) ITransferBankAccount, должен реализовать все методы IBankAccountнаряду с новым методом TransferTo(), определенным в ITransferBankAccount. Отсутствие реализации любого из этих методов приведет к ошиб­ке компиляции.

Обратите внимание на то, что метод TransferTo() использует ссылку на интерфейс IBankAccountдля указания целевого счета. Это иллюстрирует полезность интерфейсов. При реализации и последующем вызове метода вам не обязательно знать что-либо о типе объекта, которому переводятся деньги. Все, что необходимо знать - это то, что объект реализует интерфейс IBankAccount.

Чтобы проиллюстрировать применение ITransferBankAccount, предположим, что Планетарный Банк Юпитера также предлагает текущий счет (Current Account). Большая часть реализации класса CurrentAccountидентична реализациям SaverAccountи GoldAccount(опять же, это только для простоты примера - в реальности все далеко не так). Поэтому в следующем фрагменте выделена отличающаяся часть:

public class CurrentAccount: ITransferBankAccount

{

private decimal balance; public void Payln(decimal amount)

{

balance += amount;

}

public bool Withdraw(decimal amount)

{

if (balance >= amount)

{

balance -= amount;

return true;

}

Console.WriteLine("Попытка перевода денег не удалась.");

return false;

}

public decimal Balance

{

get

{

return balance;

}

}

public bool TransferTo(IBankAccount destination, decimal amount)

{

bool result;

result = Withdraw (amount);

if (result)

{

destination.Payln(amount);

}

return result;

}

public override string ToString()

{

return String.Format(

"Текущий счет в Банке Юпитера: Баланс = {0,6:С}", balance);

}

}

Класс можно протестировать с помощью следующего кода:

static void Main()

{

IBankAccount venusAccount = new SaverAccount ();

ITransferBankAccount jupiterAccount = new CurrentAccount();

venusAccount.Payln(200); jupiterAccount.Payln(500);

jupiterAccount.TransferTo(venusAccount, 100);

Console.WriteLine(venusAccount.ToString0);

Console.WriteLine(jupiterAccount.ToString());

)

Этот код (CurrentAccount.cs) генерирует следующий вывод, по которому можно убе­диться в том, что перевод правильной денежной суммы выполнен:

С:> CurrentAccount

Сберегательный Банк Венеры: Баланс = £300.00

Текущий счет в Банке Юпитера: Баланс = £400.00

Итоги

В данном разделе было показано, как кодировать наследование в С#. Вы увидели, что C# предлагает богатую поддержку наследования множества интерфейсов и одной реа­лизации. Также вы узнали о том, что в C# имеется множество полезных синтаксических конструкций, предназначенных для обеспечения большей устойчивости кода. Среди этих конструкций ключевое слово override, указывающее, когда функция должна переопреде­лять базовую функцию, ключевое слово new, указывающее, что функция скрывает базовую функцию, а также жесткие правила инициализации конструкторов, предназначенные для обеспечения их устойчивого взаимодействия.

 

 

Поделиться:





Воспользуйтесь поиском по сайту:



©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...