Oktatóanyag: Interfészek frissítése alapértelmezett felületi módszerekkel

Implementációt akkor határozhat meg, ha egy felület tagját deklarálja. A leggyakoribb forgatókönyv az, hogy a tagok biztonságosan hozzáadhatók a már kiadott és számtalan ügyfél által használt felülethez.

Az oktatóanyag segítségével megtanulhatja a következőket:

  • Az interfészek biztonságos kiterjesztése metódusok implementációkkal való hozzáadásával.
  • Hozzon létre paraméteres implementációkat a nagyobb rugalmasság érdekében.
  • Lehetővé teszi a implementátorok számára, hogy felülbírálás formájában konkrétabb implementációt nyújtsanak.

Előfeltételek

Be kell állítania a gépet a .NET futtatására, beleértve a C# fordítót is. A C# fordító a Visual Studio 2022-ben vagy a .NET SDK-val érhető el.

Forgatókönyv áttekintése

Ez az oktatóanyag egy ügyfélkapcsolati kódtár 1. verziójával kezdődik. A kezdőalkalmazást a GitHub minta-adattárában szerezheti be. A könyvtárat építve a meglévő alkalmazásokkal rendelkező ügyfeleknek szánták a könyvtár bevezetését. Minimális felületdefiníciókat biztosítottak a tár felhasználói számára a megvalósításhoz. Az ügyfél felületdefiníciója a következő:

public interface ICustomer
{
    IEnumerable<IOrder> PreviousOrders { get; }

    DateTime DateJoined { get; }
    DateTime? LastOrder { get; }
    string Name { get; }
    IDictionary<DateTime, string> Reminders { get; }
}

Meghatároztak egy második felületet, amely egy sorrendet jelöl:

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

Ezekből a felületekből a csapat létrehozhat egy tárat a felhasználók számára, hogy jobb élményt teremtsen az ügyfeleik számára. Céljuk a meglévő ügyfelekkel való mélyebb kapcsolat létrehozása és az új ügyfelekkel való kapcsolatok javítása volt.

Most itt az ideje, hogy frissítse a kódtárat a következő kiadásra. Az egyik kért funkció hűségkedvezményt biztosít a sok megrendeléssel rendelkező ügyfelek számára. Ez az új hűségkedvezmény minden alkalommal érvényes lesz, amikor egy ügyfél megrendelést végez. Az adott kedvezmény az egyes ügyfelek tulajdona. Az egyes implementációk ICustomer különböző szabályokat állíthatnak be a hűségkedvezményre.

Ennek a funkciónak a hozzáadásának legtermészetesebb módja az ICustomer interfész továbbfejlesztése egy olyan módszerrel, amellyel bármilyen hűségkedvezményt alkalmazhat. Ez a tervezési javaslat aggodalmat okozott a tapasztalt fejlesztők körében: "A felületek nem módosíthatók, miután megjelentek! Ne változtassa meg a törést!" Az interfészek frissítéséhez alapértelmezett felületi implementációkat kell használnia. A kódtár szerzői új tagokat adhatnak a felülethez, és alapértelmezett implementációt biztosíthatnak ezeknek a tagoknak.

Az alapértelmezett felületi implementációk lehetővé teszik a fejlesztők számára, hogy frissítsenek egy felületet, miközben a implementátorok továbbra is felülbírálhatják az implementációt. A kódtár felhasználói az alapértelmezett implementációt nem kompatibilitástörő változásként fogadhatják el. Ha az üzleti szabályaik eltérnek, felülbírálhatják őket.

Frissítés alapértelmezett felületi módszerekkel

A csapat megállapodott a legvalószínűbb alapértelmezett implementációról: hűségkedvezményt az ügyfelek számára.

A frissítésnek két tulajdonságot kell megadnia: a kedvezményre való jogosultsághoz szükséges rendelések számát és a kedvezmény százalékos arányát. Ezek a funkciók tökéletes forgatókönyvet alkotnak az alapértelmezett felületi módszerekhez. Hozzáadhat egy metódust az ICustomer interfészhez, és megadhatja a legvalószínűbb implementációt. Minden meglévő és minden új implementáció használhatja az alapértelmezett implementációt, vagy sajátot biztosíthat.

