Sdílet prostřednictvím


programování Object-Oriented (C#)

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 pro definování abstraktní reprezentace systému
  • Zapouzdření Skrytí vnitřního stavu a funkčnosti objektu a povolení přístupu pouze prostřednictvím veřejné sady funkcí.
  • Dědičnost Schopnost vytvářet nové abstrakce na základě existujících abstrakcí.
  • Polymorfizmus Schopnost implementovat zděděné vlastnosti nebo metody různými způsoby napříč více abstrakcemi.

V předchozím kurzu Úvod do tříd jste viděli jak abstrakci, tak zapouzdření. Třída BankAccount poskytla abstrakci pro koncept bankovního účtu. Můžete upravit její implementaci, aniž by to ovlivnilo jakýkoli kód, který použil BankAccount třídu. Obě třídy BankAccountTransaction poskytují zapouzdření součástí potřebných k popisu těchto konceptů v kódu.

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

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

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

  • Účet, který zhodnocuje úrok na konci každého měsíce.
  • Úvěrový rámec, který může mít zápornou bilanci, ale pokud je bilance kladná, každý měsíc se účtuje úrok.
  • Předplacený účet dárkové karty, který začíná jedním vkladem a lze jej pouze splatit. Na začátku každého měsíce je možné ho znovu doplnit.

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 v krátkodobém horizontu, ale v průběhu času by to bylo více práce. Všechny změny by se zkopírovaly napříč všemi ovlivněnými třídami.

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 specifické 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řídy, BankAccount třídy. Napište implementace pro nové a různé funkce v každé odvozené třídy. 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 myši na projekt a vybrat přidat třídu pro přidání nové třídy do nového souboru. V editoru Visual Studio Code vyberte Soubor a potom Nový a vytvořte nový zdrojový soubor. V obou nástrojích pojmenujte soubor tak, aby odpovídal třídě: InterestEarningAccount.cs, LineOfCreditAccount.cs a GiftCardAccount.cs.

Když vytvoříte třídy, jak je znázorněno v předchozí ukázce, zjistíte, že žádná z vašich odvozených tříd se kompiluje. Konstruktor je zodpovědný za inicializaci objektu. Konstruktor odvozené třídy musí inicializovat odvozenou třídu a poskytnout pokyny k inicializaci objektu základní třídy zahrnuté do odvozené třídy. Správné inicializace obvykle probíhá bez 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 při definování konstruktoru 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 a názvům parametru konstruktoru základní třídy. Použijte syntaxi : base() 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 vyvíjet kód pro každou odvozenou třídu. Požadavky na nové třídy lze uvést takto:

  • Účet pro získání úroku:
    • Získá kredit 2% za zůstatek na konci měsíce.
  • Úvěrový rámec
    • Může mít záporný zůstatek, ale nesmí být větší v absolutní hodnotě než limit kreditu.
    • Každý měsíc se vám bude účtovat poplatek za úrok, kdy zůstatek na konci měsíce není 0.
    • Za každý výběr, který překročí úvěrový limit, bude účtován poplatek.
  • Účet dárkové karty:
    • Lze naplnit zadanou částkou jednou za měsíc v posledním dni v měsíci.

Vidíte, že všechny tři z těchto typů účtů mají akci, která se koná 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 metodu virtual ve třídě BankAccount.

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 odvozená třída může poskytnout jinou implementaci. Metoda virtual je metoda, kde se každá odvozená třída může rozhodnout pro reimplement. Odvozené třídy používají override klíčové slovo k definování nové implementace. Obvykle se na to odkazuje jako "přepsání implementace základní třídy". Klíčové virtual slovo specifikuje, že odvozené třídy mohou přepsat původní funkčnost. 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 je potřeba definovat implementaci pro dvě z nových tříd, které jste vytvořili. Začněte s:InterestEarningAccount

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

Přidejte následující kód do LineOfCreditAccount. Kód neguje zůstatek pro výpočet kladného úroku, který je stažen 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 k implementaci její funkcionality na konci měsíce. Nejprve upravte konstruktor tak, aby zahrnoval volitelnou částku, kterou chcete přidat každý měsíc:

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, takže volající mohou vynechat 0 a nemít žádný měsíční vklad. Dále přepište metodu PerformMonthEndTransactions pro přidání měsíčního vkladu, pokud byla nastavena na nenulovou hodnotu v konstruktoru:

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

Přepsání aplikuje měsíční vklad, který je nastavený v konstruktoru. Přidejte do metody Main následující kód pro testování těchto změn u 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ího kódu pro LineOfCreditAccount:

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:

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

Poznámka:

Skutečný výstup zahrnuje úplnou cestu ke složce s projektem. Názvy složek byly vynechány pro stručnost. V závislosti na formátu kódu se čísla řádků můžou mírně lišit.

Tento kód selže, protože BankAccount předpokládá, že počáteční zůstatek musí být větší než 0. Dalším předpokladem začleněným do BankAccount třídy je, že zůstatek nemůže být záporný. Místo toho je zamítnut jakýkoli výběr, který přečerpá účet. Oba tyto předpoklady se musí změnit. Účet úvěrové linie začíná na 0 a obvykle má záporný zůstatek. Také, pokud si zákazník půjčí příliš mnoho peněz, zaúčtovají poplatek. Transakce je přijata, pouze stojí více. První pravidlo lze implementovat přidáním volitelného argumentu do konstruktoru BankAccount , který určuje minimální zůstatek. Výchozí hodnota je 0. Druhé pravidlo vyžaduje mechanismus, který umožňuje odvozené třídy upravit výchozí algoritmus. Základní třída "žádá" odvozený typ, co by se mělo stát, když dojde k přečerpání. Výchozí chování je odmítnout transakci vyvoláním výjimky.

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 stávajícího konstruktoru, ale to znamená, že v budoucnu budete muset provést změny na dvou místech. Místo toho můžete použít řetězení konstruktorů k tomu, aby jeden konstruktor volal jiný. Následující kód ukazuje dva konstruktory a nové další pole:

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. minimumBalance Nejprve je pole označeno jako readonly. To znamená, že hodnotu nelze po vytvoření objektu změnit. Po vytvoření BankAccount nelze 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á jiný 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 je počáteční zůstatek větší než 0. Tím se zachová pravidlo, že vklady musí být kladné, ale umožní to, aby se úvěrový účet otevřel s zůstatkem 0 .

Teď, když třída BankAccount má pole pouze pro čtení pro minimální zůstatek, poslední změna je změnit pevně zakódovanou hodnotu 0 na minimumBalance v metodě MakeWithdrawal.

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ídal významu parametru minimumBalance .

Různá pravidla přečertání

Poslední funkce, která se má přidat, umožňuje LineOfCreditAccount účtovat poplatek za překročení limitu kreditu místo odmítnutí transakce.

Jednou z technik je definování virtuální funkce, ve které implementujete požadované chování. Třída BankAccount refaktoruje metodu MakeWithdrawal do dvou metod. Nová metoda provede zadanou akci, když výběr sníží zůstatek pod minimální hodnotu. MakeWithdrawal Existující metoda má následující kód:

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

Nahraďte ho následujícím kódem:

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

Přidaná metoda je protected, což znamená, že může být volána pouze z odvozených tříd. Tato deklarace brání jiným klientům v volání metody. Je to také virtual proto, aby odvozené třídy mohly změnit chování. Návratový typ je Transaction?. Poznámka ? označuje, že metoda může vrátit null. Přidejte následující implementaci do LineOfCreditAccount, aby byl účtován poplatek při překročení limitu pro výběr.

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

Překročení vrátí poplatkovou transakci při přečerpání účtu. Pokud výběr nepřejde přes limit, metoda vrátí null transakci. To znamená, že není žádný poplatek. Otestujte tyto změny přidáním následujícího kódu do vaší Main metody ve Program třídě:

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

Spusťte program a zkontrolujte výsledky.

Shrnutí

Pokud jste se zasekli, uvidíte zdroj tohoto kurzu v našem úložišti GitHub.

V tomto kurzu jsme si ukázali řadu technik používaných v Object-Oriented programování:

  • Abstrakce jste použili při definování tříd pro každý z různých typů účtů. Tyto třídy popsaly chování pro tento typ účtu.
  • Zapouzdření jste použili tak, že jste zachovali mnoho podrobností private v každé třídě.
  • Dědičnost jste použili při využití implementace již vytvořené ve BankAccount třídě k uložení kódu.
  • Polymorfismus jste použili při vytváření virtual metod, které odvozené třídy mohou přepsat k vytvoření specifického chování pro daný typ účtu.