Eğitim: Desen eşleştirmenin temelleri

İpucu

Yazılım geliştirme konusunda yeni misiniz? İlk olarak Başlangıç öğreticileri ile başlayın. Onlar sınıfları, yöntemleri ve denetim akışını tanıtır.

Başka bir dilde mi deneyimlisiniz? Bu öğreticide, kurallar bir nesnenin geçerli durumuna bağlı olduğunda C# desenlerinin nesne davranışını nasıl açıkça ifade ebildiği gösterilir.

Bu öğreticide, kanal kilidi kurallarını modelleyen bir konsol uygulaması oluşturacaksınız.

Kanal havuzu, farklı yüksekliklerdeki iki su seviyesi arasında tekneleri yükseltir veya indirir. İki kapısı ve su seviyesi düşük bir ayar ile yüksek ayar arasında değişen bir haznesi vardır. Kilit, yalnızca su seviyesi ve kapı konumları geçerli kombinasyonlarda kalırsa güvenli bir şekilde çalışabilir.

Bu eğitimde şunları öğreniyorsunuz:

  • Durum üzerinde eşleştirerek nesne davranışını ifade edin.
  • C# desen eşleştirmesi ile bu kuralları uygulayın.
  • Uygulamanızı doğrulamak için derleyici tanılamasını kullanın.

Prerequisites

Yükleme yönergeleri

Windows'ta tüm önkoşulları yüklemek için bu WinGet yapılandırma dosyasını kullanın. Zaten yüklü bir şey varsa WinGet bu adımı atlar.

  1. Dosyayı indirin ve çift tıklayarak çalıştırın.
  2. Lisans sözleşmesini okuyun, yyazın ve kabul etmek isteyip istemediğiniz sorulduğunda Enter seçin.
  3. Görev Çubuğunuzda yanıp sönen bir Kullanıcı Hesabı Denetimi (UAC) istemi alırsanız yüklemenin devam etmesi için izin verin.

Diğer platformlarda, bu bileşenlerin her birini ayrı ayrı yüklemeniz gerekir.

  1. Önerilen yükleyiciyi .NET SDK indirme sayfasından indirin ve çift tıklayarak çalıştırın. İndirme sayfası platformunuzu algılar ve platformunuz için en son yükleyiciyi önerir.
  2. Visual Studio Code giriş sayfasından en son yükleyiciyi indirin ve çift tıklayarak çalıştırın. Bu sayfa ayrıca platformunuzu algılar ve bağlantı sisteminiz için doğru olmalıdır.
  3. C# DevKit uzantısı sayfasındaki "Yükle" düğmesine tıklayın. Bu, Visual Studio kodu açar ve uzantıyı yüklemek mi yoksa etkinleştirmek mi istediğinizi sorar. "Yükle" seçeneğini belirleyin.

Kanal kilidi simülasyonu oluşturun

Kanal havuzu, farklı seviyelerde su yolları arasında tekneleri yükseltir ve indirir. Bu öğreticide, sanal kanal havuzunun alt kapısı, üst kapısı ve suyun seviyesi düşük veya yüksek olabilir.

Normal çalışmada, kilidin içindeki su seviyesi giriş tarafındaki seviyeyle eşleştiğinde tekne girer. Tekne içeri girdikten sonra iki kapı da kapanır. Su seviyesi çıkış tarafıyla eşleşecek şekilde değişir ve çıkış kapısı açılır. Modeli güvenli tutmak için, su seviyesi yalnızca her iki kapı da kapatıldığında değişebilir ve bir kapı yalnızca su seviyesi bu tarafla eşleştiğinde açılabilir.

Bu kuralları bir CanalLock sınıfıyla modelleyebilirsiniz. Her iki kapıyı açma veya kapatma ve su seviyesini yükseltme veya alçaltma komutlarını sunar. Ayrıca kilidin geçerli durumunu bildiren özellikleri de kullanıma sunar.

Sınıfı tanımlayın

Bir konsol projesi oluşturun ve ardından CanalLock adlı bir sınıf ekleyin. Başlangıç olarak genel API'yi tasarlayıp yöntemleri uygulanmamış bırakarak:

