Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
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 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 vlastnosti BankAccount, využívající techniky 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 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á jediným vkladem a lze ho 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.
Při vytváření tříd, 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ává kredit ve výši 2 % zůstatku 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.
- Úrok se každý měsíc účtuje, pokud konečný zůstatek na konci měsíce není 0.
- Při každém výběru, který překročí limit kreditu, se účtuje 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, kterou může každá odvozená třída znovu přepsat. Když odvozená třída definuje novou implementaci, nazývá se přepsání implementace základní třídy. Odvozené třídy používají override klíčové slovo k definování nové implementace. 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
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. Do metody Main přidejte následující kód. Tento kód 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í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());
Přidejte předchozí kód a spusťte program. Výstup by měl vypadat přibližně 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. Úvěrový účet začíná s hodnotou 0 a obecně mívá 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é 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)
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(amount);
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. Tento kód účtuje poplatek za 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í
privatev každé třídě. -
Dědičnost jste použili při použití implementace již vytvořené ve
BankAccounttřídě k uložení kódu. -
Polymorfismus jste použili při vytváření
virtualmetod, které odvozené třídy mohou přepsat k vytvoření specifického chování pro daný typ účtu.