Kurz: Aktualizace rozhraní pomocí výchozích metod rozhraní
Při deklarování člena rozhraní můžete definovat implementaci. Nejběžnějším scénářem je bezpečné přidání členů do rozhraní, které už bylo vydáno a používáno nesčetnými klienty.
V tomto kurzu se naučíte:
- Rozšiřte rozhraní bezpečně přidáním metod s implementacemi.
- Vytvořte parametrizované implementace, abyste zajistili větší flexibilitu.
- Umožněte implementátorům poskytovat konkrétnější implementaci ve formě přepsání.
Požadavky
Musíte nastavit počítač tak, aby spouštět .NET, včetně kompilátoru jazyka C#. Kompilátor jazyka C# je k dispozici v sadě Visual Studio 2022 nebo v sadě .NET SDK.
Přehled scénáře
Tento kurz začíná verzí 1 knihovny vztahů se zákazníky. Úvodní aplikaci můžete získat v našem úložišti ukázek na GitHubu. Společnost, která tuto knihovnu vytvořila, zamýšlela zákazníky s existujícími aplikacemi, aby si osvojili svou knihovnu. Poskytli minimální definice rozhraní pro uživatele knihovny, které je možné implementovat. Tady je definice rozhraní pro zákazníka:
public interface ICustomer
{
IEnumerable<IOrder> PreviousOrders { get; }
DateTime DateJoined { get; }
DateTime? LastOrder { get; }
string Name { get; }
IDictionary<DateTime, string> Reminders { get; }
}
Definovali druhé rozhraní, které představuje pořadí:
public interface IOrder
{
DateTime Purchased { get; }
decimal Cost { get; }
}
Z těchto rozhraní by tým mohl vytvořit knihovnu pro své uživatele, aby pro své zákazníky vytvořil lepší prostředí. Jejich cílem bylo vytvořit hlubší vztah se stávajícími zákazníky a zlepšit jejich vztahy s novými zákazníky.
Teď je čas upgradovat knihovnu pro další vydání. Jedna z požadovaných funkcí umožňuje věrnostní slevu pro zákazníky, kteří mají velké množství objednávek. Tato nová věrnostní sleva se uplatní vždy, když zákazník zadá objednávku. Konkrétní sleva je vlastností každého jednotlivého zákazníka. Každá implementace může ICustomer
nastavit různá pravidla pro věrnostní slevu.
Nejpřirozenějším způsobem, jak tuto funkci přidat, je vylepšit ICustomer
rozhraní metodou pro uplatnění jakékoli věrnostní slevy. Tento návrh návrhu způsobil obavy mezi zkušenými vývojáři: "Rozhraní jsou po vydání neměnná! Nedělejte chybu!" Výchozí implementace rozhraní pro upgrade rozhraní Autoři knihovny můžou do rozhraní přidat nové členy a poskytnout pro tyto členy výchozí implementaci.
Implementace výchozího rozhraní umožňují vývojářům upgradovat rozhraní a zároveň umožnit všem implementátorům tuto implementaci přepsat. Uživatelé knihovny můžou přijmout výchozí implementaci jako zásadní změnu. Pokud se jejich obchodní pravidla liší, můžou přepsat.
Upgrade pomocí výchozích metod rozhraní
Tým se dohodl na nejpravděpodobnější výchozí implementaci: slevě za věrnost pro zákazníky.
Upgrade by měl poskytovat funkci pro nastavení dvou vlastností: počet objednávek potřebných pro nárok na slevu a procento slevy. Díky těmto funkcím je ideální scénář pro výchozí metody rozhraní. Do rozhraní můžete přidat metodu ICustomer
a poskytnout nejpravděpodobnější implementaci. Všechny existující a nové implementace můžou použít výchozí implementaci nebo poskytnout vlastní.
Nejprve přidejte novou metodu do rozhraní, včetně textu metody:
// Version 1:
public decimal ComputeLoyaltyDiscount()
{
DateTime TwoYearsAgo = DateTime.Now.AddYears(-2);
if ((DateJoined < TwoYearsAgo) && (PreviousOrders.Count() > 10))
{
return 0.10m;
}
return 0;
}
Autor knihovny napsal první test, který má zkontrolovat implementaci:
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()}");
Všimněte si následující části testu:
// Check the discount:
ICustomer theCustomer = c;
Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}");
To přetypování z SampleCustomer
do ICustomer
je nutné. Třída SampleCustomer
nemusí poskytovat implementaci pro ComputeLoyaltyDiscount
, která je poskytována rozhraním ICustomer
. Třída však nedědí SampleCustomer
členy ze svých rozhraní. Toto pravidlo se nezměnilo. Aby bylo možné volat jakoukoli metodu deklarovanou a implementovanou v rozhraní, proměnná musí být v tomto příkladu typem rozhraní ICustomer
.
Zadání parametrizace
Výchozí implementace je příliš omezující. Mnoho uživatelů tohoto systému může zvolit různé prahové hodnoty pro počet nákupů, jinou délku členství nebo jinou procentuální slevu. Lepší prostředí upgradu pro více zákazníků můžete poskytnout tím, že poskytnete způsob, jak tyto parametry nastavit. Pojďme přidat statickou metodu, která nastaví tyto tři parametry, které řídí výchozí implementaci:
// 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;
}
Tento malý fragment kódu obsahuje mnoho nových jazykových funkcí. Rozhraní teď můžou obsahovat statické členy, včetně polí a metod. Jsou také povolené různé modifikátory přístupu. Ostatní pole jsou soukromá, nová metoda je veřejná. U členů rozhraní jsou povoleny všechny modifikátory.
Aplikace, které používají obecný vzorec pro výpočet slevy za věrnost, ale různé parametry, nemusí poskytovat vlastní implementaci; můžou nastavit argumenty pomocí statické metody. Následující kód například nastaví "ocenění zákazníka", které odměňuje každého zákazníka s více než měsíčním členstvím:
ICustomer.SetLoyaltyThresholds(new TimeSpan(30, 0, 0, 0), 1, 0.25m);
Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}");
Rozšíření výchozí implementace
Kód, který jste zatím přidali, poskytuje pohodlnou implementaci pro scénáře, kdy uživatelé chtějí něco jako výchozí implementaci, nebo poskytnout nesouvisející sadu pravidel. Pro závěrečnou funkci pojďme kód trochu refaktorovat, abychom umožnili scénáře, ve kterých uživatelé můžou chtít stavět na výchozí implementaci.
Představte si startup, který chce přilákat nové zákazníky. Nabízejí 50% slevu z první objednávky nového zákazníka. V opačném případě stávající zákazníci získají standardní slevu. Autor knihovny musí přesunout výchozí implementaci do protected static
metody, aby všechny třídy implementují toto rozhraní mohly znovu použít kód ve své implementaci. Výchozí implementace člena rozhraní volá také tuto sdílenou metodu:
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;
}
V implementaci třídy, která implementuje toto rozhraní, může přepsání volat statickou pomocnou metodu a rozšířit tuto logiku o poskytnutí slevy "nového zákazníka":
public decimal ComputeLoyaltyDiscount()
{
if (PreviousOrders.Any() == false)
return 0.50m;
else
return ICustomer.DefaultLoyaltyDiscount(this);
}
Celý hotový kód si můžete prohlédnout v našem úložišti ukázek na GitHubu. Úvodní aplikaci můžete získat v našem úložišti ukázek na GitHubu.
Tyto nové funkce znamenají, že rozhraní je možné bezpečně aktualizovat, pokud pro tyto nové členy existuje rozumná výchozí implementace. Pečlivě navrhujte rozhraní pro vyjádření jednotlivých funkčních myšlenek implementovaných několika třídami. To usnadňuje upgrade těchto definic rozhraní, když se zjistí nové požadavky pro stejnou funkční myšlenku.