Először adja hozzá az új metódust a felülethez, beleértve a metódus törzsét is:

// Version 1:
public decimal ComputeLoyaltyDiscount()
{
    DateTime TwoYearsAgo = DateTime.Now.AddYears(-2);
    if ((DateJoined < TwoYearsAgo) && (PreviousOrders.Count() > 10))
    {
        return 0.10m;
    }
    return 0;
}

A kódtár szerzője írt egy első tesztet a megvalósítás ellenőrzéséhez:

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

Figyelje meg a teszt alábbi részét:

// Check the discount:
ICustomer theCustomer = c;
Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}");

Ez a szereposztás SampleCustomerICustomer szükséges. Az SampleCustomer osztálynak nem kell implementációt biztosítania a felület által ICustomer biztosított ; számáraComputeLoyaltyDiscount. Az SampleCustomer osztály azonban nem örökli a tagokat a felületéről. Ez a szabály nem változott. Az illesztőben deklarált és implementált metódus meghívásához a változónak az interfész típusának kell lennie ebben ICustomer a példában.

Paraméterezés biztosítása

Az alapértelmezett implementáció túl korlátozó. A rendszer számos fogyasztója különböző küszöbértékeket választhat a vásárlások számához, a tagság eltérő hosszához vagy eltérő százalékos kedvezményhez. A paraméterek beállításával jobb frissítési élményt biztosíthat több ügyfél számára. Adjunk hozzá egy statikus metódust, amely beállítja az alapértelmezett implementációt vezérlő három paramétert:

// 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;
}

Ebben a kis kódrészletben számos új nyelvi képesség jelenik meg. Az interfészek mostantól statikus tagokat is tartalmazhatnak, beleértve a mezőket és a metódusokat is. A különböző hozzáférési módosítók is engedélyezve vannak. A többi mező privát, az új metódus nyilvános. A módosítók bármelyike engedélyezve van az interfésztagokon.

Azok az alkalmazások, amelyek a hűségkedvezmény kiszámításához az általános képletet használják, de különböző paraméterekkel, nem kell egyéni implementációt biztosítaniuk; statikus módszerrel állíthatják be az argumentumokat. Az alábbi kód például egy "ügyfélértékelést" állít be, amely egy hónapnál több tagsággal rendelkező ügyfelet jutalmaz:

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

Az alapértelmezett implementáció kiterjesztése

Az eddig hozzáadott kód kényelmes implementációt biztosított azokhoz a forgatókönyvekhez, amelyekben a felhasználók az alapértelmezett implementációhoz hasonlót szeretnének, vagy egy nem kapcsolódó szabálykészletet szeretnének biztosítani. A végső funkcióhoz vizsgáljuk meg újra a kódot egy kicsit, hogy olyan forgatókönyveket engedélyezzünk, amelyekben a felhasználók esetleg az alapértelmezett implementációra szeretnének építeni.

Fontolja meg egy olyan startupot, amely új ügyfeleket szeretne vonzani. 50%-os kedvezményt kínálnak egy új ügyfél első megrendeléséről. Ellenkező esetben a meglévő ügyfelek megkapják a standard kedvezményt. A kódtár szerzőjének át kell helyeznie az alapértelmezett implementációt egy protected static metódusba, hogy a felületet megvalósító bármely osztály újra felhasználhassa a kódot a megvalósításban. A felülettag alapértelmezett implementációja ezt a megosztott módszert is meghívja:

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;
}

Az interfészt implementáló osztály implementációjában a felülbírálás meghívhatja a statikus segédmetódust, és kiterjesztheti ezt a logikát az "új ügyfél" kedvezmény biztosítására:

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

A teljes kész kódot a GitHubon található minta-adattárban tekintheti meg. A kezdőalkalmazást a GitHub minta-adattárában szerezheti be.

Ezek az új funkciók azt jelentik, hogy az interfészek biztonságosan frissíthetők, ha ésszerű alapértelmezett implementáció áll rendelkezésre az új tagok számára. Gondosan tervezze meg a felületeket, hogy kifejezze a több osztály által megvalósított egyetlen funkcionális ötleteket. Ez megkönnyíti a felületdefiníciók frissítését, amikor új követelményeket fedeznek fel ugyanahhoz a funkcionális ötlethez.