Paweł Łukasiewicz: programista blogger
Paweł Łukasiewicz
2026-04-01
Paweł Łukasiewicz: programista blogger
Paweł Łukasiewicz
2026-04-01
Udostępnij Udostępnij Kontakt
Wprowadzenie

Dziedziczenie i polimorfizm to fundamenty programowania obiektowego - i w przeciwieństwie do większości ficzerów C#, te NIE zmieniły się od 2002 roku! To dobra wiadomość - jeśli znałeś virtual, override i abstract w 2015, działają IDENTYCZNIE w 2026.

Ale są nowości! Default interface implementations (C# 8) rewolucjonizują interfejsy, a pattern matching z hierarchiami (C# 9+) daje nowe możliwości pracy z polimorfizmem. W tym wpisie poznasz fundamenty + nowoczesne użycia!

📅 Timeline - dziedziczenie i polimorfizm w C#
  • C# 1.0 (2002) - virtual, override, abstract, sealed - fundamenty
  • C# 1.0-7.3 (2002-2018) - ⚪ BEZ ZMIAN (16 lat stabilności!)
  • C# 8 (2019) - 🔥 Default interface implementations
  • C# 9 (2020) - Pattern matching z type patterns
  • C# 10 (2021) - Extended property patterns dla hierarchii
  • C# 11 (2022) - List patterns z hierarchiami
🔍 Dziedziczenie vs Kompozycja

Dziedziczenie = "is-a" relationship (Dog IS-A Animal)
Kompozycja = "has-a" relationship (Car HAS-A Engine)

Zasada: "Favor composition over inheritance" - ale dziedziczenie ma swoje miejsce!

Podstawy dziedziczenia - niezmienne od 2002!

Składnia dziedziczenia

// Base class (klasa bazowa)
public class Animal
{
    public string Name { get; set; }
    public int Age { get; set; }
    
    public void Eat()
    {
        Console.WriteLine($"{Name} is eating.");
    }
    
    public void Sleep()
    {
        Console.WriteLine($"{Name} is sleeping.");
    }
}

// Derived class (klasa pochodna) - dziedziczy po Animal
public class Dog : Animal  // Dog "is-a" Animal
{
    public string Breed { get; set; }
    
    public void Bark()
    {
        Console.WriteLine($"{Name} says: Woof!");
    }
}

// Użycie
var dog = new Dog 
{ 
    Name = "Rex", 
    Age = 5, 
    Breed = "Labrador" 
};

dog.Eat();   // Metoda z Animal
dog.Sleep(); // Metoda z Animal
dog.Bark();  // Metoda z Dog

// Dog ma dostęp do WSZYSTKIEGO z Animal + własne członki

Konstruktory w dziedziczeniu

// Base class z konstruktorem
public class Animal
{
    public string Name { get; }
    public int Age { get; }
    
    public Animal(string name, int age)
    {
        Name = name;
        Age = age;
        Console.WriteLine($"Animal constructor: {name}");
    }
}

// Derived class MUSI wywołać base constructor
public class Dog : Animal
{
    public string Breed { get; }
    
    // : base(name, age) - wywołanie konstruktora bazowego
    public Dog(string name, int age, string breed) : base(name, age)
    {
        Breed = breed;
        Console.WriteLine($"Dog constructor: {breed}");
    }
}

var dog = new Dog("Rex", 5, "Labrador");
// Output:
// Animal constructor: Rex
// Dog constructor: Labrador

// Konstruktor bazowy ZAWSZE wykonuje się PRZED derived!

C# 12 - Primary constructors w hierarchii

// C# 12 - primary constructors z dziedziczeniem
public class Animal(string name, int age)
{
    public string Name { get; } = name;
    public int Age { get; } = age;
}

// Derived class - musi przekazać parametry do base
public class Dog(string name, int age, string breed) : Animal(name, age)
{
    public string Breed { get; } = breed;
}

var dog = new Dog("Rex", 5, "Labrador");
Console.WriteLine($"{dog.Name}, {dog.Age}, {dog.Breed}");

Modyfikatory dostępu w dziedziczeniu

public class Animal
{
    public string Name { get; set; }        // Dostępne wszędzie
    protected int Age { get; set; }         // Tylko w Animal i klasach pochodnych
    private string _id;                     // Tylko w Animal
    internal string Species { get; set; }   // Tylko w tym samym assembly
    
    private void InternalMethod() { }       // Tylko Animal
    protected void ProtectedMethod() { }    // Animal i klasy pochodne
    public void PublicMethod() { }          // Wszędzie
}

public class Dog : Animal
{
    public void Test()
    {
        var name = Name;       // ✅ OK - public
        var age = Age;         // ✅ OK - protected (dostęp w klasie pochodnej)
        // var id = _id;       // ❌ BŁĄD - private (tylko w Animal)
        var sp = Species;      // ✅ OK - internal (ten sam assembly)
        
        PublicMethod();        // ✅ OK
        ProtectedMethod();     // ✅ OK - protected
        // InternalMethod();   // ❌ BŁĄD - private
    }
}
⚠️ C# pozwala tylko na SINGLE INHERITANCE!

Klasa może dziedziczyć tylko po JEDNEJ klasie bazowej!

// ❌ To NIE działa w C# - multiple inheritance
public class Dog : Animal, Mammal  // BŁĄD!
{
}

// ✅ Możesz implementować wiele INTERFEJSÓW
public class Dog : Animal, IRunnable, ISwimmable  // OK!
{
}
Virtual, Override - polimorfizm w akcji

virtual - metody które można nadpisać

// Base class z virtual method
public class Animal
{
    public string Name { get; set; }
    
    // virtual - klasy pochodne MOGĄ nadpisać (ale nie muszą)
    public virtual void MakeSound()
    {
        Console.WriteLine("Some generic animal sound");
    }
    
    public virtual string GetDescription()
    {
        return $"Animal: {Name}";
    }
}

// Derived class - override virtual method
public class Dog : Animal
{
    // override - nadpisz metodę z base class
    public override void MakeSound()
    {
        Console.WriteLine("Woof! Woof!");
    }
    
    public override string GetDescription()
    {
        return $"Dog: {Name}";
    }
}

public class Cat : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("Meow!");
    }
}

