Öğretici: Varsayılan arabirim yöntemleriyle arabirimleri kullanarak sınıf oluştururken işlevini karıştırma

Bir arabirimin üyesini bildirdiğinizde bir uygulama tanımlayabilirsiniz. Bu özellik, arabirimlerde bildirilen özellikler için varsayılan uygulamaları tanımlayabileceğiniz yeni özellikler sağlar. Sınıflar işlevselliğin ne zaman geçersiz kılındığını, varsayılan işlevselliğin ne zaman kullanılacağını ve ayrık özellikler için ne zaman destek bildirilmeyebileceğini seçebilir.

Bu öğreticide aşağıdakilerin nasıl yapılacağını öğreneceksiniz:

  • Ayrık özellikleri açıklayan uygulamalarla arabirimler oluşturun.
  • Varsayılan uygulamaları kullanan sınıflar oluşturun.
  • Varsayılan uygulamaların bazılarını veya tümünü geçersiz kılan sınıflar oluşturun.

Önkoşullar

C# derleyicisi de dahil olmak üzere makinenizi .NET'i çalıştıracak şekilde ayarlamanız gerekir. C# derleyicisi Visual Studio 2022 veya .NET SDK ile kullanılabilir.

Uzantı yöntemlerinin sınırlamaları

Bir arabirimin parçası olarak görünen davranışı uygulayabilmenizin bir yolu, varsayılan davranışı sağlayan uzantı yöntemlerini tanımlamaktır. Arabirimler, bu arabirimi uygulayan tüm sınıflar için daha büyük bir yüzey alanı sağlarken en düşük üye kümesini bildirir. Örneğin içindeki uzantı yöntemleri Enumerable , LINQ sorgusunun kaynağı olacak herhangi bir dizi için uygulamayı sağlar.

Uzantı yöntemleri, değişkenin bildirilen türü kullanılarak derleme zamanında çözümlenir. Arabirimi uygulayan sınıflar herhangi bir uzantı yöntemi için daha iyi bir uygulama sağlayabilir. Derleyicinin bu uygulamayı seçmesini sağlamak için değişken bildirimlerinin uygulama türüyle eşleşmesi gerekir. Derleme zamanı türü arabirimle eşleştiğinde, yöntem çağrıları uzantı yöntemine çözümlenmiş olur. Uzantı yöntemleriyle ilgili bir diğer sorun da, uzantı yöntemlerini içeren sınıfın erişilebilir olduğu her yerde bu yöntemlerin erişilebilir olmasıdır. Sınıflar, uzantı yöntemlerinde bildirilen özellikleri sağlamalı veya sağlamamalıdır.

Varsayılan uygulamaları arabirim yöntemleri olarak bildirebilirsiniz. Ardından, her sınıf otomatik olarak varsayılan uygulamayı kullanır. Daha iyi bir uygulama sağlayabilen tüm sınıflar, arabirim yöntemi tanımını daha iyi bir algoritmayla geçersiz kılabilir. Bir anlamda bu teknik, uzantı yöntemlerini nasıl kullanabileceğinize benzer.

Bu makalede, varsayılan arabirim uygulamalarının yeni senaryolara nasıl olanak sağladığını öğreneceksiniz.

Uygulamayı tasarlama

Bir ev otomasyonu uygulaması düşünün. Muhtemelen evin her köşesinde kullanılabilecek birçok farklı ışık ve gösterge türüne sahipsinizdir. Her ışığın API'leri açıp kapatmak ve geçerli durumu raporlamak için desteklemesi gerekir. Bazı ışıklar ve göstergeler şu gibi diğer özellikleri destekleyemeyebilir:

  • Işığı açın, ardından zamanlayıcıdan sonra kapatın.
  • Bir süre ışığı yanıp söner.

Bu genişletilmiş özelliklerden bazıları, minimum kümeyi destekleyen cihazlarda öykünebilir. Bu, varsayılan bir uygulama sağladığını gösterir. Yerleşik daha fazla özelliğe sahip cihazlar için cihaz yazılımı yerel özellikleri kullanır. Diğer ışıklar için arabirimi uygulamayı ve varsayılan uygulamayı kullanmayı seçebilirler.

