Kurz: aktualizace rozhraní s použitím výchozích metod rozhraní v C# 8,0

Počínaje jazykem C# 8,0 pro .NET Core 3,0 můžete definovat implementaci při deklaraci člena rozhraní. Nejběžnějším scénářem je bezpečné přidání členů do rozhraní, které již bylo uvolněno a používáno klienty nespočet.

V tomto kurzu se naučíte:

  • Bezpečnější rozšíření rozhraní přidáním metod s implementacemi.
  • Vytváření parametrizovaných implementací k zajištění větší flexibility.
  • Povolit implementátorům poskytnutí konkrétnější implementace ve formě přepsání.

Požadavky

Musíte nastavit počítač tak, aby běžel .NET Core, včetně kompilátoru C# 8,0. kompilátor C# 8,0 je k dispozici počínaje verzí Visual Studio 2019 16,3 nebo .net Core 3,0 SDK.

Přehled scénáře

Tento kurz začíná verzí 1 knihovny vztahů se zákazníky. Úvodní aplikaci můžete získat na základě našich ukázek úložiště na GitHub. Společnost, která vytvořila tuto knihovnu, určila zákazníky, kteří mají existující aplikace k přijetí své knihovny. Poskytovaly minimální definice rozhraní pro uživatele své knihovny k implementaci. 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; }
}

Definují druhé rozhraní, které představuje objednávku:

public interface IOrder
{
    DateTime Purchased { get; }
    decimal Cost { get; }
}

Z těchto rozhraní může tým sestavit knihovnu pro své uživatele a vytvořit tak lepší prostředí pro své zákazníky. Jejich cílem bylo vytvoření hlubšího vztahu se stávajícími zákazníky a vylepšení jejich vztahů s novými zákazníky.

Teď je čas upgradovat knihovnu pro další verzi. Jedna z požadovaných funkcí umožňuje zákazníkům, kteří mají spoustu objednávek, věrnostní slevu. Tato nová věrnostní sleva se použije vždy, když zákazník provede objednávku. Konkrétní sleva je vlastnost každého jednotlivého zákazníka. Každá implementace ICustomer nástroje může nastavit jiná pravidla pro věrnostní slevu.

Nejpřirozenější způsob, jak tuto funkci přidat, je vylepšit ICustomer rozhraní s metodou pro použití věrnostní slevy. Tento návrh návrhu způsobil obavy mezi zkušenými vývojáři: "rozhraní jsou po vydání proměnlivá. Toto je zásadní změna. " C# 8,0 přidává výchozí implementaci rozhraní pro upgrade rozhraní. Autoři knihovny mohou přidat nové členy do rozhraní a poskytnout výchozí implementaci pro tyto členy.

Implementace výchozích rozhraní umožňují vývojářům upgradovat rozhraní a zároveň přitom povolit jakékoli implementátory k přepsání této implementace. Uživatelé knihovny mohou přijmout výchozí implementaci jako neprůlomou změnu. Pokud jsou jejich obchodní pravidla odlišná, můžou je přepsat.

Upgrade s použitím výchozích metod rozhraní

Tým se dohodl s nejpravděpodobnější výchozí implementací: věrnostní sleva pro zákazníky.

Upgrade by měl poskytovat funkce pro nastavení dvou vlastností: počet objednávek potřebných k poskytnutí slevy a procento slevy. Díky tomu je ideální scénář pro výchozí metody rozhraní. Do ICustomer rozhraní můžete přidat metodu a poskytnout nejpravděpodobnější implementaci. Všechny stávající a jakékoli nové implementace mohou použít výchozí implementaci nebo zadat vlastní.

Nejprve přidejte do rozhraní novou metodu, včetně těla 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 pro kontrolu implementace:

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()}");

Přetypování z SampleCustomer na ICustomer je nezbytné. SampleCustomerTřída nemusí poskytovat implementaci pro ComputeLoyaltyDiscount , která je poskytována ICustomer rozhraním. SampleCustomerTřída však nedědí členy z jeho rozhraní. Toto pravidlo se nezměnilo. Aby bylo možné volat jakoukoli metodu deklarovanou a implementovanou v rozhraní, proměnná musí být typu rozhraní, ICustomer v tomto příkladu.

Poskytněte Parametrizace

To je dobrý začátek. Výchozí implementace je ale 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 procentní slevu. Můžete zajistit lepší prostředí pro upgrade pro více zákazníků 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;
}

V této malém fragmentu kódu je zobrazený velký počet nových možností jazyka. Rozhraní teď můžou zahrnovat statické členy, včetně polí a metod. Jsou povolené taky různé modifikátory přístupu. Další pole jsou soukromá, nová metoda je veřejná. Všechny modifikátory jsou povoleny u členů rozhraní.

Aplikace, které používají obecný vzorec pro výpočet věrnostní slevy, ale jiné parametry, nepotřebují zadat vlastní implementaci; mohou nastavovat argumenty prostřednictvím statické metody. Například následující kód nastaví "zhodnocení zákazníka", které vyměňuje každého zákazníka s více než jedním měsícem členství:

ICustomer.SetLoyaltyThresholds(new TimeSpan(30, 0, 0, 0), 1, 0.25m);
Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}");

Rozšiřování výchozí implementace

Kód, který jste dosud přidali, měl k dispozici pohodlnou implementaci pro tyto scénáře, kde uživatelé mají něco podobného jako výchozí implementace, nebo poskytnutí nesouvisející sady pravidel. Pro závěrečnou funkci můžeme kód trochu přefaktorovat, aby bylo možné povolit scénáře, kde mohou uživatelé chtít sestavit výchozí implementaci.

Vezměte v úvahu spuštění, které chce přilákat nové zákazníky. Nabízí 50% slevu od prvního pořadí 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 jakákoliv třída implementující toto rozhraní mohla znovu použít kód v jejich implementaci. Výchozí implementace člena rozhraní volá i 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í zavolat statickou pomocnou metodu a tuto logiku zvětšit tak, aby poskytovala slevu "novému zákazníkovi":

public decimal ComputeLoyaltyDiscount()
{
   if (PreviousOrders.Any() == false)
        return 0.50m;
    else
        return ICustomer.DefaultLoyaltyDiscount(this);
}

Celý dokončený kód si můžete prohlédnout v části úložiště ukázek na GitHub. Úvodní aplikaci můžete získat na základě našich ukázek úložiště na GitHub.

Tyto nové funkce znamenají, že rozhraní je možné bezpečně aktualizovat, pokud existuje přiměřená výchozí implementace pro tyto nové členy. Pečlivě Navrhněte rozhraní pro vyjádření jednoduchých funkčních nápadů, které mohou být implementovány více třídami. To usnadňuje upgrade těchto definic rozhraní, když jsou zjištěny nové požadavky pro stejný funkční nápad.