// Polimorfizm - ta sama referencja, różne zachowania!
Animal animal1 = new Dog { Name = "Rex" };
Animal animal2 = new Cat { Name = "Whiskers" };

animal1.MakeSound();  // Woof! Woof! (wywołuje Dog.MakeSound)
animal2.MakeSound();  // Meow! (wywołuje Cat.MakeSound)

// Runtime decyduje która wersja zostanie wywołana!

base - wywołanie metody bazowej

public class Animal
{
    public virtual void Introduce()
    {
        Console.WriteLine("I'm an animal");
    }
}

public class Dog : Animal
{
    public override void Introduce()
    {
        // base. - wywołaj wersję z klasy bazowej
        base.Introduce();  // "I'm an animal"
        Console.WriteLine("And I'm a dog!");
    }
}

var dog = new Dog();
dog.Introduce();
// Output:
// I'm an animal
// And I'm a dog!

override vs new - kluczowa różnica!

public class Animal
{
    public virtual void MakeSound()
    {
        Console.WriteLine("Animal sound");
    }
}

// override - polimorfizm (dynamic dispatch)
public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("Woof!");
    }
}

// new - ukrywa metodę bazową (NO polimorfizm!)
public class Cat : Animal
{
    public new void MakeSound()  // ⚠️ new, nie override!
    {
        Console.WriteLine("Meow!");
    }
}

// Test polimorfizmu
Animal dog = new Dog();
Animal cat = new Cat();

dog.MakeSound();  // Woof! (override - polimorfizm działa! ✅)
cat.MakeSound();  // Animal sound (new - NIE polimorfizm! ❌)

// Cat jako Cat
Cat catDirect = new Cat();
catDirect.MakeSound();  // Meow! (bezpośrednia referencja działa)

// Zasada: ZAWSZE używaj override, NIE new (chyba że masz BARDZO dobry powód)
❌ new - ukrywa, nie nadpisuje
public class Base
{
    public virtual void Method()
    {
        Console.WriteLine("Base");
    }
}

public class Derived : Base
{
    public new void Method()  // ❌ new!
    {
        Console.WriteLine("Derived");
    }
}

Base obj = new Derived();
obj.Method();  // Base (❌ nie polimorfizm!)

// new UKRYWA metodę - to NIE jest polimorfizm!
✅ override - prawdziwy polimorfizm
public class Base
{
    public virtual void Method()
    {
        Console.WriteLine("Base");
    }
}

public class Derived : Base
{
    public override void Method()  // ✅ override!
    {
        Console.WriteLine("Derived");
    }
}

Base obj = new Derived();
obj.Method();  // Derived (✅ polimorfizm!)

// override daje prawdziwy polimorfizm! ✨
Abstract classes i members - kontrakty do implementacji

abstract class - nie można utworzyć instancji

// abstract class - szablon dla klas pochodnych
public abstract class Shape
{
    public string Color { get; set; }
    
    // abstract method - MUSI być zaimplementowana w klasie pochodnej
    public abstract double GetArea();
    
    // virtual method - MOŻE być nadpisana (ale nie musi)
    public virtual void Draw()
    {
        Console.WriteLine($"Drawing {Color} shape");
    }
    
    // Normalna metoda - NIE może być nadpisana
    public void Display()
    {
        Console.WriteLine($"Area: {GetArea()}, Color: {Color}");
    }
}

// ❌ Nie możesz stworzyć instancji abstract class
// var shape = new Shape();  // BŁĄD kompilacji!

// Musisz dziedziczyć i zaimplementować wszystkie abstract members
public class Circle : Shape
{
    public double Radius { get; set; }
    
    // MUSISZ zaimplementować abstract method
    public override double GetArea()
    {
        return Math.PI * Radius * Radius;
    }
}

public class Rectangle : Shape
{
    public double Width { get; set; }
    public double Height { get; set; }
    
    public override double GetArea()
    {
        return Width * Height;
    }
    
    // Możesz nadpisać virtual method (ale nie musisz)
    public override void Draw()
    {
        Console.WriteLine($"Drawing {Color} rectangle: {Width}x{Height}");
    }
}

// Użycie - polimorfizm z abstract class
Shape circle = new Circle { Color = "Red", Radius = 5 };
Shape rectangle = new Rectangle { Color = "Blue", Width = 10, Height = 20 };

Console.WriteLine(circle.GetArea());     // ~78.5
Console.WriteLine(rectangle.GetArea());  // 200

circle.Display();     // Area: 78.5..., Color: Red
rectangle.Display();  // Area: 200, Color: Blue

Abstract properties

public abstract class Animal
{
    // Abstract property - musi być zaimplementowana
    public abstract string Species { get; }
    
    // Abstract get/set property
    public abstract int LegCount { get; set; }
    
    // Virtual property
    public virtual string Sound => "Generic sound";
}

public class Dog : Animal
{
    // Implementacja abstract properties
    public override string Species => "Canis familiaris";
    
    public override int LegCount { get; set; } = 4;
    
    // Override virtual property
    public override string Sound => "Woof!";
}

Kiedy używać abstract vs interface?

