Szukaj…


Implementacja interfejsu

Interfejs służy do wymuszenia obecności metody w dowolnej klasie, która ją „implementuje”. Interfejs jest definiowany za pomocą interface słowa kluczowego interface a klasa może go „zaimplementować”, dodając : InterfaceName po nazwie klasy. Klasa może implementować wiele interfejsów, oddzielając każdy interfejs przecinkiem.
: 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";
    }
}

Ponieważ implementują INoiseMaker , zarówno cat , jak i dog muszą zawierać metodę string MakeNoise() i bez niej kompilacja się nie powiedzie.

Implementowanie wielu interfejsów

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";
    }
}

Jawna implementacja interfejsu

Jawna implementacja interfejsu jest konieczna, gdy implementujesz wiele interfejsów definiujących wspólną metodę, ale wymagane są różne implementacje w zależności od tego, który interfejs jest używany do wywołania metody (pamiętaj, że nie potrzebujesz jawnych implementacji, jeśli wiele interfejsów korzysta z tej samej metody i możliwe jest wspólne wdrożenie).

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...

Implementacji nie można wywołać z dowolnego miejsca, z wyjątkiem interfejsu:

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

Z tego powodu może być korzystne umieszczenie złożonego kodu implementacyjnego jawnie zaimplementowanego interfejsu w osobnej, prywatnej metodzie.

Implementacji jawnego interfejsu można oczywiście używać tylko w przypadku metod, które faktycznie istnieją dla tego interfejsu:

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

Podobnie użycie jawnej implementacji interfejsu bez deklarowania tego interfejsu w klasie również powoduje błąd.

Wskazówka:

W celu uniknięcia martwego kodu można również jawnie zaimplementować interfejsy. Gdy metoda nie jest już potrzebna i zostanie usunięta z interfejsu, kompilator będzie narzekał na każdą wciąż istniejącą implementację.

Uwaga:

Programiści oczekują, że umowa będzie taka sama, niezależnie od kontekstu typu, a jawna implementacja nie powinna ujawniać różnych zachowań po wywołaniu. W przeciwieństwie do powyższego przykładu, IGolfPlayer.Drive i Drive powinny robić to samo, jeśli to możliwe.

Dlaczego korzystamy z interfejsów

Interfejs to definicja umowy między użytkownikiem interfejsu a klasą, która go implementuje. Jednym ze sposobów myślenia o interfejsie jest deklaracja, że obiekt może wykonywać określone funkcje.

Powiedzmy, że definiujemy interfejs IShape do reprezentowania różnych typów kształtów, oczekujemy, że kształt będzie miał obszar, więc zdefiniujemy metodę wymuszającą na implementacjach interfejsu zwracanie ich obszaru:

public interface IShape
{
    double ComputeArea();
}

Załóżmy, że mamy dwa następujące kształty: Rectangle i 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;
    }
}

Każdy z nich ma własną definicję swojego obszaru, ale oba są kształtami. Logiczne jest więc postrzeganie ich jako IShape w naszym programie:

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

Podstawy interfejsu

Funkcja interfejsu znana jako „umowa” funkcjonalności. Oznacza to, że deklaruje właściwości i metody, ale ich nie implementuje.

W przeciwieństwie do klas Interfejsy:

  • Nie można utworzyć wystąpienia
  • Nie może mieć żadnej funkcjonalności
  • Może zawierać tylko metody * (właściwości i zdarzenia są metodami wewnętrznymi)
  • Dziedziczenie interfejsu nazywa się „wdrażaniem”
  • Możesz dziedziczyć po 1 klasie, ale możesz „wdrożyć” wiele interfejsów
public interface ICanDoThis{
    void TheThingICanDo();
    int SomeValueProperty { get; set; }
}

Rzeczy do zauważenia:

  • Prefiks „I” to konwencja nazewnictwa używana w interfejsach.
  • Ciało funkcji jest zastąpione średnikiem „;”.
  • Właściwości są również dozwolone, ponieważ wewnętrznie są również metodami
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