public enum WaterLevel
{
    Low,
    High
}
public class CanalLock
{
    // Query canal lock state:
    public WaterLevel CanalLockWaterLevel { get; private set; } = WaterLevel.Low;
    public bool HighWaterGateOpen { get; private set; } = false;
    public bool LowWaterGateOpen { get; private set; } = false;

    // Change the upper gate.
    public void SetHighGate(bool open)
    {
        throw new NotImplementedException();
    }

    // Change the lower gate.
    public void SetLowGate(bool open)
    {
        throw new NotImplementedException();
    }

    // Change water level.
    public void SetWaterLevel(WaterLevel newLevel)
    {
        throw new NotImplementedException();
    }

    public override string ToString() =>
        $"The lower gate is {(LowWaterGateOpen ? "Open" : "Closed")}. " +
        $"The upper gate is {(HighWaterGateOpen ? "Open" : "Closed")}. " +
        $"The water level is {CanalLockWaterLevel}.";
}

Yukarıdaki kod, hem kapıları kapalı hem de su düzeyi düşük olan kanal havuzunu başlatır. Ardından, ilk uygulamanıza yol göstermek için aşağıdaki kodu Main ekleyin:

// Create a new canal lock:
var canalGate = new CanalLock();

// State should be doors closed, water level low:
Console.WriteLine(canalGate);

canalGate.SetLowGate(open: true);
Console.WriteLine($"Open the lower gate:  {canalGate}");

Console.WriteLine("Boat enters lock from lower gate");

canalGate.SetLowGate(open: false);
Console.WriteLine($"Close the lower gate:  {canalGate}");

canalGate.SetWaterLevel(WaterLevel.High);
Console.WriteLine($"Raise the water level: {canalGate}");

canalGate.SetHighGate(open: true);
Console.WriteLine($"Open the higher gate:  {canalGate}");

Console.WriteLine("Boat exits lock at upper gate");
Console.WriteLine("Boat enters lock from upper gate");

canalGate.SetHighGate(open: false);
Console.WriteLine($"Close the higher gate: {canalGate}");

canalGate.SetWaterLevel(WaterLevel.Low);
Console.WriteLine($"Lower the water level: {canalGate}");

canalGate.SetLowGate(open: true);
Console.WriteLine($"Open the lower gate:  {canalGate}");

Console.WriteLine("Boat exits lock at upper gate");

canalGate.SetLowGate(open: false);
Console.WriteLine($"Close the lower gate:  {canalGate}");

Şimdi güvenlik kurallarını zorlamadan her durum değerini değiştiren ilk bir uygulama ekleyin:

// Change the upper gate.
public void SetHighGate(bool open)
{
    HighWaterGateOpen = open;
}

// Change the lower gate.
public void SetLowGate(bool open)
{
    LowWaterGateOpen = open;
}

// Change water level.
public void SetWaterLevel(WaterLevel newLevel)
{
    CanalLockWaterLevel = newLevel;
}

Bu ilk denetimler başarılı olur. Mekanik aksam çalışıyor. Ardından, ilk hata koşulu için bir test ekleyin. Önceki sekansın sonunda her iki kapı da kapalıdır ve su seviyesi düşüktür. Üst kapıyı açmayı deneyin:

Console.WriteLine("=============================================");
Console.WriteLine("     Test invalid commands");
// Open "wrong" gate (2 tests)
try
{
    canalGate = new CanalLock();
    canalGate.SetHighGate(open: true);
}
catch (InvalidOperationException)
{
    Console.WriteLine("Invalid operation: Can't open the high gate. Water is low.");
}
Console.WriteLine($"Try to open upper gate: {canalGate}");

Bu test başarısız olur çünkü üst kapı açılmaması gerekirken açılır. İlk düzeltme şöyle görünebilir:

// Change the upper gate.
public void SetHighGate(bool open)
{
    if (open && (CanalLockWaterLevel == WaterLevel.High))
        HighWaterGateOpen = true;
    else if (open && (CanalLockWaterLevel == WaterLevel.Low))
        throw new InvalidOperationException("Cannot open high gate when the water is low");
}

