Megosztás a következőn keresztül:


A meghatalmazottak gyakori mintái

Előző

A meghatalmazottak olyan mechanizmust biztosítanak, amely lehetővé teszi az összetevők közötti minimális összekapcsolást magában foglaló szoftverterveket.

Az ilyen típusú kialakításra kiváló példa a LINQ. A LINQ lekérdezési kifejezési minta minden funkciójának meghatalmazottjaira támaszkodik. Tekintse meg ezt az egyszerű példát:

var smallNumbers = numbers.Where(n => n < 10);

Ez a számsort csak a 10-nél kisebb értékekre szűri. A Where metódus egy delegáltat használ, amely meghatározza, hogy a sorozat mely elemei haladnak át a szűrőn. LINQ-lekérdezés létrehozásakor meg kell adnia a meghatalmazott implementációját erre a célra.

A Where metódus prototípusa:

public static IEnumerable<TSource> Where<TSource> (this IEnumerable<TSource> source, Func<TSource, bool> predicate);

Ez a példa a LINQ részét képező összes metódussal ismétlődik. Mindegyik az adott lekérdezést kezelő kód delegáltjaira támaszkodik. Ez az API-tervezési minta hatékony tanuláshoz és megértéshez.

Ez az egyszerű példa azt szemlélteti, hogy a delegáltaknak nagyon kevés kapcsolatra van szükségük az összetevők között. Nem kell olyan osztályt létrehoznia, amely egy adott alaposztályból származik. Nem kell implementálnia egy adott felületet. Az egyetlen követelmény az, hogy egy, a feladat szempontjából alapvető fontosságú módszer implementálását biztosítsuk.

Saját összetevők létrehozása meghatalmazottakkal

Erre a példára építve hozzunk létre egy olyan összetevőt, amely a meghatalmazottakra támaszkodik.

Határozzunk meg egy olyan összetevőt, amely egy nagy rendszerben használható naplóüzenetekhez. A kódtár összetevői számos különböző környezetben, több különböző platformon használhatók. A naplókat kezelő összetevő számos gyakori funkciót tartalmaz. A rendszer bármely összetevőjének üzeneteit el kell fogadnia. Ezek az üzenetek különböző prioritásokkal rendelkeznek, amelyeket az alapvető összetevő kezelni tud. Az üzeneteknek időbélyegzőkkel kell rendelkezniük a végleges archivált formájukban. Speciálisabb forgatókönyvek esetén szűrheti az üzeneteket a forrásösszetevő alapján.

A funkciónak van egy olyan aspektusa, amely gyakran változik: az üzenetek írásának helye. Egyes környezetekben előfordulhat, hogy a hibakonzolra írnak. Máshol egy fájl. Más lehetőségek közé tartozik az adatbázis-tárolás, az operációs rendszer eseménynaplói vagy más dokumentumtárolás.

A kimenetnek vannak olyan kombinációi is, amelyek különböző forgatókönyvekben használhatók. Érdemes lehet üzeneteket írni a konzolra és egy fájlba.

A meghatalmazottakon alapuló kialakítás nagy rugalmasságot biztosít, és megkönnyíti a jövőben hozzáadható tárolási mechanizmusok támogatását.

Ebben a kialakításban az elsődleges naplóösszetevő lehet nem virtuális, akár lezárt osztály is. Az üzenetek különböző tárolókba való írásához bármilyen meghatalmazottat csatlakoztathat. A csoportos küldési meghatalmazottak beépített támogatása megkönnyíti azokat a forgatókönyveket, amelyekben az üzeneteket több helyre (fájlba és konzolra) kell írni.

Első implementáció

Kezdjük kicsivel: a kezdeti implementáció elfogadja az új üzeneteket, és bármilyen csatolt meghatalmazott használatával megírja őket. Kezdjük azzal a meghatalmazottal, aki üzeneteket ír a konzolra.

public static class Logger
{
    public static Action<string>? WriteMessage;

    public static void LogMessage(string msg)
    {
        if (WriteMessage is not null)
            WriteMessage(msg);
    }
}

A fenti statikus osztály a legegyszerűbb dolog, ami működik. Meg kell írnunk az üzeneteket a konzolra író metódus egyetlen implementációját:

public static class LoggingMethods
{
    public static void LogToConsole(string message)
    {
        Console.Error.WriteLine(message);
    }
}

Végül csatlakoztatnia kell a delegáltat a naplózóban deklarált WriteMessage-meghatalmazotthoz csatolva:

Logger.WriteMessage += LoggingMethods.LogToConsole;

Eljárások

Az eddigi minta meglehetősen egyszerű, de még mindig bemutatja a meghatalmazottakat érintő tervek néhány fontos irányelvét.

Az alapvető keretrendszerben definiált delegálási típusok használatával a felhasználók könnyebben dolgozhatnak a meghatalmazottakkal. Nem kell új típusokat definiálnia, és a kódtárat használó fejlesztőknek nem kell új, speciális delegálási típusokat megtanulniuk.

A használt felületek a lehető legkisebbek és rugalmasak: Új kimeneti naplózó létrehozásához létre kell hoznia egy metódust. Ez a módszer lehet statikus vagy példánymetódus. Lehet, hogy bármilyen hozzáféréssel rendelkezik.

Kimenet formázása

Tegyük ezt az első verziót egy kicsit robusztusabbá, majd hozzunk létre más naplózási mechanizmusokat.

Ezután adjunk hozzá néhány argumentumot a LogMessage() metódushoz, hogy a naplóosztály strukturáltabb üzeneteket hozzon létre:

public enum Severity
{
    Verbose,
    Trace,
    Information,
    Warning,
    Error,
    Critical
}
public static class Logger
{
    public static Action<string>? WriteMessage;

    public static void LogMessage(Severity s, string component, string msg)
    {
        var outputMsg = $"{DateTime.Now}\t{s}\t{component}\t{msg}";
        if (WriteMessage is not null)
            WriteMessage(outputMsg);
    }
}

Ezután használjuk ezt az Severity argumentumot a napló kimenetére küldött üzenetek szűréséhez.

public static class Logger
{
    public static Action<string>? WriteMessage;

    public static Severity LogLevel { get; set; } = Severity.Warning;

    public static void LogMessage(Severity s, string component, string msg)
    {
        if (s < LogLevel)
            return;

        var outputMsg = $"{DateTime.Now}\t{s}\t{component}\t{msg}";
        if (WriteMessage is not null)
            WriteMessage(outputMsg);
    }
}

Eljárások

Új funkciókat adott hozzá a naplózási infrastruktúrához. Mivel a naplózó összetevő nagyon lazán kapcsolódik bármely kimeneti mechanizmushoz, ezek az új funkciók a naplózó delegáltját implementáló kód egyikére sem lesznek hatással.

Ahogy ezt tovább építi, további példákat láthat arra, hogy ez a laza összekapcsolás hogyan teszi lehetővé a webhely egyes részeinek nagyobb rugalmasságát anélkül, hogy más helyeket módosítanak. Valójában egy nagyobb alkalmazásban a naplózó kimeneti osztályai egy másik szerelvényben lehetnek, és nem is kell újraépíteni.

Második kimeneti motor létrehozása

A naplóösszetevő jól halad. Adjunk hozzá még egy kimeneti motort, amely naplózza az üzeneteket egy fájlban. Ez egy kicsit jobban érintett kimeneti motor lesz. Ez egy osztály lesz, amely beágyazza a fájlműveleteket, és biztosítja, hogy a fájl minden írás után mindig zárva legyen. Ez biztosítja, hogy az egyes üzenetek létrehozása után az összes adat ki legyen ürítve a lemezre.

Ez a fájlalapú naplózó:

public class FileLogger
{
    private readonly string logPath;
    public FileLogger(string path)
    {
        logPath = path;
        Logger.WriteMessage += LogMessage;
    }

    public void DetachLog() => Logger.WriteMessage -= LogMessage;
    // make sure this can't throw.
    private void LogMessage(string msg)
    {
        try
        {
            using (var log = File.AppendText(logPath))
            {
                log.WriteLine(msg);
                log.Flush();
            }
        }
        catch (Exception)
        {
            // Hmm. We caught an exception while
            // logging. We can't really log the
            // problem (since it's the log that's failing).
            // So, while normally, catching an exception
            // and doing nothing isn't wise, it's really the
            // only reasonable option here.
        }
    }
}

Miután létrehozta ezt az osztályt, példányosíthatja azt, és a LogMessage metódust a Logger összetevőhöz csatolja:

var file = new FileLogger("log.txt");

Ez a kettő nem zárja ki egymást. A naplómetelyeket is csatolhatja, és üzeneteket hozhat létre a konzolon és egy fájlban:

var fileOutput = new FileLogger("log.txt");
Logger.WriteMessage += LoggingMethods.LogToConsole; // LoggingMethods is the static class we utilized earlier