Jest to szczególnie przydatne, gdy pracujesz z frameworkami interfejsu użytkownika, takimi jak WinForms lub WPF, ponieważ obowiązkowe jest dziedziczenie z klasy podstawowej w celu stworzenia kontroli użytkownika i tracisz możliwość tworzenia abstrakcji dla różnych typów kontroli. Przykład? Nadchodzi:

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

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

Proponowany problem polega na tym, że oba zawierają pewne pojęcia „Tekst”, ale nazwy właściwości różnią się. I nie można utworzyć, stworzyć abstrakcyjną klasę podstawową, ponieważ mają one obowiązkowe dziedzictwo dla 2 różnych klas. Interfejs może to złagodzić

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; }
}

Teraz MyButton i MyTextBlock są wymienne.

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

   
}

„Ukrywanie” członków za pomocą jawnej implementacji

Czy nie nienawidzisz tego, gdy interfejsy zanieczyszczają twoją klasę zbyt dużą liczbą członków, na których nawet ci nie zależy? Cóż, mam rozwiązanie! Jawne implementacje

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

Zwykle zaimplementowałbyś taką klasę.

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

     }

     public void SendMessage(){

     }

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

Każdy członek jest publiczny.

var obj = new MyObjectWithMessages();

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

Odpowiedź: ja nie. Dlatego też nie należy go ogłaszać publicznie, ale po prostu zadeklarowanie członków jako prywatnych spowoduje, że kompilator zgłosi błąd

Rozwiązaniem jest użycie jawnej implementacji:

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

    void IMessageService.SendMessage() {
        
    }

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

Więc teraz zaimplementowałeś członków zgodnie z wymaganiami i nie będą oni ujawniać żadnych członków jako publicznie.

var obj = new MyObjectWithMessages();

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

Jeśli naprawdę chcesz uzyskać dostęp do członka, mimo że jest to wyraźnie implementowane, wystarczy rzutować obiekt na interfejs i gotowe.

((IMessageService)obj).OnMessageRecieve();

IComparable jako przykład implementacji interfejsu

Interfejsy mogą wydawać się abstrakcyjne, dopóki nie pojawią się w praktyce. IComparable i IComparable<T> są świetnymi przykładami, dlaczego interfejsy mogą być dla nas pomocne.

Powiedzmy, że w programie sklepu internetowego mamy wiele produktów, które możesz kupić. Każdy element ma nazwę, numer identyfikacyjny i cenę.

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        

}

Mamy nasze Item przechowywane na List<Item> , a gdzieś w naszym programie chcemy posortować naszą listę według numeru identyfikacyjnego od najmniejszej do największej. Zamiast pisać własny algorytm sortowania, możemy zamiast tego użyć metody Sort() , którą ma już List<T> . Ponieważ jednak nasza klasa Item jest obecnie, List<T> może zrozumieć, w jakiej kolejności sortuje listę. Tutaj pojawia się interfejs IComparable .

Aby poprawnie zaimplementować metodę CompareTo , CompareTo powinna zwrócić liczbę dodatnią, jeśli parametr jest „mniejszy niż”, zero, jeśli są one równe, a liczbę ujemną, jeśli parametr jest „większy niż”.

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

Oto przykładowa implementacja interfejsu 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    

}

Na poziomie powierzchni metoda CompareTo w naszym CompareTo po prostu zwraca różnicę w ich numerach identyfikacyjnych, ale co robi powyższe w praktyce?

Teraz, kiedy nazywamy Sort() na List<Item> obiektu, List automatycznie wywołać Item „s CompareTo metoda, gdy jest konieczne do ustalenia, co by umieszczać żadnych przedmiotów. Ponadto, oprócz List<T> , innych obiektów które wymagają zdolności porównania dwóch obiektów będą działać z Item ponieważ zdefiniowaliśmy możliwość porównywania dwóch różnych Item .



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow