Temsilciler için ortak desenler

Önceki

Temsilciler, bileşenler arasında minimum eşleşme içeren yazılım tasarımlarına olanak sağlayan bir mekanizma sağlar.

Bu tür bir tasarım için linq harika bir örnektir. LINQ Sorgu İfadesi Düzeni, tüm özellikleri için temsilcilerden kullanır. Bu basit örneği düşünün:

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

Bu, sayı dizisini yalnızca 10 değerinden küçük olanlar için filtreler. yöntemi, Where bir sıranın hangi öğelerinin filtreyi geçeceği belirleyen bir temsilci kullanır. LinQ sorgusu oluştururken, bu özel amaç için temsilcinin uygulamasını sağlarsınız.

Where yönteminin prototipi şu şekildedir:

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

Bu örnek LINQ'un parçası olan tüm yöntemlerle yinelenir. Hepsi, belirli bir sorguyu yöneten kod için temsilcilere güvener. Bu API tasarım deseni, öğrenilen ve anilen güçlü bir modeldir.

Bu basit örnek, temsilcilerin bileşenler arasında nasıl çok az eşleşme gerektirmesi olduğunu göstermektedir. Belirli bir temel sınıftan türetilen bir sınıf oluşturmanıza gerek yok. Belirli bir arabirim uygulamaya gerek yok. Tek gereksinim, var olan görev için temel olan bir yöntemin uygulanmasını sağlamaktır.

Temsilcilerle Kendi Bileşenlerinizi Oluşturma

Temsilcilere dayanan bir tasarım kullanarak bir bileşen oluşturarak bu örneği temel alalım.

Şimdi büyük bir sistemde günlük iletileri için kullanılmaktadır. Kitaplık bileşenleri birçok farklı ortamda, birden çok farklı platformda kullanılabilir. Bileşenin günlükleri yöneten birçok yaygın özelliği vardır. Sistemde herhangi bir bileşenden gelen iletileri kabul etmek gerekir. Bu iletilerin, temel bileşenin yönetecekleri farklı öncelikleri vardır. İletilerin son arşivlenmiş biçimlerinde zaman damgası olması gerekir. Daha gelişmiş senaryolar için iletileri kaynak bileşene göre filtreleyebilirsiniz.

Özelliğin sıklıkla değişecek bir yönü vardır: iletilerin yazıldığı yer. Bazı ortamlarda bunlar hata konsoluna yazabilir. Diğerleri ise bir dosyasıdır. Diğer olasılıklar arasında veritabanı depolama alanı, işletim sistemi olay günlükleri veya diğer belge depolaması yer alır.

Farklı senaryolarda kullanılmaktadır çıkış bileşimleri de vardır. Konsola ve bir dosyaya ileti yazmak istiyor olabilirsiniz.

Temsilcileri temel alan bir tasarım büyük esneklik sağlar ve gelecekte eklenebilir depolama mekanizmalarını desteklemeyi kolaylaştırır.

Bu tasarım kapsamında, birincil günlük bileşeni sanal olmayan, hatta korumalı bir sınıf olabilir. İletileri farklı depolama medyalarına yazmak için herhangi bir temsilci kümesi takabilirsiniz. Çok noktaya yayın temsilcileri için yerleşik destek, iletilerin birden çok konuma (bir dosya ve bir konsol) yazıldığı senaryoları desteklemeyi kolaylaştırır.

İlk Uygulama

Küçük bir başlangıçla başlayalım: ilk uygulama yeni iletileri kabul eder ve ekli herhangi bir temsilciyi kullanarak bunları yazar. Konsola ileti yazan bir temsilciyle başlayabilirsiniz.

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

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

Yukarıdaki statik sınıf, çalışan en basit şeydir. Konsola ileti yazan yöntemi için tek bir uygulama yazmamız gerekir:

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

Son olarak, günlükte bildirilen WriteMessage temsilcisine ek olarak temsilciyi bağlamanız gerekir:

Logger.WriteMessage += LoggingMethods.LogToConsole;

Uygulama

Örneğimiz şu ana kadar oldukça basittir, ancak temsilciler içeren tasarımlara ilişkin bazı önemli yönergeleri yine de gösteriyor.

Çekirdek çerçevede tanımlanan temsilci türlerini kullanmak, kullanıcıların temsilcilerle daha kolay çalışmasına neden olur. Yeni türler tanımlamanız ve kitaplığınızı kullanan geliştiricilerin yeni, özelleştirilmiş temsilci türlerini öğrenmesi gerek değildir.

Kullanılan arabirimler mümkün olduğunca az ve esnektir: Yeni bir çıkış günlükleyicisi oluşturmak için bir yöntem oluşturmanız gerekir. Bu yöntem statik bir yöntem veya örnek yöntemi olabilir. Herhangi bir erişimi olabilir.

Çıktıyı Biçimlendir

Şimdi bu ilk sürümü biraz daha sağlam hale başlayalım ve ardından diğer günlük oluşturma mekanizmalarını oluşturmaya başlayalım.

Şimdi, günlük sınıfınız daha yapılandırılmış iletiler oluşturduğu için LogMessage() yöntemine birkaç bağımsız değişken ekleriz:

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

Şimdi bu bağımsız değişkeni kullanarak Severity günlüğün çıkışına gönderilen iletileri filtreleye bakalım.

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

Uygulama

Günlüğe kaydetme altyapısına yeni özellikler ekledik. Günlükleyici bileşeni herhangi bir çıkış mekanizmasıyla gevşek bir şekilde bağlı olduğundan, bu yeni özellikler günlükçi temsilcisini uygulayan kodun herhangi bir üzerinde hiçbir etkisi olmayacak şekilde eklenebilir.

