Nasleđivanje, apstraktne klase i interfejsi u C#

Lekcija 8 od 15 · C#

Nasleđivanjem nova klasa preuzima podatke i metode postojeće klase i dodaje svoje. Polimorfizam omogućava da se objekti različitih klasa obrađuju zajedno, a da svaki radi „na svoj način“. Interfejs je ugovor: spisak metoda koje klasa obećava da ima.

Pre nego što počneš: potrebno predznanje – klase i objekti.
Kako da učiš: svaki primer prekucaj i pokreni, a zadatke na kraju prvo reši sam/a; uputstvo i rešenje otvori tek posle toga. Svi programi su konzolne aplikacije u C#-u (.NET 8 ili noviji, bez „top-level statements“); svaki je proveren i daje navedeni izlaz.

Nasleđivanje: base, virtual i override

using System;

class Osoba
{
    public string Ime { get; }
    public int Godine { get; }

    public Osoba(string ime, int godine)
    {
        Ime = ime;
        Godine = godine;
    }

    // virtual: izvedene klase smeju da promene ovu metodu
    public virtual string Opis() => $"{Ime}, {Godine} god.";
}

class Ucenik : Osoba                       // Ucenik nasleđuje klasu Osoba
{
    public int Razred { get; }

    public Ucenik(string ime, int godine, int razred) : base(ime, godine)   // konstruktor bazne klase
    {
        Razred = razred;
    }

    public override string Opis() => base.Opis() + $", ucenik {Razred}. razreda";
}

class Nastavnik : Osoba
{
    public string Predmet { get; }

    public Nastavnik(string ime, int godine, string predmet) : base(ime, godine)
    {
        Predmet = predmet;
    }

    public override string Opis() => base.Opis() + $", predaje {Predmet}";
}

class Program
{
    static void Main()
    {
        Osoba[] ljudi =
        {
            new Ucenik("Ana", 13, 7),
            new Nastavnik("Petar", 41, "informatiku"),
            new Osoba("Mila", 30)
        };
        foreach (Osoba o in ljudi)
            Console.WriteLine(o.Opis());    // poziva se Opis() stvarne klase objekta
    }
}

Izvedena klasa piše se kao class Ucenik : Osoba. Ona ima sve javne članove klase Osoba i svoje dodatne. Konstruktor bazne klase poziva se sa : base(…), a base.Opis() poziva originalnu metodu.

U nizu tipa Osoba[] nalaze se i učenik i nastavnik. Za svaki objekat poziva se Opis() njegove stvarne klase – to je polimorfizam. Za to bazna metoda mora biti virtual, a nova verzija označena sa override.

Apstraktne klase

Apstraktna klasa (abstract) služi samo kao osnova za druge klase – od nje se ne mogu praviti objekti. Apstraktna metoda nema telo, a svaka izvedena klasa mora da je napiše.

using System;

abstract class Oblik
{
    public abstract double Povrsina();          // bez tela
    public abstract double Obim();

    public override string ToString() => $"{GetType().Name}: P = {Povrsina():F2}, O = {Obim():F2}";
}

class Krug : Oblik
{
    private readonly double r;
    public Krug(double r) { this.r = r; }
    public override double Povrsina() => Math.PI * r * r;
    public override double Obim() => 2 * Math.PI * r;
}

class Pravougaonik : Oblik
{
    private readonly double a, b;
    public Pravougaonik(double a, double b) { this.a = a; this.b = b; }
    public override double Povrsina() => a * b;
    public override double Obim() => 2 * (a + b);
}

class Program
{
    static void Main()
    {
        Oblik[] oblici = { new Krug(1), new Pravougaonik(3, 4), new Krug(2.5) };
        double ukupno = 0;
        foreach (var o in oblici)
        {
            Console.WriteLine(o);
            ukupno += o.Povrsina();
        }
        Console.WriteLine($"Ukupna povrsina: {ukupno:F2}");
        // var x = new Oblik();                 // greška: apstraktna klasa nema objekte
    }
}

GetType().Name vraća ime stvarne klase objekta, pa jedna metoda ToString u baznoj klasi radi za sve oblike.

Interfejsi

Interfejs (interface, ime po dogovoru počinje slovom I) sadrži samo potpise metoda i svojstava. Klasa može da nasledi samo jednu klasu, ali može da implementira više interfejsa. Operator is proverava da li objekat implementira interfejs.

using System;

interface IZvuk
{
    string Glas();
}

interface IKretanje
{
    int Brzina { get; }
}

class Pas : IZvuk, IKretanje              // dva interfejsa
{
    public string Glas() => "Av av";
    public int Brzina => 40;
}

