Menu
Coddy logo textTech

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.

challenge icon

Wyzwanie

Łatwy

Zbudujmy 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 nazwie IPlayable w przestrzeni nazw Media. 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 nazwie MediaFile w przestrzeni nazw Media. 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 nazwie GetInfo(), 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ę AudioFile w przestrzeni nazw Media, która dziedziczy po MediaFile i implementuje IPlayable. Dodaj właściwość Artist (string). Konstruktor powinien przyjmować tytuł, czas trwania i wykonawcę. Nadpisz GetType(), aby zwracała "Audio". Zaimplementuj Play(), aby zwracała "Playing audio: {Title} by {Artist}".
  • Program.cs: W pliku głównym utwórz obiekt AudioFile, używając wartości wejściowych. Pokaż oba aspekty tej klasy: wywołaj GetInfo() i GetType() (odziedziczone z klasy abstrakcyjnej), a następnie wywołaj Play() (z interfejsu). Zapisz plik audio w zmiennej typu IPlayable i ponownie wywołaj Play(), 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 Queen

Zwróć 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
    }
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator C# online