Később, még ugyanabban az alkalmazásban is eltávolíthatja az egyik meghatalmazottat anélkül, hogy bármilyen más probléma merült fel a rendszerben:

Logger.WriteMessage -= LoggingMethods.LogToConsole;

Eljárások

Most hozzáadott egy második kimeneti kezelőt a naplózási alrendszerhez. Ehhez egy kicsit több infrastruktúrára van szükség a fájlrendszer megfelelő támogatásához. A meghatalmazott egy példánymetódus. Ez egy privát módszer is. Nincs szükség nagyobb akadálymentességre, mert a meghatalmazotti infrastruktúra képes csatlakoztatni a meghatalmazottakat.

Másodszor, a delegáltalapú kialakítás több kimeneti metódust is lehetővé tesz további kód nélkül. Nem kell további infrastruktúrát létrehoznia több kimeneti módszer támogatásához. Egyszerűen egy másik metódussá válnak a meghívási listán.

Különös figyelmet kell fordítani a fájlnaplózás kimeneti metódusában található kódra. Kódolt annak biztosítása érdekében, hogy ne legyen kivétel. Bár ez nem mindig feltétlenül szükséges, ez gyakran jó gyakorlat. Ha a delegálási metódusok bármelyike kivételt jelez, a meghívásban szereplő többi meghatalmazott nem lesz meghívva.

Utolsó megjegyzésként a fájlnaplózónak az egyes naplóüzenetek fájljának megnyitásával és bezárásával kell kezelnie az erőforrásait. Dönthet úgy is, hogy a fájl nyitva marad, és implementálva IDisposable szeretné bezárni a fájlt, amikor elkészült. Bármelyik módszernek megvannak az előnyei és hátrányai. Mindkettő egy kicsit jobban összekapcsolja az osztályokat.

Az osztály egyik Logger kódját sem kell frissíteni mindkét forgatókönyv támogatásához.

Null meghatalmazottak kezelése

Végül frissítse a LogMessage metódust, hogy az olyan esetekben is hatékony legyen, amikor nincs kimeneti mechanizmus kiválasztva. Az aktuális implementáció akkor jelenik meg NullReferenceException , ha a WriteMessage meghatalmazott nem rendelkezik csatolt meghívási listával. Érdemes lehet olyan kialakítást használni, amely csendesen folytatódik, ha nem csatolták a metódusokat. A null feltételes operátor használata egyszerű, a Delegate.Invoke() metódussal kombinálva:

public static void LogMessage(string msg)
{
    WriteMessage?.Invoke(msg);
}

A null feltételes operátor (?.) rövidzárlatai, ha a bal operandus (WriteMessage ebben az esetben) null, ami azt jelenti, hogy nem történik kísérlet az üzenet naplózására.

A dokumentációban nem találja a Invoke() metódust System.DelegateSystem.MulticastDelegate. A fordító létrehoz egy típusbiztos Invoke metódust minden deklarált deklarált típushoz. Ebben a példában ez azt jelenti Invoke , hogy egyetlen string argumentumot vesz fel, és érvénytelen visszatérési típussal rendelkezik.

A gyakorlatok összefoglalása

Láthatta egy olyan naplóösszetevő kezdetét, amely más írókkal és más funkciókkal bővíthető. Ha meghatalmazottakat használ a tervben, ezek a különböző összetevők lazán össze vannak osztva. Ez számos előnnyel jár. Könnyen létrehozhat új kimeneti mechanizmusokat, és csatolhatja őket a rendszerhez. Ezeknek a mechanizmusoknak csak egy metódusra van szükségük: a naplóüzenetet megíró metódusra. Ez egy olyan kialakítás, amely rugalmas az új funkciók hozzáadásakor. Az írók számára kötelező szerződés egy metódus implementálása. Ez a módszer lehet statikus vagy példánymetódus. Lehet nyilvános, privát vagy bármilyen más jogi hozzáférés.

A Logger osztály bármilyen számú fejlesztést vagy módosítást hajthat végre anélkül, hogy kompatibilitástörő módosításokat vezet be. Mint minden osztály, a nyilvános API-t sem módosíthatja a módosítások feltörésének veszélye nélkül. Mivel azonban a naplózó és a kimeneti motorok közötti összekapcsolás csak a delegálton keresztül történik, más típusok (például interfészek vagy alaposztályok) nem vesznek részt. A csatlakozó a lehető legkisebb.

Következő