Pytanie Abstract Class Interface
Wspólna implementacja? ✅ Tak - może mieć metody z kodem Tylko default implementations (C# 8+)
State (fields)? ✅ Tak - może mieć pola ❌ Nie
Konstruktor? ✅ Tak ❌ Nie
Multiple inheritance? ❌ Nie - tylko jedna base class ✅ Tak - wiele interfejsów
Access modifiers? ✅ Tak - public, protected, private Tylko public (domyślnie)
Kiedy używać? "is-a" relationship + wspólny kod "can-do" relationship + kontrakt
💡 Przykłady - abstract class vs interface
// ✅ Abstract class - wspólna implementacja
public abstract class Vehicle
{
    public string Brand { get; set; }
    public int Year { get; set; }
    
    // Wspólna logika dla wszystkich vehicles
    public string GetAge() => $"{DateTime.Now.Year - Year} years old";
    
    // Każdy vehicle musi zaimplementować
    public abstract void Start();
}

// ✅ Interface - kontrakt, bez implementacji
public interface IFlyable
{
    void TakeOff();
    void Land();
    double MaxAltitude { get; }
}

// Klasa może dziedziczyć po abstract + implementować interfejsy
public class Airplane : Vehicle, IFlyable
{
    public override void Start()
    {
        Console.WriteLine("Starting engines...");
    }
    
    public void TakeOff()
    {
        Console.WriteLine("Taking off...");
    }
    
    public void Land()
    {
        Console.WriteLine("Landing...");
    }
    
    public double MaxAltitude => 12000;
}
Sealed - blokowanie dziedziczenia

sealed class - nie można dziedziczyć

// sealed class - końcowa klasa w hierarchii
public sealed class FinalClass
{
    public void Method()
    {
        Console.WriteLine("This class cannot be inherited");
    }
}

// ❌ To NIE zadziała
// public class Derived : FinalClass  // BŁĄD kompilacji!
// {
// }

// Przykłady sealed classes w .NET:
// - System.String (sealed!)
// - System.Int32, Int64, etc. (sealed!)
// - Record types są domyślnie sealed

sealed override - blokowanie dalszego override

public class Base
{
    public virtual void Method()
    {
        Console.WriteLine("Base");
    }
}

public class Middle : Base
{
    // sealed override - można nadpisać Base, ale STOP tutaj!
    public sealed override void Method()
    {
        Console.WriteLine("Middle - no more overrides!");
    }
}

public class Derived : Middle
{
    // ❌ Nie możesz override sealed method
    // public override void Method()  // BŁĄD!
    // {
    // }
}

Kiedy używać sealed?

🔍 Używaj sealed gdy:
  • Performance - sealed classes są szybsze (compiler optimizations)
  • Security - zapobieganie złośliwym klasom pochodnym
  • Design intent - klasa nie jest zaprojektowana do dziedziczenia
  • Breaking changes - chronisz przed zmianami w przyszłości

Zasada: Domyślnie rób klasy sealed, chyba że jawnie projektujesz do dziedziczenia!

// ✅ DOBRZE - sealed domyślnie (records)
public sealed record UserDto(int Id, string Email);

// ✅ DOBRZE - sealed explicit
public sealed class StringHelper
{
    public static string Reverse(string input) => /* ... */;
}

// ⚠️ Tylko jeśli PROJEKTUJESZ hierarchię - pozostaw otwarte
public class BaseController  // NIE sealed - zaprojektowane do dziedziczenia
{
    public virtual void OnInit() { }
}
🔥 Default Interface Implementations (C# 8)

Problem przed C# 8

// Przed C# 8 - interfejs bez implementacji
public interface ILogger
{
    void Log(string message);
}

// 100 klas implementuje ILogger
public class ConsoleLogger : ILogger
{
    public void Log(string message) => Console.WriteLine(message);
}

// ... 99 innych klas ...

// ❌ Problem: chcesz dodać nową metodę do ILogger
public interface ILogger
{
    void Log(string message);
    void LogError(string error);  // Nowa metoda!
}

// 💥 BREAKING CHANGE! Wszystkie 100 klas muszą zaimplementować LogError!
// To jest NIEMOŻLIWE w istniejących projektach!

Rozwiązanie - Default Interface Implementation!

🎉 C# 8 - Default Interface Members

Interfejsy mogą mieć implementację! Dodawaj nowe metody BEZ breaking changes!

// C# 8 - interfejs z domyślną implementacją
public interface ILogger
{
    void Log(string message);
    
    // Default implementation - klasy NIE MUSZĄ implementować!
    void LogError(string error)
    {
        Log($"ERROR: {error}");  // używa Log() z implementacji
    }
    
    void LogWarning(string warning)
    {
        Log($"WARNING: {warning}");
    }
}

// Stara klasa - działa bez zmian! ✅
public class ConsoleLogger : ILogger
{
    public void Log(string message) => Console.WriteLine(message);
    // LogError i LogWarning są dostępne z domyślnej implementacji!
}

// Użycie
ILogger logger = new ConsoleLogger();
logger.Log("Info");           // ConsoleLogger.Log
logger.LogError("Error!");    // Default implementation z ILogger!
logger.LogWarning("Warning"); // Default implementation z ILogger!

// Output:
// Info
// ERROR: Error!
// WARNING: Warning

Override default implementation

public interface ILogger
{
    void Log(string message);
    
    void LogError(string error)
    {
        Log($"ERROR: {error}");
    }
}

// Możesz nadpisać default implementation
public class FileLogger : ILogger
{
    public void Log(string message)
    {
        File.AppendAllText("log.txt", message + "\n");
    }
    
    // Override default implementation - własna logika!
    public void LogError(string error)
    {
        File.AppendAllText("errors.txt", $"[{DateTime.Now}] {error}\n");
    }
}

ILogger logger = new FileLogger();
logger.LogError("Critical error");
// Używa FileLogger.LogError, NIE default implementation

Default properties i static members

// C# 8 - interfejsy mogą mieć więcej!
public interface IConfigurable
{
    // Default property
    string ConfigPath => "config.json";
    
    // Default method
    void LoadConfig()
    {
        var json = File.ReadAllText(ConfigPath);
        Console.WriteLine($"Loaded: {json}");
    }
    
    // Static member w interfejsie!
    static IConfigurable CreateDefault() => new DefaultConfig();
}

public class DefaultConfig : IConfigurable
{
    // ConfigPath i LoadConfig są dostępne z domyślnej implementacji
}

// Użycie
IConfigurable config = IConfigurable.CreateDefault();  // static member!
config.LoadConfig();  // default implementation
⚠️ Default interface members - ważne zasady!
  • Default members są dostępne TYLKO przez interface reference
  • NIE są dostępne przez class reference (chyba że explicit override)
  • Nie mogą mieć fields (pól) - tylko properties, methods
  • Access modifiers: domyślnie public, można użyć private, protected
public interface ILogger
{
    void LogError(string error) => Log($"ERROR: {error}");
}

public class ConsoleLogger : ILogger
{
    public void Log(string message) => Console.WriteLine(message);
}

// ✅ Przez interface reference - działa
ILogger logger1 = new ConsoleLogger();
logger1.LogError("Error");  // ✅ OK

// ❌ Przez class reference - NIE MA dostępu!
ConsoleLogger logger2 = new ConsoleLogger();
// logger2.LogError("Error");  // ❌ BŁĄD - LogError nie jest w ConsoleLogger!
💡 Praktyczne przykłady - default interface implementations
// Przykład 1: Repository pattern z default query methods
public interface IRepository
{
    Task GetByIdAsync(int id);
    Task> GetAllAsync();

    // Default implementation dla common operations
    async Task FindFirstAsync(Func predicate)
    {
        var all = await GetAllAsync();
        return all.FirstOrDefault(predicate);
    }

    async Task CountAsync()
    {
        var all = await GetAllAsync();
        return all.Count();
    }
}

// Przykład 2: IDisposable pattern
public interface IResource : IDisposable
{
    void Close();

    // Default Dispose wywołuje Close
    void IDisposable.Dispose()
    {
        Close();
        GC.SuppressFinalize(this);
    }
}

// Implementacja - tylko Close!
public class FileResource : IResource
{
    public void Close()
    {
        // zamknij plik
    }
    // Dispose jest z default implementation!
}
Pattern Matching z hierarchiami (C# 9+)

Type patterns - switch na typach

// Hierarchia
public abstract class Shape
{
    public string Color { get; init; }
}

public class Circle : Shape
{
    public double Radius { get; init; }
}

public class Rectangle : Shape
{
    public double Width { get; init; }
    public double Height { get; init; }
}

public class Triangle : Shape
{
    public double Base { get; init; }
    public double Height { get; init; }
}

// C# 9+ - pattern matching na hierarchii
double CalculateArea(Shape shape) => shape switch
{
    Circle c => Math.PI * c.Radius * c.Radius,
    Rectangle r => r.Width * r.Height,
    Triangle t => 0.5 * t.Base * t.Height,
    _ => throw new ArgumentException("Unknown shape")
};

// Użycie
Shape circle = new Circle { Color = "Red", Radius = 5 };
Shape rectangle = new Rectangle { Color = "Blue", Width = 10, Height = 20 };

Console.WriteLine(CalculateArea(circle));     // ~78.5
Console.WriteLine(CalculateArea(rectangle));  // 200

Property patterns w hierarchii

// Pattern matching z property patterns
string DescribeShape(Shape shape) => shape switch
{
    Circle { Radius: > 10 } => "Large circle",
    Circle { Radius: <= 10 } => "Small circle",
    Rectangle { Width: var w, Height: var h } when w == h => "Square",
    Rectangle { Width: > 20 } => "Wide rectangle",
    Rectangle => "Normal rectangle",
    Triangle { Base: > 10, Height: > 10 } => "Large triangle",
    _ => "Unknown shape"
};

// Relational patterns
string GetSize(Shape shape) => shape switch
{
    Circle { Radius: < 5 } => "Tiny",
    Circle { Radius: >= 5 and < 10 } => "Medium",
    Circle { Radius: >= 10 } => "Large",
    Rectangle { Width: < 10, Height: < 10 } => "Small",
    _ => "Various size"
};

Nested property patterns

// Nested hierarchie
public class Canvas
{
    public Shape? MainShape { get; init; }
    public string Theme { get; init; }
}

// Nested pattern matching
string DescribeCanvas(Canvas canvas) => canvas switch
{
    { MainShape: Circle { Radius: > 10 }, Theme: "Dark" } 
        => "Dark canvas with large circle",
    
    { MainShape: Rectangle { Width: var w, Height: var h }, Theme: "Light" } when w == h
        => "Light canvas with square",
    
    { MainShape: Triangle { Base: > 20 } }
        => "Canvas with wide triangle",
    
    { MainShape: null }
        => "Empty canvas",
    
    _ => "Generic canvas"
};

List patterns z hierarchiami (C# 11)

// C# 11 - list patterns
string DescribeShapes(Shape[] shapes) => shapes switch
{
    [] => "No shapes",
    [Circle] => "Single circle",
    [Circle, Circle] => "Two circles",
    [Circle, ..] => "Starts with circle",
    [.., Rectangle] => "Ends with rectangle",
    [Circle, Rectangle, Triangle] => "Circle, rectangle, triangle in order",
    [var first, .., var last] => $"Multiple shapes: first {first.GetType().Name}, last {last.GetType().Name}",
    _ => "Various shapes"
};

// Użycie
var shapes1 = new Shape[] { new Circle { Radius = 5 } };
var shapes2 = new Shape[] { new Circle { Radius = 5 }, new Rectangle { Width = 10, Height = 20 } };

Console.WriteLine(DescribeShapes(shapes1));  // Single circle
Console.WriteLine(DescribeShapes(shapes2));  // Starts with circle
💡 Praktyczny przykład - visitor pattern z pattern matching
// Hierarchia - Expression Tree (AST)
public abstract record Expr;
public record Constant(double Value) : Expr;
public record Variable(string Name) : Expr;
public record BinaryOp(Expr Left, string Op, Expr Right) : Expr;

// Evaluator używa pattern matching zamiast visitor pattern!
double Evaluate(Expr expr, Dictionary variables) => expr switch
{
    Constant c => c.Value,
    Variable v => variables[v.Name],
    
    BinaryOp { Op: "+", Left: var l, Right: var r } 
        => Evaluate(l, variables) + Evaluate(r, variables),
    
    BinaryOp { Op: "-", Left: var l, Right: var r }
        => Evaluate(l, variables) - Evaluate(r, variables),
    
    BinaryOp { Op: "*", Left: var l, Right: var r }
        => Evaluate(l, variables) * Evaluate(r, variables),
    
    BinaryOp { Op: "/", Left: var l, Right: var r }
        => Evaluate(l, variables) / Evaluate(r, variables),
    
    _ => throw new InvalidOperationException()
};

// Użycie: (x + 5) * 2
var expr = new BinaryOp(
    new BinaryOp(new Variable("x"), "+", new Constant(5)),
    "*",
    new Constant(2)
);

var vars = new Dictionary { ["x"] = 10 };
var result = Evaluate(expr, vars);  // (10 + 5) * 2 = 30
Podsumowanie

Dziedziczenie i polimorfizm - fundamenty OOP + nowoczesne użycia:

  • Podstawy dziedziczenia - NIEZMIENNE od 2002! ⚪
  • virtual/override - polimorfizm, base keyword
  • abstract classes - szablony do implementacji
  • sealed - blokowanie dziedziczenia (performance, security)
  • 🔥 Default interface implementations (C# 8) - interfejsy z kodem!
  • Pattern matching z hierarchiami - type patterns, property patterns
  • List patterns (C# 11) - pattern matching na kolekcjach hierarchii
  • Abstract vs Interface - kiedy czego używać
  • override vs new - kluczowa różnica!

W kolejnym wpisie poznasz Interfejsy zaawansowane - static abstract members (C# 11), generic math, covariance/contravariance!

Zadanie dla Ciebie 🎯

Stwórz hierarchię klas dla systemu płatności:

// Abstract base class
public abstract class Payment
{
    public decimal Amount { get; init; }
    public DateTime Date { get; init; }
    
    // Abstract - każda płatność musi implementować
    public abstract bool Process();
    
    // Virtual - może być nadpisane
    public virtual string GetReceipt()
    {
        return $"Payment: ${Amount} on {Date:yyyy-MM-dd}";
    }
}

// Klasy pochodne do zaimplementowania:
// 1. CreditCardPayment(Amount, Date, CardNumber, CVV)
// 2. PayPalPayment(Amount, Date, Email)
// 3. CryptoPayment(Amount, Date, WalletAddress, CryptoType)

Wymagania:

  1. Każda klasa implementuje Process() inaczej
  2. CreditCardPayment override GetReceipt() - dodaje ostatnie 4 cyfry karty
  3. Interfejs IRefundable z default implementation Refund()
  4. Pattern matching funkcja GetPaymentMethod(Payment p)
  5. Sealed class dla GiftCardPayment
🎯 BONUS: Plugin System z Default Interface Implementations

Stwórz extensible plugin system używając C# 8 default interface implementations!

Wymagania:

  1. IPlugin interface:
    • string Name { get; }
    • string Version { get; }
    • void Initialize()
    • void Execute()
  2. Default implementations:
    • void OnLoad() - default: log "Plugin loaded"
    • void OnUnload() - default: log "Plugin unloaded"
    • bool IsCompatible(string hostVersion) - default: true
  3. Abstract base PluginBase:
    • Implementuje IPlugin
    • Abstract Execute()
    • Virtual GetDescription()
  4. Concrete plugins:
    • LoggerPlugin - sealed
    • CachePlugin - może override OnLoad/OnUnload
    • SecurityPlugin - custom IsCompatible
  5. PluginManager:
    • Load/Unload plugins
    • Pattern matching do filtrowania

Przykład użycia:

var manager = new PluginManager();

// Load plugins
manager.Register(new LoggerPlugin());
manager.Register(new CachePlugin());
manager.Register(new SecurityPlugin());

// Initialize all
manager.InitializeAll();

// Execute specific plugins
var active = manager.GetPlugins(p => p switch
{
    LoggerPlugin => true,
    CachePlugin { Version: "2.0" } => true,
    _ => false
});

foreach (var plugin in active)
{
    plugin.Execute();
}

// Cleanup
manager.UnloadAll();

Ten projekt łączy dziedziczenie, abstract classes, sealed, default interface implementations i pattern matching! 🚀✨