class Macka : IZvuk
{
    public string Glas() => "Mjau";
}

class Auto : IKretanje
{
    public int Brzina => 130;
}

class Program
{
    static void Main()
    {
        IZvuk[] zvuci = { new Pas(), new Macka() };
        foreach (var z in zvuci)
            Console.WriteLine(z.Glas());

        object[] stvari = { new Pas(), new Macka(), new Auto() };
        foreach (var s in stvari)
        {
            if (s is IKretanje k)                 // da li objekat ume da se kreće?
                Console.WriteLine($"{s.GetType().Name} ide {k.Brzina} km/h");
            else
                Console.WriteLine($"{s.GetType().Name} se ne krece");
        }
    }
}

Interfejs IComparable: sortiranje sopstvenih objekata

Array.Sort i List.Sort umeju da sortiraju objekte klase koja implementira IComparable<T>. Metoda CompareTo vraća negativan broj ako ovaj objekat ide pre drugog, nulu ako su jednaki, a pozitivan broj ako ide posle.

using System;

class Takmicar : IComparable<Takmicar>
{
    public string Ime { get; }
    public int Poeni { get; }

    public Takmicar(string ime, int poeni)
    {
        Ime = ime;
        Poeni = poeni;
    }

    // više poena ide napred; kod istog broja poena – abecedno po imenu
    public int CompareTo(Takmicar drugi)
    {
        if (Poeni != drugi.Poeni)
            return drugi.Poeni.CompareTo(Poeni);
        return string.Compare(Ime, drugi.Ime, StringComparison.Ordinal);
    }
}

class Program
{
    static void Main()
    {
        Takmicar[] t =
        {
            new Takmicar("Luka", 85), new Takmicar("Ana", 92),
            new Takmicar("Iva", 85), new Takmicar("Nikola", 70)
        };
        Array.Sort(t);
        for (int i = 0; i < t.Length; i++)
            Console.WriteLine($"{i + 1}. {t[i].Ime} ({t[i].Poeni})");
    }
}

Sortiranje se može zadati i bez interfejsa – lambda izrazom ili LINQ upitom, što je pokazano u lekciji LINQ upiti i lambda izrazi.

Zadaci za vežbu

Za svaki zadatak postoji primer, uputstvo i rešenje. Rešenja su provereno tačna na primerima iz teksta.

ZADATAK 01Težina: srednje

Plate zaposlenih

Napisati apstraktnu klasu Zaposleni (ime i apstraktna metoda Plata()) i dve izvedene klase: Radnik (plata = satnica · broj sati) i Menadzer (plata = osnovna · (1 + bonus/100)). Ulaz: broj zaposlenih n, zatim n redova oblika R ime satnica sati ili M ime osnovna bonus (celi brojevi). Ispisati platu svakog zaposlenog i ukupan iznos, sa dve decimale.

Primer

Ulaz

3
R Marko 800 160
M Jovana 150000 20
R Ivan 950 120

Izlaz

Marko: 128000.00
Jovana: 180000.00
Ivan: 114000.00
Ukupno: 422000.00
ZADATAK 02Težina: srednje

Najveća površina

Napisati interfejs IOblik sa metodom double Povrsina() i svojstvom string Naziv i klase Krug, Pravougaonik i Trougao (površina po Heronovom obrascu). Ulaz: broj oblika, zatim redovi K r, P a b ili T a b c (celi brojevi). Ispisati naziv oblika najveće površine i njegovu površinu sa dve decimale.

Primer 1

Ulaz

3
K 2
P 3 5
T 3 4 5

Izlaz

Pravougaonik 15.00

Primer 2

Ulaz

2
T 5 5 6
K 3

Izlaz

Krug 28.27
Česte greške i saveti
  • Bazna metoda koju izvedena klasa menja mora biti virtual (ili abstract), a nova verzija override.
  • Klasa nasleđuje najviše jednu klasu, ali može implementirati više interfejsa: class Pas : Zivotinja, IZvuk, IKretanje.
  • Od apstraktne klase i interfejsa ne mogu se praviti objekti, ali mogu postojati promenljive tog tipa.
  • protected član vide klasa i njene izvedene klase, ali ne i ostatak programa.

Proveri znanje

Otvori kviz

Nasleđivanje i interfejsi u C#

1. Kako se piše da klasa Ucenik nasleđuje klasu Osoba?




2. Šta mora da ima metoda bazne klase da bi izvedena klasa mogla da je promeni sa override?




3. Koliko interfejsa klasa može da implementira?




4. Šta je tačno za apstraktnu klasu?