Varsayılan arabirim üyeleri bu senaryo için uzantı yöntemlerinden daha iyi bir çözüm sağlar. Sınıf yazarları hangi arabirimleri uygulamayı seçtiklerini denetleyebiliyor. Seçtikleri arabirimler yöntem olarak kullanılabilir. Ayrıca, varsayılan arabirim yöntemleri varsayılan olarak sanal olduğundan, dispatch yöntemi her zaman sınıfındaki uygulamayı seçer.

Şimdi bu farklılıkları göstermek için kodu oluşturalım.

Arabirim oluşturma

Tüm ışıklar için davranışı tanımlayan arabirimi oluşturarak başlayın:

public interface ILight
{
    void SwitchOn();
    void SwitchOff();
    bool IsOn();
}

Temel bir ek yük ışık fikstür aşağıdaki kodda gösterildiği gibi bu arabirimi uygulayabilir:

public class OverheadLight : ILight
{
    private bool isOn;
    public bool IsOn() => isOn;
    public void SwitchOff() => isOn = false;
    public void SwitchOn() => isOn = true;

    public override string ToString() => $"The light is {(isOn ? "on" : "off")}";
}

Bu öğreticide kod IoT cihazlarını yönlendirmez, ancak konsola ileti yazarak bu etkinliklere öykünmektedir. Evinizi otomatikleştirmeden kodu keşfedebilirsiniz.

Şimdi zaman aşımından sonra otomatik olarak kapanabilen bir ışık arabirimini tanımlayalım:

public interface ITimerLight : ILight
{
    Task TurnOnFor(int duration);
}

Ek yük ışığına temel bir uygulama ekleyebilirsiniz, ancak bu arabirim tanımını varsayılan bir uygulama sağlayacak şekilde değiştirmek daha iyi bir virtual çözümdür:

public interface ITimerLight : ILight
{
    public async Task TurnOnFor(int duration)
    {
        Console.WriteLine("Using the default interface method for the ITimerLight.TurnOnFor.");
        SwitchOn();
        await Task.Delay(duration);
        SwitchOff();
        Console.WriteLine("Completed ITimerLight.TurnOnFor sequence.");
    }
}

sınıfı, OverheadLight arabirimi için destek bildirerek timer işlevini uygulayabilir:

public class OverheadLight : ITimerLight { }

Farklı bir ışık türü daha gelişmiş bir protokolü destekleyemeyebilir. Aşağıdaki kodda gösterildiği gibi için TurnOnForkendi uygulamasını sağlayabilir:

public class HalogenLight : ITimerLight
{
    private enum HalogenLightState
    {
        Off,
        On,
        TimerModeOn
    }

    private HalogenLightState state;
    public void SwitchOn() => state = HalogenLightState.On;
    public void SwitchOff() => state = HalogenLightState.Off;
    public bool IsOn() => state != HalogenLightState.Off;
    public async Task TurnOnFor(int duration)
    {
        Console.WriteLine("Halogen light starting timer function.");
        state = HalogenLightState.TimerModeOn;
        await Task.Delay(duration);
        state = HalogenLightState.Off;
        Console.WriteLine("Halogen light finished custom timer function");
    }

    public override string ToString() => $"The light is {state}";
}

Sanal sınıf yöntemlerini geçersiz kılmanın aksine, sınıfında bildirimi TurnOnForHalogenLight anahtar sözcüğünü override kullanmaz.

Özellikleri karıştırma ve eşleştirme

Daha gelişmiş özellikler ekledikçe varsayılan arabirim yöntemlerinin avantajları daha net hale gelir. Arabirimleri kullanmak, özellikleri karıştırmanıza ve eşleştirmenize olanak tanır. Ayrıca her sınıf yazarının varsayılan uygulama ile özel uygulama arasında seçim yapmalarını sağlar. Şimdi yanıp sönen ışık için varsayılan uygulamaya sahip bir arabirim ekleyelim:

public interface IBlinkingLight : ILight
{
    public async Task Blink(int duration, int repeatCount)
    {
        Console.WriteLine("Using the default interface method for IBlinkingLight.Blink.");
        for (int count = 0; count < repeatCount; count++)
        {
            SwitchOn();
            await Task.Delay(duration);
            SwitchOff();
            await Task.Delay(duration);
        }
        Console.WriteLine("Done with the default interface method for IBlinkingLight.Blink.");
    }
}

Varsayılan uygulama, tüm ışığın yanıp sönmesini sağlar. Ek yük ışığı, varsayılan uygulamayı kullanarak hem zamanlayıcı hem de yanıp sönme özellikleri ekleyebilir:

public class OverheadLight : ILight, ITimerLight, IBlinkingLight
{
    private bool isOn;
    public bool IsOn() => isOn;
    public void SwitchOff() => isOn = false;
    public void SwitchOn() => isOn = true;

    public override string ToString() => $"The light is {(isOn ? "on" : "off")}";
}

Yeni bir ışık türü, LEDLight hem zamanlayıcı işlevini hem de yanıp sönme işlevini doğrudan destekler. Bu açık stil hem ve IBlinkingLight arabirimlerini uygular hem de ITimerLight yöntemini geçersiz kılarBlink:

public class LEDLight : IBlinkingLight, ITimerLight, ILight
{
    private bool isOn;
    public void SwitchOn() => isOn = true;
    public void SwitchOff() => isOn = false;
    public bool IsOn() => isOn;
    public async Task Blink(int duration, int repeatCount)
    {
        Console.WriteLine("LED Light starting the Blink function.");
        await Task.Delay(duration * repeatCount);
        Console.WriteLine("LED Light has finished the Blink function.");
    }

    public override string ToString() => $"The light is {(isOn ? "on" : "off")}";
}

, ExtraFancyLight hem yanıp sönme hem de zamanlayıcı işlevlerini doğrudan destekleyebileceğinden:

public class ExtraFancyLight : IBlinkingLight, ITimerLight, ILight
{
    private bool isOn;
    public void SwitchOn() => isOn = true;
    public void SwitchOff() => isOn = false;
    public bool IsOn() => isOn;
    public async Task Blink(int duration, int repeatCount)
    {
        Console.WriteLine("Extra Fancy Light starting the Blink function.");
        await Task.Delay(duration * repeatCount);
        Console.WriteLine("Extra Fancy Light has finished the Blink function.");
    }
    public async Task TurnOnFor(int duration)
    {
        Console.WriteLine("Extra Fancy light starting timer function.");
        await Task.Delay(duration);
        Console.WriteLine("Extra Fancy light finished custom timer function");
    }

    public override string ToString() => $"The light is {(isOn ? "on" : "off")}";
}

Daha HalogenLight önce oluşturduğunuz yanıp sönme özelliği desteklenmez. Bu nedenle, öğesini desteklenen arabirimler listesine eklemeyin IBlinkingLight .

Desen eşleştirmeyi kullanarak ışık türlerini algılama

Şimdi biraz test kodu yazalım. Hangi arabirimleri desteklediğini inceleyerek ışığın özelliklerini belirlemek için C# desen eşleştirme özelliğini kullanabilirsiniz. Aşağıdaki yöntem her ışığın desteklenen özelliklerini kullanır:

private static async Task TestLightCapabilities(ILight light)
{
    // Perform basic tests:
    light.SwitchOn();
    Console.WriteLine($"\tAfter switching on, the light is {(light.IsOn() ? "on" : "off")}");
    light.SwitchOff();
    Console.WriteLine($"\tAfter switching off, the light is {(light.IsOn() ? "on" : "off")}");

    if (light is ITimerLight timer)
    {
        Console.WriteLine("\tTesting timer function");
        await timer.TurnOnFor(1000);
        Console.WriteLine("\tTimer function completed");
    }
    else
    {
        Console.WriteLine("\tTimer function not supported.");
    }

    if (light is IBlinkingLight blinker)
    {
        Console.WriteLine("\tTesting blinking function");
        await blinker.Blink(500, 5);
        Console.WriteLine("\tBlink function completed");
    }
    else
    {
        Console.WriteLine("\tBlink function not supported.");
    }
}