Bunu sürekli olarak, bu gevşek bağlantının sitenin başka konumlarda herhangi bir değişiklik yapmadan bölümlerini güncelleştirme konusunda daha fazla esneklik nasıl mümkün olduğu hakkında daha fazla örnek bulabilirsiniz. Aslında, daha büyük bir uygulamada, günlükleyici çıkış sınıfları farklı bir derlemede olabilir ve hatta yeniden oluşturulmuş olması bile gerek değildir.

İkinci Bir Çıkış Altyapısı Oluşturma

Günlük bileşeni iyi bir şekilde geliyor. İletileri bir dosyaya kaydeden bir çıkış altyapısı daha ek o zaman. Bu biraz daha ilgili bir çıkış altyapısı olacak. Bu, dosya işlemlerini kapsüller ve her yazmadan sonra dosyanın her zaman kapalı olması için bir sınıf olacak. Bu, her ileti iletiyi üretdikten sonra tüm verilerin diske boşaltıldıktan sonra emin olur.

Dosya tabanlı günlükleyici şu şekildedir:

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

Bu sınıfı oluşturduktan sonra örneğini oluşturduktan sonra LogMessage yöntemini Logger bileşenine iliştirebilirsiniz:

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

Bu ikisi birbirini dışlar. Her iki günlük yöntemini de iliştirebilirsiniz ve konsola ve bir dosyaya ileti oluşturabilirsiniz:

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

Daha sonra, aynı uygulamada bile, sistemde başka bir sorun olmadan temsilcilerden birini kaldırabilirsiniz:

Logger.WriteMessage -= LoggingMethods.LogToConsole;

Uygulama

Şimdi, günlük alt sistemi için ikinci bir çıkış işleyicisi ekledik. Dosya sistemini doğru şekilde desteklemek için bunun biraz daha fazla altyapıya ihtiyacı vardır. Temsilci bir örnek yöntemidir. Bu aynı zamanda özel bir yöntemdir. Temsilci altyapısı temsilcilere bağlanaa olduğundan daha fazla erişilebilirlik gerek yoktur.

İkincisi, temsilci tabanlı tasarım fazladan kod olmadan birden çok çıkış yöntemine olanak sağlar. Birden çok çıkış yöntemini desteklemek için ek altyapı derlemeniz gerek değildir. Yalnızca çağrı listesinde başka bir yöntem haline geldi.

Dosya günlüğü çıkış yönteminde koda özellikle dikkatin. Herhangi bir özel durum atmaması için kodludur. Bu her zaman kesin olarak gerekli değildir ancak genellikle iyi bir uygulamadır. Temsilci yöntemlerden biri özel durum oluşturursa, çağrıda kalan temsilciler çağrılmıyor.

Son not olarak, dosya günlükleyicinin her günlük iletisinde dosyasını açarak ve kapatarak kaynaklarını yönetmesi gerekir. Tamamlandıktan sonra dosyayı kapatıp uygulamak IDisposable için dosyayı açık tutabilirsiniz. her iki yöntemin de avantajları ve dezavantajları vardır. Her ikisi de sınıflar arasında biraz daha fazla eşleşme sağlar.

İki senaryoyu da Logger desteklemek için sınıfındaki kodun hiçbiri güncelleştirilmez.

Null Temsilcileri Işleme

Son olarak, çıkış mekanizması seçilmemişse bu durumlara karşı sağlam olması için LogMessage yöntemini güncelleştirin. Geçerli uygulama, NullReferenceException temsilcinin WriteMessage bağlı bir çağırma listesi yoksa bir atar. Hiçbir yöntem ekli değilken sessizce devam eden bir tasarım tercih edersiniz. Bu, yöntemiyle birlikte null koşullu işleci kullanılarak Delegate.Invoke() kolaydır:

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

Null koşullu işleç ( ) sol işlenen (bu durumda) null olduğunda kısa devreler; başka bir ifadeyle ileti günlüğe ?. WriteMessage kaydedilir.

veya belgelerinde Invoke() listelenen yöntemi System.Delegate System.MulticastDelegate bulamazsanız. Derleyici, bildirilen herhangi bir temsilci türü Invoke için tür güvenli bir yöntem üretir. Bu örnekte bu, tek bir Invoke bağımsız değişken alır ve void dönüş string türüne sahip olduğu anlamına gelir.

Uygulamaların Özeti

Diğer yazıcılarla ve diğer özelliklerle genişletilen bir günlük bileşeninin başlangıcını gördünüz. Tasarımda temsilciler kullanılarak, bu farklı bileşenler gevşek bir şekilde bir aradadır. Bu, çeşitli avantajlar sağlar. Yeni çıkış mekanizmaları oluşturmak ve bunları sisteme eklemek kolaydır. Bu diğer mekanizmalarda yalnızca bir yöntem gerekir: günlük iletisi yazan yöntem. Bu, yeni özellikler eklenmiştir. Herhangi bir yazar için gereken sözleşme bir yöntem uygulamaktır. Bu yöntem statik veya örnek bir yöntem olabilir. Genel, özel veya başka herhangi bir yasal erişim olabilir.

Logger sınıfı, hataya neden olan değişikliklere neden olmadan herhangi bir sayıda geliştirme veya değişiklik yapabilirsiniz. Herhangi bir sınıfta olduğu gibi, genel API'yi değişikliklere bozma riski olmadan değiştiremezsiniz. Ancak, günlükleyici ile herhangi bir çıkış altyapısı arasındaki bağlantı yalnızca temsilci aracılığıyla olduğundan, başka türler (arabirimler veya temel sınıflar gibi) dahil olmaz. Bağlantı mümkün olduğunca küçüktür.

Sonraki