Testleriniz tekrar başarılı oldu. Ancak daha fazla koşul ekledikçe, daha fazla if ifadesi biriktirirsiniz. Her kural diğerlerinden ayrıldığından kodu taramak zorlaşır.

Komutları desenlerle uygulayın

Daha net bir seçenek, geçerli birleşimleri doğrudan açıklamak için patternleri kullanmaktır. Sonraki adımda, her anahtar ifadesi desen girişi olarak bir demet kullanır. C#, bu demeti bir kez değerlendirir ve her switch kolu geçerli kapı durumunu, su düzeyini ve istenen yeni ayarı test eder.

Üst kapı için şu bileşimleri özetleyebilirsiniz:

Yeni ayar Kapı durumu Su seviyesi Result
Kapalı Kapalı Yüksek Kapalı
Kapalı Kapalı Low Kapalı
Kapalı Open Yüksek Kapalı
Kapalı Aç Low Kapalı
Open Kapalı Yüksek Open
Open Kapalı Low Kapalı (hata)
Open Open Yüksek Open
Aç Aç Low Kapalı (hata)

Üstü çizili satırlar geçersiz iç durumları temsil eder. Switch ifadesi geçerli geçişleri doğrudan kodlayabilir. false hâlâ geçidin kapalı olduğu anlamına gelir:

HighWaterGateOpen = (open, HighWaterGateOpen, CanalLockWaterLevel) switch
{
    (false, false, WaterLevel.High) => false,
    (false, false, WaterLevel.Low) => false,
    (false, true, WaterLevel.High) => false,
    (false, true, WaterLevel.Low) => false, // should never happen
    (true, false, WaterLevel.High) => true,
    (true, false, WaterLevel.Low) => throw new InvalidOperationException("Cannot open high gate when the water is low"),
    (true, true, WaterLevel.High) => true,
    (true, true, WaterLevel.Low) => false, // should never happen
};

Bu sürümü deneyin. Testleriniz başarılı. Derleyici ayrıca switch ifadesinin eksiksiz olmadığı konusunda da uyarır — mümkün olan her değeri kapsamaz — çünkü WaterLevel bir enum'dur ve C#, enum'un temel alınan sayısal türündeki herhangi bir değerin, adlandırılmış bir üyesi olmasa bile, bu enum'a dönüştürülmesine izin verir. İmkansız iç durumları işlemek için atma deseni (_) ile son bir kol ekleyin:

_ => throw new InvalidOperationException("Invalid internal state"),

Atma düzeni kalan her girişle eşleştiğinden bu dalın son olması gerekir.

Daha sonra önceki dalları basitleştirebilirsiniz. Kapının kapatılmasına her zaman izin verilir, böylece bir kol dört ayrı kapalı durumun yerini alabilir:

(false, _, _) => false,

Ayrıca geçerli açık vakaları birleştirebilir ve mevcut tek güvenlik hatasını koruyabilirsiniz:

(true, _, WaterLevel.High) => true,
(true, false, WaterLevel.Low) => throw new InvalidOperationException("Cannot open high gate when the water is low"),
_ => throw new InvalidOperationException("Invalid internal state"),

Programı yeniden çalıştırın. Testler hala geçiyor. İşte son SetHighGate uygulama:

// Change the upper gate.
public void SetHighGate(bool open)
{
    HighWaterGateOpen = (open, HighWaterGateOpen, CanalLockWaterLevel) switch
    {
        (false, _,    _)               => false,
        (true, _,     WaterLevel.High) => true,
        (true, false, WaterLevel.Low)  => throw new InvalidOperationException("Cannot open high gate when the water is low"),
        _                              => throw new InvalidOperationException("Invalid internal state"),
    };
}

Kalan kuralları uygulayın

Şimdi aynı fikri SetWaterLevel ve SetLowGate için uygulayın. Geçersiz işlemleri ortaya çıkaran testler ekleyerek başlayın:

