Różne sposoby na implementacje interfejsu

Obrazek tytułowy

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:

Zrzut z ekranu visual studio, pokazujący brak dostępu do właściwości zaimplementowanych explicit.
Zrzut z visual studio

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:

6 thoughts on “Różne sposoby na implementacje interfejsu

  1. 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.

  2. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.