Поиск…


Внедрение интерфейса

Интерфейс используется для обеспечения наличия метода в любом классе, который «реализует» его. Интерфейс определяется interface ключевого слова, и класс может «реализовать» его, добавив : InterfaceName после имени класса. Класс может реализовывать несколько интерфейсов, разделяя каждый интерфейс запятой.
: InterfaceName, ISecondInterface

public interface INoiseMaker
{
    string MakeNoise();
}

public class Cat : INoiseMaker
{
    public string MakeNoise()
    {
        return "Nyan";
    }
}

public class Dog : INoiseMaker
{
    public string MakeNoise()
    {
        return "Woof";
    }
}

Поскольку они реализуют INoiseMaker , как cat и dog должны включать метод string MakeNoise() и не смогут скомпилироваться без него.

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

public interface IAnimal 
{
    string Name { get; set; }
}

public interface INoiseMaker
{
    string MakeNoise();
}

public class Cat : IAnimal, INoiseMaker
{
    public Cat() 
    {
        Name = "Cat";
    }

    public string Name { get; set; }

    public string MakeNoise()
    {
        return "Nyan";
    }
}

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

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

interface IChauffeur 
{
    string Drive();
}

interface IGolfPlayer
{
    string Drive();
}

class GolfingChauffeur : IChauffeur, IGolfPlayer 
{
    public string Drive()
    {
        return "Vroom!";
    }

    string IGolfPlayer.Drive()
    {
        return "Took a swing...";
    }
}


GolfingChauffeur obj = new GolfingChauffeur();
IChauffeur chauffeur = obj;
IGolfPlayer golfer = obj;

Console.WriteLine(obj.Drive()); // Vroom!
Console.WriteLine(chauffeur.Drive()); // Vroom!
Console.WriteLine(golfer.Drive()); // Took a swing...

Реализация не может быть вызвана нигде, кроме как с помощью интерфейса:

public class Golfer : IGolfPlayer
{
    string IGolfPlayer.Drive()
    {
        return "Swinging hard...";
    }
    public void Swing()
    {
        Drive(); // Compiler error: No such method
    }
}

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

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

public class ProGolfer : IGolfPlayer
{
    string IGolfPlayer.Swear() // Error
    {
        return "The ball is in the pit";
    }
}

Аналогичным образом, использование явной реализации интерфейса без объявления этого интерфейса в классе также вызывает ошибку.

Подсказка:

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

Замечания:

Программисты ожидают, что контракт будет одинаковым независимо от контекста типа и явной реализации не должен выставлять другое поведение при вызове. Поэтому, в отличие от приведенного выше примера, IGolfPlayer.Drive и Drive должны делать то же самое, когда это возможно.

Почему мы используем интерфейсы

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

Предположим, что мы определяем интерфейс IShape для представления различных типов фигур, мы ожидаем, что у формы будет область, поэтому мы определим метод, чтобы заставить реализации интерфейса вернуть свою область:

public interface IShape
{
    double ComputeArea();
}

Давайте будем иметь следующие две формы: Rectangle и Circle

public class Rectangle : IShape
{
    private double length;
    private double width;

    public Rectangle(double length, double width)
    {
        this.length = length;
        this.width = width;
    }

    public double ComputeArea()
    {
        return length * width;
    }
}

public class Circle : IShape
{
    private double radius;

    public Circle(double radius)
    {
        this.radius = radius;
    }

    public double ComputeArea()
    {
        return Math.Pow(radius, 2.0) * Math.PI;
    }
}

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

private static void Main(string[] args)
{
    var shapes = new List<IShape>() { new Rectangle(5, 10), new Circle(5) };
    ComputeArea(shapes);

    Console.ReadKey();
}