Console.WriteLine();
Console.WriteLine();
try
{
    canalGate = new CanalLock();
    canalGate.SetWaterLevel(WaterLevel.High);
    canalGate.SetLowGate(open: true);
}
catch (InvalidOperationException)
{
    Console.WriteLine("invalid operation: Can't open the lower gate. Water is high.");
}
Console.WriteLine($"Try to open lower gate: {canalGate}");
// change water level with gate open (2 tests)
Console.WriteLine();
Console.WriteLine();
try
{
    canalGate = new CanalLock();
    canalGate.SetLowGate(open: true);
    canalGate.SetWaterLevel(WaterLevel.High);
}
catch (InvalidOperationException)
{
    Console.WriteLine("invalid operation: Can't raise water when the lower gate is open.");
}
Console.WriteLine($"Try to raise water with lower gate open: {canalGate}");
Console.WriteLine();
Console.WriteLine();
try
{
    canalGate = new CanalLock();
    canalGate.SetWaterLevel(WaterLevel.High);
    canalGate.SetHighGate(open: true);
    canalGate.SetWaterLevel(WaterLevel.Low);
}
catch (InvalidOperationException)
{
    Console.WriteLine("invalid operation: Can't lower water when the high gate is open.");
}
Console.WriteLine($"Try to lower water with high gate open: {canalGate}");

Uygulamayı yeniden çalıştırın. Bu testler başarısız olur ve canal lock geçersiz durumlara ulaşır. Kalan yöntemleri tüm durum üzerinde eşleştirerek uygulayın. SetLowGate, SetHighGateile benzerdir. SetWaterLevel geçerli su seviyesini ve her iki kapı pozisyonunu da kullanır:

CanalLockWaterLevel = (newLevel, CanalLockWaterLevel, LowWaterGateOpen, HighWaterGateOpen) switch
{
    // arms go here
};

Dikkate almanız gereken 16 kombinasyon var. Tam tabloyla başlayın, switch arm’larını yazın, testleri çalıştırın ve tekrarlanan sonuçları basitleştirin.

Siz de bunlara benzer yöntemler mi kullandınız?

// Change the lower gate.
public void SetLowGate(bool open)
{
    LowWaterGateOpen = (open, LowWaterGateOpen, CanalLockWaterLevel) switch
    {
        (false, _, _) => false,
        (true, _, WaterLevel.Low) => true,
        (true, false, WaterLevel.High) => throw new InvalidOperationException("Cannot open low gate when the water is high"),
        _ => throw new InvalidOperationException("Invalid internal state"),
    };
}

// Change water level.
public void SetWaterLevel(WaterLevel newLevel)
{
    CanalLockWaterLevel = (newLevel, CanalLockWaterLevel, LowWaterGateOpen, HighWaterGateOpen) switch
    {
        (WaterLevel.Low, WaterLevel.Low, true, false) => WaterLevel.Low,
        (WaterLevel.High, WaterLevel.High, false, true) => WaterLevel.High,
        (WaterLevel.Low, _, false, false) => WaterLevel.Low,
        (WaterLevel.High, _, false, false) => WaterLevel.High,
        (WaterLevel.Low, WaterLevel.High, false, true) => throw new InvalidOperationException("Cannot lower water when the high gate is open"),
        (WaterLevel.High, WaterLevel.Low, true, false) => throw new InvalidOperationException("Cannot raise water when the low gate is open"),
        _ => throw new InvalidOperationException("Invalid internal state"),
    };
}

Testleriniz şimdi geçmelidir ve Canal Lock güvenlik kurallarını zorunlu kılmalıdır.

Summary

Bu öğreticide, bir nesnenin geçerli bir durumdan diğerine nasıl değişebileceğini ifade etmek için desen eşleştirmeyi kullandınız. Kalıplar, izin verilen geçişleri bir arada tutar, böylece bunları uzun bir dallanmalı ifadeler serisine göre daha kolay karşılaştırabilirsiniz. Davranış birkaç durum değerinin birleşik şekline bağlı olduğunda bu yaklaşım iyi çalışır.