Явные интерфейсы
Часть раздела Объектно-ориентированное программирование путешествия по C# на Coddy — урок 28 из 70.
Когда класс реализует несколько интерфейсов, имеющих члены с одинаковыми именами, вы сталкиваетесь с коллизией имен. Явная реализация интерфейса решает эту проблему, позволяя вам предоставить отдельные реализации для каждого интерфейса.
При явной реализации вы добавляете имя интерфейса в качестве префикса к имени члена и опускаете модификатор доступа:
public interface IAmericanPlug
{
void Connect();
}
public interface IEuropeanPlug
{
void Connect();
}
public class UniversalAdapter : IAmericanPlug, IEuropeanPlug
{
void IAmericanPlug.Connect()
{
Console.WriteLine("Connected to 120V");
}
void IEuropeanPlug.Connect()
{
Console.WriteLine("Connected to 220V");
}
}Явно реализованные члены могут быть доступны только через переменную типа интерфейса, а не через сам класс:
var adapter = new UniversalAdapter();
// adapter.Connect(); // Не скомпилируется!
IAmericanPlug american = adapter;
american.Connect(); // Подключено к 120В
IEuropeanPlug european = adapter;
european.Connect(); // Подключено к 220ВЭта техника также полезна, когда вы хотите скрыть члены интерфейса из публичного API класса. Если метод имеет смысл только при рассмотрении объекта как конкретного интерфейса, явная реализация позволяет сохранить чистоту публичного интерфейса вашего класса, при этом по-прежнему выполняя контракт.
Задание
ЛегкоДавайте создадим систему генерации отчетов, которая демонстрирует, как явная реализация интерфейса позволяет одному классу предоставлять различное поведение в зависимости от того, какой интерфейс используется. Вы создадите документ, который может быть отформатирован по-разному для отображения и для печати.
Вы организуете свой код в четырех файлах:
IDisplayable.cs: Определите интерфейс с именемIDisplayableв пространстве именReports. Этот интерфейс представляет контент, который может быть показан на экране. Он должен объявлять методRender(), который возвращает строку.IPrintable.cs: Определите интерфейс с именемIPrintableв пространстве именReports. Этот интерфейс представляет контент, который может быть отправлен на принтер. Он также должен объявлять методRender(), который возвращает строку — обратите внимание, что это создает конфликт имен сIDisplayable!Report.cs: Определите классReportв пространстве именReports, который реализует какIDisplayable, так иIPrintable. Отчет должен иметь свойствоTitle(строка) и свойствоContent(строка), а также конструктор, который устанавливает оба значения. Используйте явную реализацию интерфейса, чтобы обеспечить различное поведениеRender():- При рендеринге для отображения:
"[SCREEN] {Title}: {Content}" - При рендеринге для печати:
"[PRINT] {Title} | {Content}"
- При рендеринге для отображения:
Program.cs: В основном файле создайтеReport, используя входные значения. Поскольку к явным реализациям можно получить доступ только через переменные интерфейса, сохраните один и тот же отчет в переменнойIDisplayableи в переменнойIPrintable, затем вызовитеRender()для каждой из них, чтобы показать, как один и тот же объект выдает разные результаты в зависимости от того, какой интерфейс вы используете.
Вы получите два входных значения:
- Заголовок отчета
- Содержимое отчета
Выведите результат в следующем формате:
Display version:
{Render() via IDisplayable}
Print version:
{Render() via IPrintable}Например, если входными данными являются Sales Report и Q4 revenue increased by 15%, результат должен быть:
Display version:
[SCREEN] Sales Report: Q4 revenue increased by 15%
Print version:
[PRINT] Sales Report | Q4 revenue increased by 15%Обратите внимание, как один и тот же объект Report ведет себя по-разному в зависимости от того, какую ссылку на интерфейс вы используете для доступа к нему. В этом и заключается сила явной реализации интерфейса — разрешение конфликтов имен при обеспечении поведения, соответствующего контексту!
Шпаргалка
Когда класс реализует несколько интерфейсов с членами, имеющими одинаковые имена, вы можете использовать явную реализацию интерфейса, чтобы предоставить отдельные реализации для каждого интерфейса.
Чтобы явно реализовать член интерфейса, добавьте имя интерфейса в качестве префикса к имени члена и опустите модификатор доступа:
public interface IAmericanPlug
{
void Connect();
}
public interface IEuropeanPlug
{
void Connect();
}
public class UniversalAdapter : IAmericanPlug, IEuropeanPlug
{
void IAmericanPlug.Connect()
{
Console.WriteLine("Connected to 120V");
}
void IEuropeanPlug.Connect()
{
Console.WriteLine("Connected to 220V");
}
}К явно реализованным членам можно получить доступ только через переменную типа интерфейса, а не через сам класс:
var adapter = new UniversalAdapter();
// adapter.Connect(); // Не скомпилируется!
IAmericanPlug american = adapter;
american.Connect(); // Connected to 120V
IEuropeanPlug european = adapter;
european.Connect(); // Connected to 220VЭта техника полезна для:
- Разрешения конфликтов имен между членами интерфейса
- Скрытия членов интерфейса из публичного API класса, когда они имеют смысл только в контексте конкретного интерфейса
Попробуйте сами
using System;
using Reports;
class Program
{
public static void Main(string[] args)
{
// Чтение входных данных
string title = Console.ReadLine();
string content = Console.ReadLine();
// TODO: Создайте объект Report с заголовком и содержимым
// TODO: Сохраните отчет в переменной типа IDisplayable
// TODO: Сохраните тот же отчет в переменной типа IPrintable
// TODO: Выведите версию для отображения, вызвав Render() у IDisplayable
Console.WriteLine("Display version:");
// Выведите результат рендеринга здесь
// TODO: Выведите версию для печати, вызвав Render() у IPrintable
Console.WriteLine("Print version:");
// Выведите результат рендеринга здесь
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыПространства имен и директивыВведение в классы и объектыКлючевое слово 'this'Методы и параметрыПоля и свойстваКонструкторыИнициализаторы объектовИтоги — Простой калькулятор4Наследование
Основы синтаксиса наследования (:)Ключевое слово 'base'Ключевые слова Virtual и OverrideЗапечатанные классыБазовый класс 'object'Итоги — Иерархия сотрудников7Продвинутые возможности
Перегрузка операторовИндексаторы (this[])Переопределение ToString()Методы расширенияПовторение — Пользовательский список10Паттерны проектирования. Часть 1
Введение в паттерны проектированияПотокобезопасный SingletonПаттерн FactoryПаттерн Observer (События)Паттерн Strategy13Финальные испытания
Платформа для онлайн-обученияКомпонент игрового персонажаСервис аренды транспорта2Свойства и статические члены
Автоматически реализуемые свойстваСвойства только для чтения и записиСтатические поля и методыСтатические классыЧлены в виде выражений5Полиморфизм и интерфейсы
Полиморфизм: компиляция vs выполнениеИнтерфейс vs абстрактный классМножественные интерфейсыЯвные интерфейсыUpcasting и DowncastingИтоги: Калькулятор фигур8Продвинутые концепции ООП
Композиция вместо наследованияGenerics (классы и методы)Делегаты и событияАтрибуты и рефлексияIDisposable и оператор usingОсновы Dependency Injection11Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн Компоновщик3Архитектура классов
Экземплярные и статические данныеКлючевые слова 'readonly' и 'const'Опорные поляПовторение — Менеджер банковских счетов6Инкапсуляция
Модификаторы доступаСвойства для инкапсуляцииРеализация скрытия данныхПаттерны неизменяемостиИтоги — Записи студентов9Переменное количество аргументов
Ключевое слово 'params'Необязательные параметрыИменованные аргументыПерегрузка методов