Yönteminizdeki Main aşağıdaki kod, sıralı olarak her ışık türünü oluşturur ve bu ışığı test ediyor:

static async Task Main(string[] args)
{
    Console.WriteLine("Testing the overhead light");
    var overhead = new OverheadLight();
    await TestLightCapabilities(overhead);
    Console.WriteLine();

    Console.WriteLine("Testing the halogen light");
    var halogen = new HalogenLight();
    await TestLightCapabilities(halogen);
    Console.WriteLine();

    Console.WriteLine("Testing the LED light");
    var led = new LEDLight();
    await TestLightCapabilities(led);
    Console.WriteLine();

    Console.WriteLine("Testing the fancy light");
    var fancy = new ExtraFancyLight();
    await TestLightCapabilities(fancy);
    Console.WriteLine();
}

Derleyicinin en iyi uygulamayı belirleme şekli

Bu senaryo, herhangi bir uygulama içermeyen bir temel arabirim gösterir. Arabirime bir yöntem eklemek ILight yeni karmaşıklıklar sağlar. Varsayılan arabirim yöntemlerini yöneten dil kuralları, birden çok türetilmiş arabirim uygulayan somut sınıflar üzerindeki etkisini en aza indirir. Bunun kullanımını nasıl değiştirdiğini göstermek için özgün arabirimi yeni bir yöntemle geliştirelim. Her gösterge ışığı, güç durumunu numaralandırılmış değer olarak bildirebilir:

public enum PowerStatus
{
    NoPower,
    ACPower,
    FullBattery,
    MidBattery,
    LowBattery
}

Varsayılan uygulama güç olmadığını varsayar:

public interface ILight
{
    void SwitchOn();
    void SwitchOff();
    bool IsOn();
    public PowerStatus Power() => PowerStatus.NoPower;
}

Bu değişiklikler, arabirimi ve her iki türetilmiş arabirim ve için destek bildirse ExtraFancyLight de temiz bir şekilde derlenmiştir ITimerLightIBlinkingLight.ILight Arabirimde ILight bildirilen yalnızca bir "en yakın" uygulama vardır. Geçersiz kılma bildiren herhangi bir sınıf, "en yakın" uygulama olur. Önceki sınıflarda diğer türetilmiş arabirimlerin üyelerini aşan örnekler gördünüz.

Aynı yöntemi birden çok türetilmiş arabirimde geçersiz kılmaktan kaçının. Bunu yapmak, bir sınıf her iki türetilmiş arabirimi de uyguladığında belirsiz bir yöntem çağrısı oluşturur. Derleyici tek bir daha iyi yöntem seçemez, bu nedenle bir hata oluşturur. Örneğin, hem hem IBlinkingLight de ITimerLight öğesinin geçersiz kılmasını uyguladıysa, öğesinin PowerStatusOverheadLight daha belirgin bir geçersiz kılma sağlaması gerekir. Aksi takdirde, derleyici türetilmiş iki arabirimdeki uygulamalar arasında seçim yapamaz. Genellikle arabirim tanımlarını küçük tutarak ve tek bir özelliğe odaklanarak bu durumdan kaçınabilirsiniz. Bu senaryoda, bir ışığın her özelliği kendi arabirimidir; yalnızca sınıflar birden çok arabirimi devralır.

Bu örnek, sınıflara karıştırılabilir ayrık özellikleri tanımlayabileceğiniz bir senaryo gösterir. Bir sınıfın hangi arabirimleri desteklediğini bildirerek desteklenen işlevler kümesini bildirirsiniz. Sanal varsayılan arabirim yöntemlerinin kullanılması, sınıfların arabirim yöntemlerinin herhangi biri veya tümü için farklı bir uygulama kullanmasını veya tanımlamasını sağlar. Bu dil özelliği, oluşturduğunuz gerçek dünya sistemlerini modellemek için yeni yollar sağlar. Varsayılan arabirim yöntemleri, bu özelliklerin sanal uygulamalarını kullanarak farklı özellikleri karıştırıp eşleştirebilecek ilgili sınıfları ifade etmek için daha net bir yol sağlar.