Algoritmy a programování IV

Týden 4

C# je objektově orientovaný programovací jazyk. Čtyři základní principy objektově orientovaného programování jsou:

  • Abstrakce Modelování relevantních atributů a interakcí entit jako tříd za účelem definování abstraktní reprezentace systému
  • Zapouzdření Skrytí interního stavu a funkčnosti objektu a povolení přístupu pouze prostřednictvím veřejné sady funkcí
  • Dědičnosti Možnost vytvářet nové abstrakce na základě existujících abstrakcí.
  • Polymorfismus Schopnost implementovat zděděné vlastnosti nebo metody různými způsoby napříč více abstrakcemi.

V předchozím kurzu jste viděli úvod do třídjak abstrakci , tak zapouzdření. Třída BankAccount poskytla abstrakci pro koncept bankovního účtu. Můžete upravit jeho implementaci, aniž by to mělo vliv na kód, který používal BankAccount třídu . BankAccount Třídy a Transaction poskytují zapouzdření komponent potřebných k popisu těchto konceptů v kódu.

V tomto kurzu tuto aplikaci rozšíříte tak, aby využívala dědičnost a polymorfismus k přidání nových funkcí. Do třídy také přidáte funkce BankAccount s využitím technik abstrakce a zapouzdření , které jste se naučili v předchozím kurzu.

Vytvoření různých typů účtů

Po vytvoření tohoto programu obdržíte žádosti o přidání funkcí do tohoto programu. Funguje to skvěle v situaci, kdy existuje pouze jeden typ bankovního účtu. V průběhu času se vyžadují změny potřeb a související typy účtů:

  • Účet pro příjmy z úroků, na který se načítá úrok na konci každého měsíce.
  • Úvěrový řádek, který může mít záporný zůstatek, ale pokud je zůstatek, každý měsíc se účtuje úrok.
  • Předplacený účet dárkové karty, který začíná jediným vkladem a dá se splácit jenom Může se znovu naplnit jednou na začátku každého měsíce.

Všechny tyto různé účty jsou podobné BankAccount třídě definované v předchozím kurzu. Tento kód můžete zkopírovat, přejmenovat třídy a provést úpravy. Tato technika by fungovala krátkodobě, ale v průběhu času by to bylo více práce. Všechny změny se zkopírují do všech ovlivněných tříd.

Místo toho můžete vytvořit nové typy bankovních účtů, které dědí metody a data z BankAccount třídy vytvořené v předchozím kurzu. Tyto nové třídy mohou rozšířit BankAccount třídu o konkrétní chování potřebné pro každý typ:


public class InterestEarningAccount : BankAccount
{
}public class LineOfCreditAccount : BankAccount
{
}public class GiftCardAccount : BankAccount
{
}

Každá z těchto tříd dědí sdílené chování ze své sdílené základní třídyBankAccount třídy. Napište implementace pro nové a různé funkce v každé z odvozených tříd. Tyto odvozené třídy již mají veškeré chování definované ve BankAccount třídě .

Je vhodné vytvořit každou novou třídu v jiném zdrojovém souboru. V sadě Visual Studio můžete kliknout pravým tlačítkem na projekt a vybrat přidat třídu a přidat novou třídu do nového souboru. V editoru Visual Studio Code vyberte File (Soubor ) a pak New (Nový ) a vytvořte nový zdrojový soubor. V obou nástrojích pojmenujte soubor tak, aby odpovídal třídě : InterestEarningAccount.csLineOfCreditAccount.cs a GiftCardAccount.cs.

Když vytvoříte třídy, jak je znázorněno v předchozí ukázce, zjistíte, že se žádná z odvozených tříd nekompiluje. Konstruktor je zodpovědný za inicializaci objektu. Konstruktor odvozené třídy musí inicializovat odvozenou třídu a poskytnout pokyny, jak inicializovat objekt základní třídy zahrnutý v odvozené třídě. Správná inicializace obvykle probíhá bez jakéhokoli dalšího kódu. Třída BankAccount deklaruje jeden veřejný konstruktor s následujícím podpisem:


public BankAccount(string name, decimal initialBalance)

Kompilátor negeneruje výchozí konstruktor, když konstruktor definujete sami. To znamená, že každá odvozená třída musí explicitně volat tento konstruktor. Deklarujete konstruktor, který může předat argumenty konstruktoru základní třídy. Následující kód ukazuje konstruktor pro InterestEarningAccount:


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

Parametry tohoto nového konstruktoru odpovídají typu parametru a názvům konstruktoru základní třídy. Syntaxi : base() použijete k označení volání konstruktoru základní třídy. Některé třídy definují více konstruktorů a tato syntaxe umožňuje vybrat, který konstruktor základní třídy voláte. Po aktualizaci konstruktorů můžete vyvinout kód pro každou odvozenou třídu. Požadavky na nové třídy lze uvést takto:

  • Účet pro získání úroků:
    • Získá kredit ve hodnotě 2 % zůstatku na konci měsíce.
  • Úvěrový řádek:
    • Může mít záporný zůstatek, ale nesmí být vyšší v absolutní hodnotě, než je úvěrový limit.
    • Každý měsíc, kdy zůstatek na konci měsíce není 0, se bude účtovat poplatek za úrok.
    • Za každý výběr, který překročí úvěrový limit, se bude účtovat poplatek.
  • Účet dárkové karty:
    • Určitou částku je možné znovu naplnit jednou měsíčně, a to poslední den v měsíci.

