Interfejs a klasa abstrakcyjna
Część sekcji Programowanie obiektowe ścieżki C# w Coddy. Lekcja 26 z 70.
Zarówno interfejsy, jak i klasy abstrakcyjne pozwalają definiować kontrakty, których muszą przestrzegać inne klasy, ale służą różnym celom i mają odmienne możliwości.
Interfejs określa, co klasa musi robić, bez żadnej implementacji. To czysty kontrakt:
public interface IFlyable
{
void Fly();
}
public class Bird : IFlyable
{
public void Fly()
{
Console.WriteLine("Flapping wings");
}
}Klasa abstrakcyjna może zapewniać zarówno kontrakt, jak i współdzieloną implementację. To częściowy szablon:
public abstract class Vehicle
{
public string Brand { get; set; }
public void StartEngine()
{
Console.WriteLine("Engine started");
}
public abstract void Drive(); // Musi zostać zaimplementowana
}
public class Car : Vehicle
{
public override void Drive()
{
Console.WriteLine("Driving on roads");
}
}Kluczowe różnice sprowadzają się do zasad dziedziczenia i przeznaczenia.
Klasa może implementować wiele interfejsów, ale dziedziczyć tylko po jednej klasie abstrakcyjnej.
Użyj interfejsu, gdy niepowiązane klasy muszą współdzielić zachowanie (na przykład IFlyable dla ptaków i samolotów). Użyj klasy abstrakcyjnej, gdy powiązane klasy współdzielą wspólny kod i stan (na przykład pojazdy korzystające ze wspólnego mechanizmu uruchamiania silnika).
Klasy abstrakcyjne mogą mieć konstruktory, pola i modyfikatory dostępu składowych. Interfejsy tradycyjnie nie mogły ich mieć, choć współczesny C# pozwala na implementacje domyślne.
W razie wątpliwości wybieraj interfejsy ze względu na elastyczność — w razie potrzeby zawsze możesz później dodać klasę abstrakcyjną, jeśli wspólna implementacja okaże się konieczna.
Wyzwanie
ŁatwyZbudujmy system odtwarzacza multimediów, który pokazuje, kiedy używać interfejsów, a kiedy klas abstrakcyjnych. Stworzysz projekt, w którym niezwiązane ze sobą urządzenia współdzielą zachowanie związane z odtwarzaniem za pośrednictwem interfejsu, a powiązane typy multimediów współdzielą wspólną implementację za pośrednictwem klasy abstrakcyjnej.
Rozmieścisz kod w czterech plikach:
IPlayable.cs: Zdefiniuj interfejs o nazwieIPlayablew przestrzeni nazwMedia. Ten interfejs określa kontrakt dla wszystkiego, co może odtwarzać multimedia — niezależnie od tego, czy jest to telefon, telewizor, czy dedykowany odtwarzacz muzyczny. Powinien deklarować jedną metodęPlay(), która zwraca ciąg znaków.MediaFile.cs: Zdefiniuj klasę abstrakcyjną o nazwieMediaFilew przestrzeni nazwMedia. Stanowi ona wspólną podstawę dla wszystkich plików multimedialnych. Powinna mieć właściwośćTitle(string) i właściwośćDuration(int, oznaczającą liczbę sekund). Dodaj konstruktor, który ustawia obie wartości. Dodaj konkretną metodę o nazwieGetInfo(), która zwraca"{Title} ({Duration}s)". Zadeklaruj też metodę abstrakcyjnąGetType(), która zwraca ciąg znaków — każdy typ mediów określi, jakiego rodzaju jest to plik.AudioFile.cs: Zdefiniuj klasęAudioFilew przestrzeni nazwMedia, która dziedziczy poMediaFilei implementujeIPlayable. Dodaj właściwośćArtist(string). Konstruktor powinien przyjmować tytuł, czas trwania i wykonawcę. NadpiszGetType(), aby zwracała"Audio". ZaimplementujPlay(), aby zwracała"Playing audio: {Title} by {Artist}".Program.cs: W pliku głównym utwórz obiektAudioFile, używając wartości wejściowych. Pokaż oba aspekty tej klasy: wywołajGetInfo()iGetType()(odziedziczone z klasy abstrakcyjnej), a następnie wywołajPlay()(z interfejsu). Zapisz plik audio w zmiennej typuIPlayablei ponownie wywołajPlay(), aby pokazać polimorfizm interfejsów.
Otrzymasz trzy dane wejściowe:
- Tytuł utworu
- Czas trwania w sekundach
- Nazwa wykonawcy
Wypisz wynik w tym formacie:
Info: {GetInfo() result}
Type: {GetType() result}
Direct: {Play() result}
Via Interface: {Play() result from IPlayable variable}Na przykład, jeśli dane wejściowe to Bohemian Rhapsody, 354 i Queen, wynik powinien wyglądać następująco:
Info: Bohemian Rhapsody (354s)
Type: Audio
Direct: Playing audio: Bohemian Rhapsody by Queen
Via Interface: Playing audio: Bohemian Rhapsody by QueenZwróć uwagę, że AudioFile korzysta z obu podejść: dziedziczy wspólną implementację (GetInfo()) z klasy abstrakcyjnej, a jednocześnie spełnia kontrakt IPlayable. Klasa abstrakcyjna umożliwia ponowne wykorzystanie kodu w powiązanych typach mediów, natomiast interfejs pozwala dowolnej, niezwiązanej klasie być „odtwarzalną” bez współdzielenia hierarchii dziedziczenia.
Spróbuj swoich sił
using System;
using Media;
class Program
{
public static void Main(string[] args)
{
// Odczytaj wartości wejściowe
string title = Console.ReadLine();
int duration = Convert.ToInt32(Console.ReadLine());
string artist = Console.ReadLine();
// TODO: Utwórz AudioFile, używając wartości wejściowych
// TODO: Wypisz Info:, a następnie wynik GetInfo()
// TODO: Wypisz Type:, a następnie wynik GetType()
// TODO: Wypisz Direct:, a następnie wynik Play()
// TODO: Zapisz plik audio w zmiennej typu IPlayable
// TODO: Wypisz Via Interface:, a następnie wynik Play() wywołanej na zmiennej typu IPlayable
}
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrznePrzestrzenie nazw i dyrektywyWprowadzenie do klas i obiektówSłowo kluczowe „this”Metody i parametryPola a właściwościKonstruktoryInicjalizatory obiektówPodsumowanie — prosty kalkulator4Dziedziczenie
Podstawowa składnia dziedziczenia (:)Słowo kluczowe 'base'Słowa kluczowe virtual i overrideKlasy sealedBazowa klasa 'object'Powtórzenie – hierarchia pracowników7Zaawansowane funkcje
Przeciążanie operatorówIndeksery (this[])Nadpisywanie ToString()Metody rozszerzającePodsumowanie – własna lista2Właściwości i składowe statyczne
Właściwości automatyczneWłaściwości tylko do odczytu i zapisuPola i metody statyczneKlasy statyczneSkładowe z ciałem wyrażeniowym5Polimorfizm i interfejsy
Polimorfizm czasu kompilacji a czasu wykonaniaInterfejs a klasa abstrakcyjnaWiele interfejsówJawne implementowanie interfejsówRzutowanie w górę i w dółPowtórka — kalkulator kształtów8Zaawansowane koncepcje OOP
Kompozycja zamiast dziedziczeniaTypy generyczne (klasy i metody)Delegaty i zdarzeniaAtrybuty i refleksjaIDisposable i instrukcja usingPodstawy wstrzykiwania zależności11Wzorce projektowe, część 2
Wzorzec poleceniaWzorzec adapteraWzorzec dekoratoraWzorzec metody szablonowejWzorzec stanuWzorzec kompozytu3Architektura klas
Dane instancji a dane statyczneSłowa kluczowe 'readonly' i 'const'Pola zapasowePowtórzenie — menedżer kont bankowych6Hermetyzacja
Modyfikatory dostępuWłaściwości w hermetyzacjiImplementacja ukrywania danychWzorce niezmiennościPowtórka — dane studentów9Argumenty zmiennej liczby
Słowo kluczowe „params”Parametry opcjonalneArgumenty nazwanePrzeciążanie metodPoćwicz samodzielnie: Kompilator C# online