Jak to różne sposoby na implementacje interfejsu? To nie ma jednego właściwego sposobu na to? Bierzesz takiego, inspirujesz się nim, mówisz że będziesz jak on, spełniasz się, a potem robisz psikusa – jak, po co?
Niby wiesz że interfejs można implementować jawnie i niejawnie. Ale czy wiesz co z tego wynika? Czy wiesz co można z tym zrobić? Różne rzeczy, albo też nic.
Wersja youtube jest na dole.
Zacznijmy od samego interfejsu:
public interface IPerson | |
{ | |
string Firstname { get; set; } | |
string Lastname { get; set; } | |
string Fullname(); | |
} |
Prosty interfejs definiujący w jakiś sposób osobę, imię, nazwisko i dla oficjeli imię i nazwisko. Nic skomplikowanego.
Implementacja interfejsu:
public class OpenPerson : IPerson | |
{ | |
public string Firstname { get; set; } | |
public string Lastname { get; set; } | |
public string Fullname() => $"{this.Firstname} {this.Lastname}"; | |
} |
Wszystko jak najbardziej standardowo. Jest kontrakt, jest implementacja, jest obsługa. Jest cacy.
Różni ludzie, różne zachowania, różna implementacja:
public class ClosedPerson : IPerson | |
{ | |
string IPerson.Firstname { get; set; } | |
string IPerson.Lastname { get; set; } | |
public ClosedPerson(string firstname, string lastname) | |
{ | |
((IPerson)this).Firstname = firstname; | |
((IPerson)this).Lastname = lastname; | |
} | |
public string Fullname() => $"{((IPerson)this).Firstname} {((IPerson)this).Lastname}"; | |
} |
Zaczyna się. Na początku mówię, że właściwości są, ale tylko dlatego że spełniam kontrakt i tylko przez ten kontrakt będę je udostępniać. Jak developer kompilatorowi, tak kompilator developerowi. Aby w takiej sytuacji uzyskać dostęp do właściwości w konstruktorze będę musiał się rzutować na taki kontrakt. Podobnie w metodzie dla oficjeli, aby uzyskać dostęp do właściwości robię rzut.
Różnica w zapisie deklaracji metody Fullname a właściwości, powoduje to, że metoda jest dostępna w dla instancji ClosedPerson, podczas gdy Firstname oraz Lastname są dostępne tylko przez dostęp kontraktowy. Pacz na obrazek:

