Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Implementację można zdefiniować podczas deklarowania członka interfejsu. Najbardziej typowym scenariuszem jest bezpieczne dodawanie członków do interfejsu, który został już wydany i używany przez niezliczonych klientów.
W tym poradniku dowiesz się, jak:
- Bezpieczne rozszerzanie interfejsów przez dodawanie metod za pomocą implementacji.
- Utwórz sparametryzowane implementacje, aby zapewnić większą elastyczność.
- Pozwól implementatorom zapewnić bardziej szczegółową implementację w formie przesłonięcia.
Wymagania wstępne
Musisz skonfigurować maszynę do uruchamiania platformy .NET, w tym kompilatora języka C#. Kompilator języka C# jest dostępny w programie Visual Studio 2022 lub .NET SDK.
Omówienie scenariusza
Ten samouczek rozpoczyna się od wersji 1 biblioteki relacji klienta. Aplikację startową można pobrać w repozytorium przykładów w witrynie GitHub. Firma, która utworzyła tę bibliotekę, zamierzała, aby klienci korzystający z istniejących aplikacji przyjęli ich bibliotekę. Udostępniali minimalne definicje interfejsów dla użytkowników biblioteki do zaimplementowania. Oto definicja interfejsu klienta:
public interface ICustomer
{
IEnumerable<IOrder> PreviousOrders { get; }
DateTime DateJoined { get; }
DateTime? LastOrder { get; }
string Name { get; }
IDictionary<DateTime, string> Reminders { get; }
}
Zdefiniowali drugi interfejs reprezentujący kolejność:
public interface IOrder
{
DateTime Purchased { get; }
decimal Cost { get; }
}
Dzięki tym interfejsom zespół może utworzyć bibliotekę dla swoich użytkowników, aby stworzyć lepsze środowisko dla swoich klientów. Ich celem było stworzenie głębszej relacji z istniejącymi klientami i ulepszenie relacji z nowymi klientami.
Teraz nadszedł czas, aby uaktualnić bibliotekę na potrzeby następnej wersji. Jedna z żądanych funkcji zapewnia rabat lojalnościowy dla klientów, którzy mają wiele zamówień. Ten nowy rabat lojalnościowy jest stosowany za każdym razem, gdy klient składa zamówienie. Określony rabat jest właściwością każdego klienta. Każda implementacja ICustomer
programu może ustawić różne reguły rabatu lojalnościowego.
Najbardziej naturalnym sposobem dodania tej funkcji jest ulepszenie interfejsu ICustomer
za pomocą metody stosowania rabatu lojalnościowego. Ta sugestia projektowa spowodowała obawy doświadczonych deweloperów: "Interfejsy są niezmienne po ich wydaniu! Nie wprowadzaj zmiany powodującej niezgodność!" Do uaktualniania interfejsów należy używać domyślnych implementacji interfejsów. Autorzy bibliotek mogą dodawać nowe elementy członkowskie do interfejsu i zapewniać domyślną implementację dla tych elementów członkowskich.
Implementacje interfejsu domyślnego umożliwiają deweloperom uaktualnianie interfejsu, jednocześnie umożliwiając wszystkim implementatorom zastąpienie tej implementacji. Użytkownicy biblioteki mogą zaakceptować domyślną implementację jako zmianę, która nie ulega zmianie. Jeśli ich reguły biznesowe są inne, mogą one je nadpisać.
Uaktualnianie przy użyciu domyślnych metod interfejsu
Zespół zgodził się na najbardziej prawdopodobną domyślną implementację: rabat lojalnościowy dla klientów.
Uaktualnienie powinno zapewnić funkcjonalność ustawiania dwóch właściwości: liczby zamówień potrzebnych do kwalifikowania się do rabatu oraz procentu rabatu. Te funkcje sprawiają, że jest to idealny scenariusz dla domyślnych metod interfejsu. Możesz dodać metodę do interfejsu ICustomer
i zapewnić najbardziej prawdopodobną implementację. Wszystkie istniejące i wszystkie nowe implementacje mogą używać implementacji domyślnej lub udostępniać własne.
Najpierw dodaj nową metodę do interfejsu, w tym treść metody:
// Version 1:
public decimal ComputeLoyaltyDiscount()
{
DateTime TwoYearsAgo = DateTime.Now.AddYears(-2);
if ((DateJoined < TwoYearsAgo) && (PreviousOrders.Count() > 10))
{
return 0.10m;
}
return 0;
}
Autor biblioteki napisał pierwszy test w celu sprawdzenia implementacji:
SampleCustomer c = new SampleCustomer("customer one", new DateTime(2010, 5, 31))
{
Reminders =
{
{ new DateTime(2010, 08, 12), "childs's birthday" },
{ new DateTime(1012, 11, 15), "anniversary" }
}
};
SampleOrder o = new SampleOrder(new DateTime(2012, 6, 1), 5m);
c.AddOrder(o);
o = new SampleOrder(new DateTime(2103, 7, 4), 25m);
c.AddOrder(o);
// Check the discount:
ICustomer theCustomer = c;
Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}");
Zwróć uwagę na następującą część testu:
// Check the discount:
ICustomer theCustomer = c;
Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}");
Ten rzut z SampleCustomer
na ICustomer
jest konieczny. Klasa SampleCustomer
nie musi dostarczać implementacji dla ComputeLoyaltyDiscount
; jest to zapewniane przez interfejs ICustomer
. Jednak SampleCustomer
klasa nie dziedziczy składowych ze swoich interfejsów. Ta reguła nie uległa zmianie. Aby wywołać dowolną metodę zadeklarowaną i zaimplementowaną w interfejsie, zmienna musi być typem interfejsu, ICustomer
w tym przykładzie.
Podaj parametryzację
Implementacja domyślna jest zbyt restrykcyjna. Wielu konsumentów tego systemu może wybrać różne progi dla liczby zakupów, innej długości członkostwa lub innego rabatu procentowego. Możesz zapewnić lepsze środowisko uaktualniania dla większej liczby klientów, zapewniając sposób ustawiania tych parametrów. Dodajmy metodę statyczną, która ustawia te trzy parametry kontrolujące implementację domyślną:
// Version 2:
public static void SetLoyaltyThresholds(
TimeSpan ago,
int minimumOrders = 10,
decimal percentageDiscount = 0.10m)
{
length = ago;
orderCount = minimumOrders;
discountPercent = percentageDiscount;
}
private static TimeSpan length = new TimeSpan(365 * 2, 0,0,0); // two years
private static int orderCount = 10;
private static decimal discountPercent = 0.10m;
public decimal ComputeLoyaltyDiscount()
{
DateTime start = DateTime.Now - length;
if ((DateJoined < start) && (PreviousOrders.Count() > orderCount))
{
return discountPercent;
}
return 0;
}
Istnieje wiele nowych możliwości języka pokazanych w tym małym fragmentcie kodu. Interfejsy mogą teraz zawierać statycznych członków, w tym pola i metody. Różne modyfikatory dostępu są również włączone. Inne pola są prywatne, nowa metoda jest publiczna. Każdy z modyfikatorów jest dozwolony dla członków interfejsu.
Aplikacje korzystające z formuły ogólnej do obliczania rabatu lojalnościowego, ale różne parametry, nie muszą zapewniać implementacji niestandardowej; mogą one ustawiać argumenty za pomocą metody statycznej. Na przykład poniższy kod określa "uznanie dla klientów", które nagradza każdego klienta z ponad jednym miesiącem członkostwa:
ICustomer.SetLoyaltyThresholds(new TimeSpan(30, 0, 0, 0), 1, 0.25m);
Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}");
Rozszerzanie implementacji domyślnej
Dodany do tej pory kod dostarczył wygodnej implementacji dla tych scenariuszy, w których użytkownicy chcą czegoś takiego jak implementacja domyślna lub udostępnić niepowiązany zestaw reguł. W przypadku ostatniej funkcji refaktoryzujmy nieco kod, aby umożliwić scenariusze, w których użytkownicy mogą chcieć opierać się na domyślnej implementacji.
Rozważmy startup, który chce przyciągnąć nowych klientów. Oferują one 50% zniżki od pierwszego zamówienia nowego klienta. W przeciwnym razie istniejący klienci otrzymują rabat standardowy. Autor biblioteki musi przenieść domyślną implementację do metody protected static
, aby każda klasa implementująca ten interfejs mogła ponownie wykorzystać kod w swojej implementacji. Domyślna implementacja elementu członkowskiego interfejsu wywołuje również tę udostępnioną metodę:
public decimal ComputeLoyaltyDiscount() => DefaultLoyaltyDiscount(this);
protected static decimal DefaultLoyaltyDiscount(ICustomer c)
{
DateTime start = DateTime.Now - length;
if ((c.DateJoined < start) && (c.PreviousOrders.Count() > orderCount))
{
return discountPercent;
}
return 0;
}
W implementacji klasy, która realizuje ten interfejs, metoda przesłaniająca może wywołać statyczną metodę pomocniczą i rozszerzyć tę logikę, aby zapewnić rabat dla „nowego klienta”.
public decimal ComputeLoyaltyDiscount()
{
if (PreviousOrders.Any() == false)
return 0.50m;
else
return ICustomer.DefaultLoyaltyDiscount(this);
}
Cały gotowy kod można zobaczyć w repozytorium przykładów w witrynie GitHub. Aplikację startową można pobrać w repozytorium przykładów w witrynie GitHub.
Te nowe funkcje oznaczają, że interfejsy można bezpiecznie aktualizować, gdy istnieje rozsądna implementacja domyślna dla tych nowych elementów. Starannie projektuj interfejsy, aby wyrazić pojedyncze pomysły funkcjonalne implementowane przez wiele klas. Ułatwia to uaktualnienie tych definicji interfejsu po odnalezieniu nowych wymagań dla tego samego pomysłu funkcjonalnego.