Vidíte, že všechny tři tyto typy účtů mají akci, která se provede na konci každého měsíce. Každý typ účtu ale dělá jiné úkoly. K implementaci tohoto kódu použijete polymorfismus . Vytvořte jednu virtual metodu BankAccount ve třídě :


public virtual void PerformMonthEndTransactions() { }

Předchozí kód ukazuje, jak pomocí klíčového virtual slova deklarovat metodu v základní třídě, pro kterou může odvozená třída poskytnout jinou implementaci. Metoda virtual je metoda, ve které se může libovolná odvozená třída rozhodnout provést znovu. Odvozené třídy používají override klíčové slovo k definování nové implementace. Obvykle se to označuje jako "přepsání implementace základní třídy". Klíčové virtual slovo určuje, že odvozené třídy mohou přepsat chování. Můžete také deklarovat abstract metody, kde odvozené třídy musí přepsat chování. Základní třída neposkytuje implementaci metody abstract . Dále musíte definovat implementaci pro dvě z nových tříd, které jste vytvořili. Začněte s InterestEarningAccount:


 500m)
    {
        decimal interest = Balance * 0.02m;
        MakeDeposit(interest, DateTime.Now, "apply monthly interest");
    }
}
" style="box-sizing: inherit; outline-color: inherit; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 1em; direction: ltr; border: 0px; padding: 0px; line-height: 1.3571; display: block; position: relative;">public override void PerformMonthEndTransactions()
{
    if (Balance > 500m)
    {
        decimal interest = Balance * 0.02m;
        MakeDeposit(interest, DateTime.Now, "apply monthly interest");
    }
}

Do přidejte následující kód LineOfCreditAccount. Kód neguje zůstatek pro výpočet kladného úroku, který se stahuje z účtu:


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

Třída GiftCardAccount potřebuje dvě změny, aby implementovala funkci na konci měsíce. Nejprve upravte konstruktor tak, aby zahrnoval volitelnou částku, která se má přičítat každý měsíc:


 _monthlyDeposit = monthlyDeposit;
" style="box-sizing: inherit; outline-color: inherit; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 1em; direction: ltr; border: 0px; padding: 0px; line-height: 1.3571; display: block; position: relative;">private readonly decimal _monthlyDeposit = 0m;public GiftCardAccount(string name, decimal initialBalance, decimal monthlyDeposit = 0) : base(name, initialBalance)
    => _monthlyDeposit = monthlyDeposit;

Konstruktor poskytuje výchozí hodnotu pro monthlyDeposit hodnotu, takže volající mohou vynechat 0 pro žádný měsíční vklad. Dále přepište metodu PerformMonthEndTransactions pro přidání měsíčního vkladu, pokud byla v konstruktoru nastavena na nenulovou hodnotu:


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

Přepsání použije měsíční vklad nastavený v konstruktoru. Do metody přidejte Main následující kód, který otestuje tyto změny pro GiftCardAccount a :InterestEarningAccount


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());

Ověřte výsledky. Teď přidejte podobnou sadu testovacích kódů pro LineOfCreditAccount:

C#
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());

Když přidáte předchozí kód a spustíte program, zobrazí se něco jako následující chyba:

Konzola
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

Začněme přidáním druhého konstruktoru, který obsahuje volitelný minimumBalance parametr. Tento nový konstruktor provádí všechny akce provedené existujícím konstruktorem. Nastaví také vlastnost minimálního zůstatku. Můžete zkopírovat tělo existujícího konstruktoru, ale to znamená, že se v budoucnu změní dvě umístění. Místo toho můžete použít zřetězování konstruktoru , aby jeden konstruktor volal jiný. Následující kód ukazuje dva konstruktory a nové další pole:


 0)
        MakeDeposit(initialBalance, DateTime.Now, "Initial balance");
}
" style="box-sizing: inherit; outline-color: inherit; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 1em; direction: ltr; border: 0px; padding: 0px; line-height: 1.3571; display: block; position: relative;">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");
}

Předchozí kód ukazuje dvě nové techniky. Nejprve se pole označí minimumBalance jako readonly. To znamená, že hodnotu nelze po vytvoření objektu změnit. Jakmile je BankAccount objekt vytvořený, nemůže se minimumBalance změnit. Za druhé konstruktor, který přebírá dva parametry, používá : this(name, initialBalance, 0) { } jako svou implementaci. Výraz : this() volá druhý konstruktor, ten se třemi parametry. Tato technika umožňuje mít jednu implementaci pro inicializaci objektu, i když klientský kód může zvolit jeden z mnoha konstruktorů.

Tato implementace volá MakeDeposit pouze v případě, že počáteční zůstatek je větší než 0. Tím se zachová pravidlo, že vklady musí být kladné, ale umožní otevření úvěrového účtu se zůstatkem 0 .

Teď, když BankAccount má třída pole jen pro čtení minimálního zůstatku, je poslední změnou změna v metodě na pevný kód minimumBalance0MakeWithdrawal:


if (Balance - amount < _minimumBalance)

Po rozšíření BankAccount třídy můžete upravit LineOfCreditAccount konstruktor tak, aby volal nový základní konstruktor, jak je znázorněno v následujícím kódu:


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

Všimněte si, že LineOfCreditAccount konstruktor změní znaménko parametru creditLimit tak, aby odpovídalo významu parametru minimumBalance .