Zrób coś bardziej szalonego:
public class TrickyPerson : IPerson | |
{ | |
string IPerson.Firstname { get; set; } | |
string IPerson.Lastname { get; set; } | |
public string Firstname { get; set; } | |
public string Lastname { get; set; } | |
public TrickyPerson(string first, string last, string pfirst, string plast) | |
{ | |
this.Firstname = first; | |
this.Lastname = last; | |
((IPerson)this).Firstname = pfirst; | |
((IPerson)this).Lastname = plast; | |
} | |
string IPerson.Fullname() => $"{((IPerson)this).Firstname} {((IPerson)this).Lastname}"; | |
public string Fullname() => $"{this.Lastname} {this.Firstname}"; | |
} |
W tym momencie zwiększyłem poziom szaleństwa z poprzedniego przykładu i teraz klasa TrickyPerson w zależności od tego w jaki sposób (w zależności od typu) się do niej odwołamy będzie zachowywać się lekko inaczej. Łat?
Ale to nic, bo klimat zabawy można podkręcić jeszcze troszkę dalej:
public interface IPerson2 : IPerson | |
{ | |
string Fullname(); | |
} | |
public class ComplexPerson : IPerson2 | |
{ | |
public string Firstname { get; set; } | |
public string Lastname { get; set; } | |
string IPerson2.Fullname() => $"{Firstname} {Lastname}"; | |
string IPerson.Fullname() => $"{Lastname} {Firstname}"; | |
public string Fullname() => $"My name is {Lastname}, {Firstname} {Lastname}"; | |
} |
Dołożyłem kolejny interfejs, który ponownie definiuje metodę Fullname, a potem klasa która w zależności od tego jak się na nią spojrzy będzie zachowywać się inaczej.
To wszystko w połączone w jedność:
namespace ConsoleApp1 | |
{ | |
using System; | |
internal class Program | |
{ | |
private static void Main(string[] args) | |
{ | |
var cp = new ClosedPerson("jarek", "stadnicki"); | |
var op = new OpenPerson { Firstname = "jarek", Lastname = "stadnicki" }; | |
var tp = new TrickyPerson("jarek", "stadnicki", "mroczny", "bloger"); | |
var xp = new ComplexPerson { Firstname = "jaroslaw", Lastname = "stadnicki" }; | |
Console.WriteLine("-----------------------------"); | |
Console.WriteLine(cp.Fullname()); | |
Console.WriteLine(op.Fullname()); | |
Console.WriteLine(tp.Fullname()); | |
Console.WriteLine(xp.Fullname()); | |
Console.WriteLine("-----------------------------"); | |
PrintName(cp); | |
PrintName(op); | |
PrintName(tp); | |
PrintName(xp); | |
Console.WriteLine("-----------------------------"); | |
PrintName2(xp); | |
} | |
private static void PrintName(IPerson person) => Console.WriteLine(person.Fullname()); | |
private static void PrintName2(IPerson2 person) => Console.WriteLine(person.Fullname()); | |
} | |
public interface IPerson | |
{ | |
string Firstname { get; set; } | |
string Lastname { get; set; } | |
string Fullname(); | |
} | |
public interface IPerson2 : IPerson | |
{ | |
string Fullname(); | |
} | |
public class ComplexPerson : IPerson2 | |
{ | |
public string Firstname { get; set; } | |
public string Lastname { get; set; } | |
string IPerson2.Fullname() => $"{this.Firstname} {this.Lastname}"; | |
string IPerson.Fullname() => $"{this.Lastname} {this.Firstname}"; | |
public string Fullname() => $"My name is {this.Lastname}, {this.Firstname} {this.Lastname}"; | |
} | |
public class TrickyPerson : IPerson | |
{ | |
public TrickyPerson(string first, string last, string pfirst, string plast) | |
{ | |
this.Firstname = first; | |
this.Lastname = last; | |
((IPerson)this).Firstname = pfirst; | |
((IPerson)this).Lastname = plast; | |
} | |
public string Firstname { get; set; } | |
public string Lastname { get; set; } | |
string IPerson.Firstname { get; set; } | |
string IPerson.Lastname { get; set; } | |
string IPerson.Fullname() => $"{((IPerson)this).Firstname} {((IPerson)this).Lastname}"; | |
public string Fullname() => $"{this.Lastname} {this.Firstname}"; | |
} | |
public class OpenPerson : IPerson | |
{ | |
public string Firstname { get; set; } | |
public string Lastname { get; set; } | |
public string Fullname() => $"{this.Firstname} {this.Lastname}"; | |
} | |
public class ClosedPerson : IPerson | |
{ | |
public ClosedPerson(string firstname, string lastname) | |
{ | |
((IPerson)this).Firstname = firstname; | |
((IPerson)this).Lastname = lastname; | |
} | |
string IPerson.Firstname { get; set; } | |
string IPerson.Lastname { get; set; } | |
public string Fullname() => $"{((IPerson)this).Firstname} {((IPerson)this).Lastname}"; | |
} | |
} |
Co spowoduje taki output na ekranie:
----------------------------- | |
jarek stadnicki | |
jarek stadnicki | |
stadnicki jarek | |
My name is stadnicki, jaroslaw stadnicki | |
----------------------------- | |
jarek stadnicki | |
jarek stadnicki | |
mroczny bloger | |
stadnicki jaroslaw | |
----------------------------- | |
jaroslaw stadnicki | |
Press any key to continue . . . |
Co dalej?
Na plus, jeśli nie korzystasz z abstrakcji, możesz ukryć część implementacji przed innymi programistami, np. publiczne setery i wymusić, aby wszystko zostało podane przez konstruktor.
Możesz wymusić korzystanie z abstrakcji, poprzez np. rzucanie wyjątków gdy ktoś odwołuje się do konkretnej implementacji klasy a nie kontraktu.
Możesz korzystać z rozszerzonego logowania gdy korzystasz z konkretnej klasy.
Ale ponad wszystko pamiętaj aby nie robić czegoś zupełnie, zupełnie innego niż mówi metoda, czy czegoś czego nie spodziewa się użytkownik twojej klasy. Chciałem napisać o zasadzie Baśki (SOLID), ale w ciężko mi tutaj się do niej odwołać, bo tutaj jest implementacja interfejsu a nie zupełniej innej klasy. Ale warto mieć to w głowie.
Nagranie do wpisu:
Chyba gdzieś >>nie<< zjadło w tym zdaniu
Ale ponad wszystko pamiętaj aby robić czegoś zupełnie, zupełnie innego niż mówi metoda, czy czegoś czego nie spodziewa się użytkownik twojej klasy.
Podziękował i poprawił
To że można coś zrobić, nie znaczy że powinno się. Haki są spoko (np. wykorzystywanie mało znany mechanizmów języka) dopóki są eleganckie i czytelne dla obiorców
Ale na pewno takie eksperymenty są odkrywcze i pouczające.
Jasna sprawa, trochę jak z bronią palną. To że trzymasz ją w ręku nie znaczy, że masz strzelać do wszystkich w zasięgu widzenia
Jak na razie dopiero wchodzę w to wszystko, więc dla mnie czarna magia, ale dodaję bloga do ulubionych i do zakładek. Czekam na kolejne wpisy
Dzięki. Będą. Ogarniam prezentację na najbliższe wystąpienie.