private static void ComputeArea(IEnumerable<IShape> shapes) 
{
    foreach (shape in shapes)
    {
        Console.WriteLine("Area: {0:N}, shape.ComputeArea());
    }
}

// Output:
// Area : 50.00
// Area : 78.54

Основы интерфейса

Функция интерфейса, известная как «контракт» функциональности. Это означает, что он объявляет свойства и методы, но не реализует их.

Так что в отличие от классов Интерфейсы:

  • Невозможно создать экземпляр
  • Не может быть никакой функциональности
  • Может содержать только методы * (Свойства и события являются методами внутри)
  • Наследование интерфейса называется «Реализация»,
  • Вы можете наследовать от 1 класса, но вы можете «реализовать» несколько интерфейсов
public interface ICanDoThis{
    void TheThingICanDo();
    int SomeValueProperty { get; set; }
}

Что следует заметить:

  • Префикс «I» - это соглашение об именах, используемое для интерфейсов.
  • Тело функции заменяется точкой с запятой «;».
  • Свойства также разрешены, потому что внутренне они также являются методами
public class MyClass : ICanDoThis {
    public void TheThingICanDo(){
        // do the thing
    }

    public int SomeValueProperty { get; set; }
    public int SomeValueNotImplemtingAnything { get; set; }
}

,

ICanDoThis obj = new MyClass();

// ok
obj.TheThingICanDo();

// ok
obj.SomeValueProperty = 5;

// Error, this member doesn't exist in the interface
obj.SomeValueNotImplemtingAnything = 5;

// in order to access the property in the class you must "down cast" it
((MyClass)obj).SomeValueNotImplemtingAnything = 5; // ok

Это особенно полезно, когда вы работаете с инфраструктурами пользовательского интерфейса, такими как WinForms или WPF, потому что для создания пользовательского элемента управления необходимо унаследовать от базового класса, и вы теряете способность создавать абстракции по различным типам управления. Пример? Прибытие:

public class MyTextBlock : TextBlock {
    public void SetText(string str){
        this.Text = str;
    }
}

public class MyButton : Button {
    public void SetText(string str){
        this.Content = str;
    }
}

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

public interface ITextControl{
    void SetText(string str);
}

public class MyTextBlock : TextBlock, ITextControl {
    public void SetText(string str){
        this.Text = str;
    }
}

public class MyButton : Button, ITextControl {
    public void SetText(string str){
        this.Content = str;
    }

    public int Clicks { get; set; }
}

Теперь MyButton и MyTextBlock взаимозаменяемы.

var controls = new List<ITextControls>{
    new MyTextBlock(),
    new MyButton()
};

foreach(var ctrl in controls){
    ctrl.SetText("This text will be applied to both controls despite them being different");


    // Compiler Error, no such member in interface
    ctrl.Clicks = 0;

    // Runtime Error because 1 class is in fact not a button which makes this cast invalid
    ((MyButton)ctrl).Clicks = 0;


    /* the solution is to check the type first.
    This is usually considered bad practice since
    it's a symptom of poor abstraction */
    var button = ctrl as MyButton;
    if(button != null)
        button.Clicks = 0; // no errors

   
}

«Скрытие» членов с явной реализацией

Разве вы не ненавидите его, когда интерфейсы загрязняют вас классом со слишком многими членами, которых вам даже не волнует? Ну, я получил решение! Явные реализации

public interface IMessageService {
    void OnMessageRecieve();
    void SendMessage();
    string Result { get; set; }
    int Encoding { get; set; }
    // yadda yadda
}

Обычно вы реализуете класс следующим образом.

public class MyObjectWithMessages : IMessageService {
     public void OnMessageRecieve(){

     }

     public void SendMessage(){

     }

     public string Result { get; set; }
     public int Encoding { get; set; }
}

Каждый участник является публичным.

var obj = new MyObjectWithMessages();

// why would i want to call this function?
obj.OnMessageRecieve();

Ответ: Не знаю. Таким образом, ни один из них не должен быть объявлен открытым, но просто объявляя, что члены как частные, заставят компилятор выбросить ошибку

Решение заключается в использовании явной реализации:

public class MyObjectWithMessages : IMessageService{
    void IMessageService.OnMessageRecieve() {
        
    }

    void IMessageService.SendMessage() {
        
    }

    string IMessageService.Result { get; set; }
    int IMessageService.Encoding { get; set; }
}

Итак, теперь вы внедрили участников по мере необходимости, и они не будут публиковать публичных участников.

var obj = new MyObjectWithMessages();

/* error member does not exist on type MyObjectWithMessages. 
 * We've succesfully made it "private" */
obj.OnMessageRecieve();

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

((IMessageService)obj).OnMessageRecieve();

IComparable как пример реализации интерфейса

Интерфейсы могут казаться абстрактными, пока вы не увидите их на практике. IComparable и IComparable<T> - отличные примеры того, почему интерфейсы могут быть полезны для нас.

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

public class Item {
    
    public string name; // though public variables are generally bad practice,
    public int idNumber; // to keep this example simple we will use them instead
    public decimal price; // of a property.

    // body omitted for brevity        

}

У нас есть наш Item хранящийся внутри List<Item> , и в нашей программе где-то мы хотим отсортировать наш список по ID-номеру от наименьшего до самого большого. Вместо написания нашего собственного алгоритма сортировки мы можем вместо этого использовать метод Sort() который уже имеет List<T> . Однако, поскольку наш класс Item находится прямо сейчас, для List<T> нет способа понять, какой порядок сортировать список. Здесь находится интерфейс IComparable .

Чтобы правильно реализовать метод CompareTo , CompareTo должен возвращать положительное число, если параметр «меньше» текущего, ноль, если они равны, и отрицательное число, если параметр «больше».

Item apple = new Item();
apple.idNumber = 15;
Item banana = new Item();
banana.idNumber = 4;
Item cow = new Item();
cow.idNumber = 15;
Item diamond = new Item();
diamond.idNumber = 18;

Console.WriteLine(apple.CompareTo(banana)); // 11
Console.WriteLine(apple.CompareTo(cow)); // 0
Console.WriteLine(apple.CompareTo(diamond)); // -3

Вот на примере Item реализации «сек интерфейса:

public class Item : IComparable<Item> {
    
    private string name;
    private int idNumber;
    private decimal price;

    public int CompareTo(Item otherItem) {

        return (this.idNumber - otherItem.idNumber);

    }

    // rest of code omitted for brevity    

}

На поверхностном уровне метод CompareTo в нашем элементе просто возвращает разницу в их идентификационных номерах, но что делает это на практике?

Теперь, когда мы вызываем Sort() в объекте List<Item> , List будет автоматически вызывать метод CompareTo Item когда ему нужно определить, в какой порядок помещать объекты. Кроме того, помимо List<T> , любые другие объекты которые нуждаются в способности сравнивать два объекта, будут работать с Item потому что мы определили способность двух разных Item s сравнивать друг с другом.



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow