Kurz: Aktualizace rozhraní pomocí výchozích metod rozhraní

Implementaci můžete definovat, když deklarujete člena rozhraní. 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 čísly klienty.

V tomto kurzu se naučíte:

  • Rozšiřte rozhraní bezpečně přidáním metod s implementacemi.
  • Vytvořte parametrizované implementace, které poskytují větší flexibilitu.
  • Umožňuje implementátorům poskytnout konkrétnější implementaci ve formě přepsání.

Požadavky

Musíte nastavit počítač tak, aby běžel na platformě .NET, včetně kompilátoru jazyka C#. Kompilátor jazyka C# je k dispozici se sadou Visual Studio 2022 nebo sadou .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, chtěla, aby zákazníci se stávajícími aplikacemi přijali svou knihovnu. Poskytli uživatelům knihovny minimální definice rozhraní, které se mají 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 vztahy s novými zákazníky.

Teď je čas upgradovat knihovnu pro příští verzi. Jedna z požadovaných funkcí umožňuje slevu za věrnost zákazníkům, kteří mají spoustu objednávek. Tato nová věrnostní sleva se uplatní 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 může nastavit různá pravidla pro slevu za věrnost.

Nejpřirozenější způsob, jak tuto funkci přidat, je vylepšit ICustomer rozhraní metodou uplatnění jakékoli věrnostní slevy. Tento návrh návrhu způsobil obavy zkušených vývojářů: "Rozhraní jsou neměnná po vydání! Nedělejte zásadní změnu!" Pro upgrade rozhraní byste měli použít výchozí implementace rozhraní. Autoři knihovny mohou do rozhraní přidat nové členy a poskytnout výchozí implementaci pro tyto členy.

Výchozí implementace rozhraní umožňují vývojářům upgradovat rozhraní a zároveň povolit všem implementátorům přepsat tuto implementaci. Uživatelé knihovny můžou jako změnu způsobující chybu přijmout výchozí implementaci. Pokud se jejich obchodní pravidla liší, můžou je přepsat.

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

Tým se dohodl na nejpravděpodobnější výchozí implementaci: slevu za věrnost zákazníkům.

Upgrade by měl poskytovat funkce pro nastavení dvou vlastností: počet objednávek potřebných k získání slevy 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 všechny 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 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()}");

Toto přetypování ICustomer je SampleCustomer nezbytné. Třída SampleCustomer nemusí poskytovat implementaci , ComputeLoyaltyDiscountkterá je poskytována rozhraním ICustomer . SampleCustomer Třída však nedědí členy ze svých rozhraní. Toto pravidlo se nezměnilo. Aby bylo možné volat jakoukoli metodu deklarovanou a implementovanou v rozhraní, musí být proměnná typem rozhraní, ICustomer v tomto příkladu.

Zadání parametrizace

Výchozí implementace je příliš omezující. Mnoho spotřebitelů 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ší možnosti upgradu můžete poskytnout více zákazníkům 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 tom malém fragmentu kódu je zobrazeno mnoho nových jazykových funkcí. Rozhraní teď můžou zahrnovat statické členy, včetně polí a metod. Jsou povoleny také různé modifikátory přístupu. Ostatní pole jsou soukromá, nová metoda je veřejná. U členů rozhraní jsou povoleny některé modifikátory.

Aplikace, které používají obecný vzorec pro výpočet slevy za věrnost, ale různé parametry, nemusí poskytovat vlastní implementaci; mohou nastavit argumenty statickou metodou. Například následující kód nastaví "ocenění zákazníka", které odměňuje každého zákazníka s členstvím za více než jeden měsíc:

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 tyto scénáře, kdy uživatelé chtějí něco jako výchozí implementaci, nebo poskytnout nesouvisející sadu pravidel. V případě konečné funkce pojďme kód trochu refaktorovat, abychom umožnili scénáře, ve kterých by uživatelé mohli chtít vytvořit výchozí implementaci.

Zvažte 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 každá třída, která implementuje toto rozhraní, mohl znovu použít kód v jejich 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;
}

Při implementaci třídy, která implementuje toto rozhraní, může přepsání volat statickou pomocnou metodu a rozšířit tuto logiku tak, aby poskytovala slevu "nový zákazník":

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

Celý hotový kód můžete vidět 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í se dají bezpečně aktualizovat, když je pro tyto nové členy přiměřenou výchozí implementaci. Pečlivě navrhujte rozhraní pro vyjádření jednoduchých funkčních myšlenek implementovaných více třídami. To usnadňuje upgrade těchto definic rozhraní při zjištění nových požadavků pro stejnou funkční myšlenku.