Nesne Odaklı programlama (C#)

C# nesne odaklı bir programlama dilidir. Nesne odaklı programlamanın dört temel ilkesi şunlardır:

  • Soyutlama Sistemin soyut bir gösterimini tanımlamak için varlıkların ilgili özniteliklerini ve etkileşimlerini sınıf olarak modelleme.
  • Kapsülleme Bir nesnenin iç durumunu ve işlevselliğini gizleme ve yalnızca genel bir işlev kümesi üzerinden erişime izin verme.
  • Devralma Özelliği, mevcut soyutlamaları temel alan yeni soyutlamalar oluşturabilir.
  • Çok biçimlilik Birden çok soyutlamada devralınan özellikleri veya yöntemleri farklı şekillerde uygulama olanağı.

Önceki öğreticide, hem soyutlama hem de kapsülleme gördüğünüz sınıflara giriş. sınıfı, BankAccount banka hesabı kavramı için bir soyutlama sağladı. sınıfını kullanan BankAccount kodlardan herhangi birini etkilemeden uygulamasını değiştirebilirsiniz. BankAccount Hem hem de Transaction sınıfları, koddaki bu kavramları açıklamak için gereken bileşenlerin kapsüllemesini sağlar.

Bu öğreticide, yeni özellikler eklemek için devralma ve çok biçimlilikten yararlanmak için bu uygulamayı genişleteceksiniz. Ayrıca, önceki öğreticide BankAccount öğrendiğiniz soyutlama ve kapsülleme tekniklerinden yararlanarak sınıfına özellikler ekleyeceksiniz.

Farklı hesap türleri oluşturma

Bu programı derledikten sonra, bu programa özellik ekleme istekleri alırsınız. Yalnızca bir banka hesabı türünün olduğu durumlarda harika çalışır. Zaman içinde değişiklik gerekiyor ve ilgili hesap türleri isteniyor:

  • Her ayın sonunda tahakkuk eden bir faiz kazanma hesabı.
  • Negatif bakiyesi olabilecek ancak bakiye olduğunda her ay bir faiz ücreti olan bir kredi satırı.
  • Tek bir depozito ile başlayan ve yalnızca ödenebilen ön ödemeli bir hediye kartı hesabı. Her ayın başında bir kez yeniden doldurulabilir.

Bu farklı hesapların tümü önceki öğreticide tanımlanan sınıfa BankAccount benzer. Bu kodu kopyalayabilir, sınıfları yeniden adlandırabilir ve değişiklikler yapabilirsiniz. Bu teknik kısa vadede işe yarasa da zaman içinde daha fazla iş olacaktır. Tüm değişiklikler etkilenen tüm sınıflara kopyalanır.

Bunun yerine, önceki öğreticide oluşturulan sınıftan BankAccount yöntemleri ve verileri devralan yeni banka hesabı türleri oluşturabilirsiniz. Bu yeni sınıflar sınıfı her tür için gereken belirli bir davranışla genişletebilir BankAccount :

public class InterestEarningAccount : BankAccount
{
}

public class LineOfCreditAccount : BankAccount
{
}

public class GiftCardAccount : BankAccount
{
}

Bu sınıfların her biri paylaşılan davranışı paylaşılan temel sınıfı olan sınıfından devralırBankAccount. Türetilmiş sınıfların her birinde yeni ve farklı işlevler için uygulamaları yazın. Bu türetilmiş sınıflar zaten sınıfında tanımlanan tüm davranışlara BankAccount sahiptir.

Her yeni sınıfı farklı bir kaynak dosyada oluşturmak iyi bir uygulamadır. Visual Studio'da projeye sağ tıklayabilir ve sınıf ekle'yi seçerek yeni bir dosyaya yeni bir sınıf ekleyebilirsiniz. Visual Studio Code'da Dosya'yı ve ardından Yeni'yi seçerek yeni bir kaynak dosya oluşturun. Her iki araçta da dosyayı sınıfıyla eşleşecek şekilde adlandırın: InterestEarningAccount.cs, LineOfCreditAccount.cs ve GiftCardAccount.cs.

Önceki örnekte gösterildiği gibi sınıfları oluşturduğunuzda, türetilmiş sınıflarınızdan hiçbirinin derlenmediğini görürsünüz. Oluşturucu, bir nesneyi başlatmakla sorumludur. Türetilmiş bir sınıf oluşturucu, türetilmiş sınıfı başlatmalı ve türetilmiş sınıfa dahil edilen temel sınıf nesnesini başlatma yönergeleri sağlamalıdır. Düzgün başlatma normalde fazladan kod olmadan gerçekleşir. BankAccount sınıfı aşağıdaki imzaya sahip bir ortak oluşturucu bildirir:

public BankAccount(string name, decimal initialBalance)

Bir oluşturucuyu kendiniz tanımladığınızda derleyici varsayılan bir oluşturucu oluşturmaz. Bu, türetilmiş her sınıfın açıkça bu oluşturucuyu çağırması gerektiği anlamına gelir. Temel sınıf oluşturucusunun bağımsız değişkenlerini geçirebilen bir oluşturucu bildirirsiniz. Aşağıdaki kod, için oluşturucuyu InterestEarningAccountgösterir:

public InterestEarningAccount(string name, decimal initialBalance) : base(name, initialBalance)
{
}

Bu yeni oluşturucunun parametreleri, temel sınıf oluşturucusunun parametre türü ve adlarıyla eşleşmektedir. Bir temel sınıf oluşturucusunun çağrısını belirtmek için söz dizimini kullanırsınız : base() . Bazı sınıflar birden çok oluşturucu tanımlar ve bu söz dizimi, çağırdığınız temel sınıf oluşturucuyu seçmenizi sağlar. Oluşturucuları güncelleştirdikten sonra, türetilmiş sınıfların her biri için kodu geliştirebilirsiniz. Yeni sınıfların gereksinimleri aşağıdaki gibi belirtilebilir:

  • Bir faiz kazanç hesabı:
    • Ay sonu bakiyesinin %2'sinin kredisini alır.
  • Kredi satırı:
    • Negatif bakiyeye sahip olabilir, ancak mutlak değerde kredi limitinden büyük olamaz.
    • Ay sonu bakiyesinin 0 olmadığı her ay bir faiz ücreti uygulanır.
    • Kredi limitini aşan her para çekme için bir ücret uygulanır.
  • Hediye kartı hesabı:
    • Her ay, ayın son günü belirli bir miktarla yeniden doldurulabilir.

Bu hesap türlerinin üçünün de her ayın sonunda gerçekleşen bir eylemi olduğunu görebilirsiniz. Ancak, her hesap türü farklı görevler yapar. Bu kodu uygulamak için çok biçimlilik kullanırsınız. sınıfında tek virtual bir yöntem BankAccount oluşturun:

public virtual void PerformMonthEndTransactions() { }

Yukarıdaki kod, türetilmiş bir sınıfın virtual farklı bir uygulama sağlayabilecekleri temel sınıfta bir yöntemi bildirmek için anahtar sözcüğünü nasıl kullandığınızı gösterir. virtual Yöntem, türetilmiş herhangi bir sınıfın yeniden göndermeyi seçebileceği bir yöntemdir. Türetilmiş sınıflar, yeni uygulamayı tanımlamak için anahtar sözcüğünü kullanır override . Genellikle bunu "temel sınıf uygulamasını geçersiz kılma" olarak adlandırın. virtual anahtar sözcüğü, türetilmiş sınıfların davranışı geçersiz kılabileceğini belirtir. Ayrıca türetilmiş sınıfların davranışı geçersiz kılması gereken yöntemleri de bildirebilirsiniz abstract . Temel sınıf bir yöntem için abstract bir uygulama sağlamaz. Ardından, oluşturduğunuz yeni sınıflardan ikisi için uygulamayı tanımlamanız gerekir. ile InterestEarningAccountbaşlayın:

public override void PerformMonthEndTransactions()
{
    if (Balance > 500m)
    {
        decimal interest = Balance * 0.02m;
        MakeDeposit(interest, DateTime.Now, "apply monthly interest");
    }
}

aşağıdaki kodu öğesine LineOfCreditAccountekleyin. Kod, hesaptan çekilen pozitif bir faiz ücretini hesaplamak için bakiyeyi geçersiz hale getirir:

public override void PerformMonthEndTransactions()
{
    if (Balance < 0)
    {
        // Negate the balance to get a positive interest charge:
        decimal interest = -Balance * 0.07m;
        MakeWithdrawal(interest, DateTime.Now, "Charge monthly interest");
    }
}

sınıfı, GiftCardAccount ay sonu işlevselliğini uygulamak için iki değişikliğe ihtiyaç duyar. İlk olarak, oluşturucuyu her ay eklenecek isteğe bağlı bir miktar içerecek şekilde değiştirin:

private readonly decimal _monthlyDeposit = 0m;

public GiftCardAccount(string name, decimal initialBalance, decimal monthlyDeposit = 0) : base(name, initialBalance)
    => _monthlyDeposit = monthlyDeposit;

Oluşturucu değer için monthlyDeposit varsayılan bir değer sağlar, böylece arayanlar aylık para yatırma işlemi yapılmadan atlayabilir 0 . Ardından, oluşturucuda PerformMonthEndTransactions sıfır olmayan bir değere ayarlanmışsa aylık depozito eklemek için yöntemini geçersiz kılın:

public override void PerformMonthEndTransactions()
{
    if (_monthlyDeposit != 0)
    {
        MakeDeposit(_monthlyDeposit, DateTime.Now, "Add monthly deposit");
    }
}

Geçersiz kılma, oluşturucuda ayarlanan aylık depozitoyu uygular. ve için bu değişiklikleri test etmek için yöntemine GiftCardAccountInterestEarningAccountaşağıdaki kodu Main ekleyin:

var giftCard = new GiftCardAccount("gift card", 100, 50);
giftCard.MakeWithdrawal(20, DateTime.Now, "get expensive coffee");
giftCard.MakeWithdrawal(50, DateTime.Now, "buy groceries");
giftCard.PerformMonthEndTransactions();
// can make additional deposits:
giftCard.MakeDeposit(27.50m, DateTime.Now, "add some additional spending money");
Console.WriteLine(giftCard.GetAccountHistory());

var savings = new InterestEarningAccount("savings account", 10000);
savings.MakeDeposit(750, DateTime.Now, "save some money");
savings.MakeDeposit(1250, DateTime.Now, "Add more savings");
savings.MakeWithdrawal(250, DateTime.Now, "Needed to pay monthly bills");
savings.PerformMonthEndTransactions();
Console.WriteLine(savings.GetAccountHistory());

Sonuçları doğrulayın. Şimdi, için LineOfCreditAccountbenzer bir test kodu kümesi ekleyin:

var lineOfCredit = new LineOfCreditAccount("line of credit", 0);
// How much is too much to borrow?
lineOfCredit.MakeWithdrawal(1000m, DateTime.Now, "Take out monthly advance");
lineOfCredit.MakeDeposit(50m, DateTime.Now, "Pay back small amount");
lineOfCredit.MakeWithdrawal(5000m, DateTime.Now, "Emergency funds for repairs");
lineOfCredit.MakeDeposit(150m, DateTime.Now, "Partial restoration on repairs");
lineOfCredit.PerformMonthEndTransactions();
Console.WriteLine(lineOfCredit.GetAccountHistory());

Yukarıdaki kodu ekleyip programı çalıştırdığınızda aşağıdaki hataya benzer bir şey görürsünüz:

Unhandled exception. System.ArgumentOutOfRangeException: Amount of deposit must be positive (Parameter 'amount')
   at OOProgramming.BankAccount.MakeDeposit(Decimal amount, DateTime date, String note) in BankAccount.cs:line 42
   at OOProgramming.BankAccount..ctor(String name, Decimal initialBalance) in BankAccount.cs:line 31
   at OOProgramming.LineOfCreditAccount..ctor(String name, Decimal initialBalance) in LineOfCreditAccount.cs:line 9
   at OOProgramming.Program.Main(String[] args) in Program.cs:line 29

Not

Gerçek çıkış, projenin bulunduğu klasörün tam yolunu içerir. Klasör adları kısa olduğu için atlandı. Ayrıca, kod biçiminize bağlı olarak satır numaraları biraz farklı olabilir.

İlk bakiyenin 0'dan BankAccount büyük olması gerektiğini varsaydığından bu kod başarısız olur. Sınıfın bir diğer varsayımı BankAccount da bakiyenin negatife gidemiyor olmasıdır. Bunun yerine, hesabın üzerine çizen tüm para çekme işlemleri reddedilir. Bu varsayımların her ikisinin de değişmesi gerekir. Kredi hesabı satırı 0'da başlar ve genellikle negatif bakiyeye sahip olur. Ayrıca, bir müşteri çok fazla borç alırsa ücrete tabidir. İşlem kabul edilir, yalnızca maliyeti daha yüksektir. İlk kural, oluşturucuya en düşük bakiyeyi BankAccount belirten isteğe bağlı bir bağımsız değişken eklenerek uygulanabilir. Varsayılan değer: 0. İkinci kural, türetilmiş sınıfların varsayılan algoritmayı değiştirmesini sağlayan bir mekanizma gerektirir. Bir anlamda, temel sınıfı türetilmiş türe aşırı yükleme olduğunda ne olması gerektiğini "sorar". Varsayılan davranış, bir özel durum oluşturarak işlemi reddetmektir.

İsteğe bağlı minimumBalance bir parametre içeren ikinci bir oluşturucu ekleyerek başlayalım. Bu yeni oluşturucu, var olan oluşturucu tarafından yapılan tüm eylemleri gerçekleştirir. Ayrıca, minimum balance özelliğini ayarlar. Mevcut oluşturucunun gövdesini kopyalayabilirsiniz, ancak bu gelecekte değiştirebileceği iki konum anlamına gelir. Bunun yerine, bir oluşturucu başka bir oluşturucu çağırmak için oluşturucu zincirleme kullanabilirsiniz. Aşağıdaki kod iki oluşturucuyu ve yeni ek alanı gösterir:

private readonly decimal _minimumBalance;

public BankAccount(string name, decimal initialBalance) : this(name, initialBalance, 0) { }

public BankAccount(string name, decimal initialBalance, decimal minimumBalance)
{
    Number = s_accountNumberSeed.ToString();
    s_accountNumberSeed++;

    Owner = name;
    _minimumBalance = minimumBalance;
    if (initialBalance > 0)
        MakeDeposit(initialBalance, DateTime.Now, "Initial balance");
}

Yukarıdaki kod iki yeni tekniği gösterir. İlk olarak, minimumBalance alan olarak readonlyişaretlenir. Bu, nesne oluşturulduğunda değerin değiştirilemeyeceği anlamına gelir. bir BankAccount oluşturulduktan minimumBalance sonra değiştirilemez. İkinci olarak, iki parametre alan oluşturucu uygulaması olarak kullanır : this(name, initialBalance, 0) { } . ifadesi : this() , üç parametresi olan diğer oluşturucuyu çağırır. Bu teknik, istemci kodu birçok oluşturucudan birini seçebilse bile bir nesneyi başlatmak için tek bir uygulamaya sahip olmanıza olanak tanır.

Bu uygulama yalnızca ilk bakiye değerinden 0büyükse çağırırMakeDeposit. Bu, depozitoların pozitif olması gerektiği kuralını korur, ancak kredi hesabının bir 0 bakiyeyle açılmasını sağlar.

Artık sınıfın BankAccount minimum bakiye için salt okunur bir alanı olduğuna göre, son değişiklik sabit kodu 0 yönteminde MakeWithdrawal olarak minimumBalance değiştirmektir:

if (Balance - amount < _minimumBalance)

sınıfını BankAccount genişlettik sonra, aşağıdaki kodda LineOfCreditAccount gösterildiği gibi oluşturucuyu yeni temel oluşturucuyu çağıracak şekilde değiştirebilirsiniz:

public LineOfCreditAccount(string name, decimal initialBalance, decimal creditLimit) : base(name, initialBalance, -creditLimit)
{
}

Oluşturucunun LineOfCreditAccount parametrenin işaretini değiştirerek parametrenin creditLimit anlamıyla eşleşmesine minimumBalance dikkat edin.

Farklı overdraft kuralları

Eklenecek son özellik, işlemi reddetmek yerine kredi limitini aşmak için ücret tahsil etme olanağı sağlar LineOfCreditAccount .

Bir teknik, gerekli davranışı uyguladığınız bir sanal işlev tanımlamaktır. sınıfı, BankAccount yöntemini iki yöntem halinde yeniden düzenlemektedir MakeWithdrawal . Yeni yöntem, para çekme işlemi minimum bakiyenin altına indiğinde belirtilen eylemi yapar. Mevcut MakeWithdrawal yöntem aşağıdaki koda sahiptir:

public void MakeWithdrawal(decimal amount, DateTime date, string note)
{
    if (amount <= 0)
    {
        throw new ArgumentOutOfRangeException(nameof(amount), "Amount of withdrawal must be positive");
    }
    if (Balance - amount < _minimumBalance)
    {
        throw new InvalidOperationException("Not sufficient funds for this withdrawal");
    }
    var withdrawal = new Transaction(-amount, date, note);
    _allTransactions.Add(withdrawal);
}

Şunu aşağıdaki kodla değiştirin:

public void MakeWithdrawal(decimal amount, DateTime date, string note)
{
    if (amount <= 0)
    {
        throw new ArgumentOutOfRangeException(nameof(amount), "Amount of withdrawal must be positive");
    }
    Transaction? overdraftTransaction = CheckWithdrawalLimit(Balance - amount < _minimumBalance);
    Transaction? withdrawal = new(-amount, date, note);
    _allTransactions.Add(withdrawal);
    if (overdraftTransaction != null)
        _allTransactions.Add(overdraftTransaction);
}

protected virtual Transaction? CheckWithdrawalLimit(bool isOverdrawn)
{
    if (isOverdrawn)
    {
        throw new InvalidOperationException("Not sufficient funds for this withdrawal");
    }
    else
    {
        return default;
    }
}

Eklenen yöntem, protectedyalnızca türetilmiş sınıflardan çağrılabileceği anlamına gelir. Bu bildirim, diğer istemcilerin yöntemini çağırmasını engeller. Ayrıca virtual türetilmiş sınıfların davranışı değiştirebilmesi için de böyledir. Dönüş türü bir Transaction?'dir. Ek ? açıklama, yönteminin döndürebileceğini nullgösterir. Para çekme limiti aşıldığında ücret tahsil etmek için aşağıdaki uygulamayı LineOfCreditAccount ekleyin:

protected override Transaction? CheckWithdrawalLimit(bool isOverdrawn) =>
    isOverdrawn
    ? new Transaction(-20, DateTime.Now, "Apply overdraft fee")
    : default;

Geçersiz kılma, hesap fazla çizildiğinde bir ücret işlemi döndürür. Para çekme işlemi sınırı aşmazsa yöntem bir null işlem döndürür. Bu bir ücret olmadığını gösterir. sınıfındaki yönteminize Main aşağıdaki kodu ekleyerek bu değişiklikleri test edin Program :

var lineOfCredit = new LineOfCreditAccount("line of credit", 0, 2000);
// How much is too much to borrow?
lineOfCredit.MakeWithdrawal(1000m, DateTime.Now, "Take out monthly advance");
lineOfCredit.MakeDeposit(50m, DateTime.Now, "Pay back small amount");
lineOfCredit.MakeWithdrawal(5000m, DateTime.Now, "Emergency funds for repairs");
lineOfCredit.MakeDeposit(150m, DateTime.Now, "Partial restoration on repairs");
lineOfCredit.PerformMonthEndTransactions();
Console.WriteLine(lineOfCredit.GetAccountHistory());

Programı çalıştırın ve sonuçları denetleyin.

Özet

Takıldıysanız GitHub depomuzda bu öğreticinin kaynağını görebilirsiniz.

Bu öğreticide Nesne Odaklı programlamada kullanılan tekniklerin birçoğu gösterilmiştir:

  • Farklı hesap türlerinin her biri için sınıflar tanımlarken Soyutlama kullandınız. Bu sınıflar, bu tür bir hesabın davranışını açıklamaktadır.
  • Her sınıfta birçok ayrıntı private tutarken Kapsüllemesini kullandınız.
  • Kodu kaydetmek için sınıfında önceden oluşturulmuş BankAccount olan uygulamadan yararlanırken Devralma'yı kullandınız.
  • Türetilmiş sınıfların bu hesap türü için belirli bir davranış oluşturmak üzere geçersiz kılabileceği yöntemler oluştururken virtual Çok biçimlilik kullandınız.