Objektumorientált programozás (C#)
A C# egy objektumorientált programozási nyelv. Az objektumorientált programozás négy alapelve:
- Absztrakció Az entitások releváns attribútumainak és interakcióinak modellezése osztályokként a rendszer absztrakciós ábrázolásának meghatározásához.
- Beágyazás Elrejti az objektumok belső állapotát és funkcióit, és csak nyilvános függvénykészleten keresztül engedélyezi a hozzáférést.
- Öröklési képesség új absztrakciók létrehozására a meglévő absztrakciók alapján.
- Polimorfizmus Az öröklött tulajdonságok vagy metódusok implementálása különböző módokon több absztrakcióban.
Az előző oktatóanyagban az osztályok bemutatása során absztrakciót és beágyazást is láthatott. Az BankAccount
osztály absztrakciót biztosított a bankszámla fogalmához. A megvalósítást anélkül módosíthatja, hogy az az osztályt használó BankAccount
kód bármelyikét érintené. Mind az osztályok, mind az BankAccount
Transaction
osztályok biztosítják a kódban szereplő fogalmak leírásához szükséges összetevők beágyazását.
Ebben az oktatóanyagban kiterjeszti az alkalmazást, hogy az öröklés és a polimorfizmus használatával új funkciókat adjon hozzá. Emellett funkciókkal is bővítheti az BankAccount
osztályt, kihasználva az előző oktatóanyagban tanult absztrakciós és beágyazási technikákat.
Különböző típusú fiókok létrehozása
A program létrehozása után a program funkcióinak hozzáadására vonatkozó kérelmeket kap. Nagyszerűen működik abban a helyzetben, amikor csak egy bankszámlatípus van. Idővel szükség van a változásra, és a kapcsolódó fióktípusokat a rendszer kéri:
- Kamatkereseti számla, amely minden hónap végén kamatot halmoz fel.
- Egy hitelkeret, amely negatív egyenleggel rendelkezhet, de ha van egyenleg, minden hónapban kamatot számítunk fel.
- Előre fizetett ajándékkártya számla, amely egyetlen befizetéssel kezdődik, és csak kifizethető. Minden hónap elején egyszer tölthető újra.
A különböző fiókok mindegyike hasonló a korábbi oktatóanyagban meghatározott osztályhoz BankAccount
. Másolhatja a kódot, átnevezheti az osztályokat, és módosításokat végezhet. Ez a technika rövid távon működne, de idővel több munka lenne. A módosítások az összes érintett osztályra át lesznek másolva.
Ehelyett létrehozhat új bankszámlatípusokat, amelyek az előző oktatóanyagban létrehozott osztályból öröklik a BankAccount
metódusokat és az adatokat. Ezek az új osztályok kiterjeszthetik az BankAccount
osztályt az egyes típusokhoz szükséges viselkedéssel:
public class InterestEarningAccount : BankAccount
{
}
public class LineOfCreditAccount : BankAccount
{
}
public class GiftCardAccount : BankAccount
{
}
Mindegyik osztály örökli a megosztott viselkedést a megosztott alaposztálytól, az BankAccount
osztálytól. Írja meg az implementációkat az új és a különböző funkciókhoz az egyes származtatott osztályokban. Ezek a származtatott osztályok már rendelkeznek az osztály összes viselkedésével BankAccount
.
Ajánlott minden új osztályt egy másik forrásfájlban létrehozni. A Visual Studióban kattintson a jobb gombbal a projektre, és válassza az Osztály hozzáadása lehetőséget, ha új osztályt szeretne hozzáadni egy új fájlhoz. A Visual Studio Code-ban válassza a Fájl, majd az Új lehetőséget egy új forrásfájl létrehozásához. Mindkét eszközben nevezze el a fájlt, hogy megfeleljen az osztálynak: InterestEarningAccount.cs, LineOfCreditAccount.cs és GiftCardAccount.cs.
Amikor az előző mintában látható módon hozza létre az osztályokat, azt fogja tapasztalni, hogy egyik származtatott osztály sem fordítható le. A konstruktor feladata egy objektum inicializálása. A származtatott osztálykonstruktornak inicializálnia kell a származtatott osztályt, és útmutatást kell adnia a származtatott osztályban szereplő alaposztály-objektum inicializálásához. A megfelelő inicializálás általában további kód nélkül történik. Az BankAccount
osztály egy nyilvános konstruktort deklarál a következő aláírással:
public BankAccount(string name, decimal initialBalance)
A fordító nem hoz létre alapértelmezett konstruktort, amikor ön definiál egy konstruktort. Ez azt jelenti, hogy minden származtatott osztálynak explicit módon kell meghívnia ezt a konstruktort. Deklarálhat egy konstruktort, amely argumentumokat adhat át az alaposztály-konstruktornak. Az alábbi kód a következő konstruktort mutatja:InterestEarningAccount
public InterestEarningAccount(string name, decimal initialBalance) : base(name, initialBalance)
{
}
Az új konstruktor paraméterei megegyeznek az alaposztály-konstruktor paramétertípusával és nevével. A szintaxissal : base()
egy alaposztály-konstruktor hívását jelezheti. Egyes osztályok több konstruktort határoznak meg, és ez a szintaxis lehetővé teszi, hogy kiválaszthatja, hogy melyik alaposztály-konstruktort hívja meg. Miután frissítette a konstruktorokat, fejlesztheti az egyes származtatott osztályok kódját. Az új osztályok követelményei az alábbiak szerint adhatók meg:
- Kamatkereseti fiók:
- A hónap végi egyenleg 2%-ának jóváírása lesz.
- Hitelkeret:
- Negatív egyenlege lehet, de abszolút értékben nem lehet nagyobb, mint a hitelkeret.
- Minden hónapban kamatot számít fel, ha a hónap végi egyenleg nem 0.
- A jóváírási korlátot túllépő minden egyes kifizetésért díjat kell fizetnie.
- Ajándékkártya-fiók:
- Havonta egyszer, a hónap utolsó napján egy adott összeggel tölthető újra.
Láthatja, hogy mindhárom fióktípus rendelkezik olyan művelettel, amely minden hónap végén történik. Az egyes fióktípusok azonban különböző feladatokat végeznek. A kód implementálásához polimorfizmust használ. Egyetlen metódus létrehozása virtual
az BankAccount
osztályban:
public virtual void PerformMonthEndTransactions() { }
Az előző kód bemutatja, hogyan deklarálhat egy metódust a virtual
kulcsszóval az alaposztályban, amelynek egy származtatott osztály más implementációt biztosíthat. A virtual
metódus olyan módszer, amelyben bármely származtatott osztály dönthet úgy, hogy újrakonfigurálja azokat. A származtatott osztályok a override
kulcsszó használatával határozzák meg az új implementációt. Ezt általában "az alaposztály implementálásának felülírása" kifejezésre hivatkozik. A virtual
kulcsszó azt határozza meg, hogy a származtatott osztályok felülírhatják a viselkedést. Deklarálhat abstract
olyan metódusokat is, amelyekben a származtatott osztályoknak felül kell bírálnia a viselkedést. Az alaposztály nem biztosít implementációt egy abstract
metódushoz. Ezután meg kell határoznia a létrehozott két új osztály implementációját. Kezdje a InterestEarningAccount
következővel:
public override void PerformMonthEndTransactions()
{
if (Balance > 500m)
{
decimal interest = Balance * 0.02m;
MakeDeposit(interest, DateTime.Now, "apply monthly interest");
}
}
Adja hozzá a következő kódot a LineOfCreditAccount
következőhöz. A kód tagadja az egyenleget, hogy kiszámítsa a számláról kivont pozitív kamatot:
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");
}
}
Az GiftCardAccount
osztálynak két módosításra van szüksége a hónap végi funkcióinak implementálásához. Először módosítsa a konstruktort úgy, hogy az tartalmazza a havonta hozzáadandó opcionális összeget:
private readonly decimal _monthlyDeposit = 0m;
public GiftCardAccount(string name, decimal initialBalance, decimal monthlyDeposit = 0) : base(name, initialBalance)
=> _monthlyDeposit = monthlyDeposit;
A konstruktor alapértelmezett értéket biztosít az értékhez, így a monthlyDeposit
hívók kihagyhatnak egy 0
havi befizetést. Ezután felülbírálja a PerformMonthEndTransactions
havi betét hozzáadásának metódusát, ha a konstruktor nem nulla értékre van beállítva:
public override void PerformMonthEndTransactions()
{
if (_monthlyDeposit != 0)
{
MakeDeposit(_monthlyDeposit, DateTime.Now, "Add monthly deposit");
}
}
A felülbírálás a konstruktorban beállított havi betétet alkalmazza. Adja hozzá a következő kódot a metódushoz a Main
módosítások teszteléséhez a következőhöz és a GiftCardAccount
InterestEarningAccount
következőhöz:
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());
Ellenőrizze az eredményeket. Most vegyen fel egy hasonló tesztkódot a LineOfCreditAccount
következőhöz:
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());
Amikor hozzáadja az előző kódot, és futtatja a programot, az alábbihoz hasonló hibaüzenet jelenik meg:
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
Feljegyzés
A tényleges kimenet tartalmazza a projekttel rendelkező mappa teljes elérési útját. A mappanevek nem lettek megadva a rövidség kedvéért. Emellett a kódformátumtól függően a sorszámok kissé eltérőek lehetnek.
Ez a kód meghiúsul, mert feltételezi BankAccount
, hogy a kezdeti egyenlegnek 0-nál nagyobbnak kell lennie. Egy másik feltételezést sütött az BankAccount
osztályban, hogy az egyensúly nem megy negatív. Ehelyett minden olyan visszavonást, amely felülírja a fiókot, elutasítja. Mindkét feltételezésnek változnia kell. A hitelkeret 0-kor kezdődik, és általában negatív egyenlege lesz. Továbbá, ha egy ügyfél túl sok pénzt vesz fel, díjat kell fizetnie. A tranzakció elfogadva, csak többe kerül. Az első szabály implementálható úgy, hogy hozzáad egy opcionális argumentumot a BankAccount
konstruktorhoz, amely meghatározza a minimális egyenleget. Az alapértelmezett érték 0
. A második szabályhoz olyan mechanizmus szükséges, amely lehetővé teszi a származtatott osztályok számára az alapértelmezett algoritmus módosítását. Bizonyos értelemben az alaposztály "megkérdezi" a származtatott típust, hogy mi történjen overdraft esetén. Az alapértelmezett viselkedés a tranzakció elvetése kivétellel.
Először adjunk hozzá egy második konstruktort, amely tartalmaz egy opcionális minimumBalance
paramétert. Ez az új konstruktor elvégzi a meglévő konstruktor összes műveletét. Emellett beállítja a minimális egyenleg tulajdonságot is. Másolhatja a meglévő konstruktor törzsét, de ez azt jelenti, hogy a jövőben két hely változik. Ehelyett konstruktorláncolással egy konstruktor meghívhat egy másikat. Az alábbi kód a két konstruktort és az új további mezőt mutatja be:
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");
}
Az előző kód két új technikát mutat be. Először is a minimumBalance
mező a következőként readonly
van megjelölve: . Ez azt jelenti, hogy az érték nem módosítható az objektum létrehozása után. BankAccount
A létrehozás után a minimumBalance
nem módosítható. Másodszor, a két paramétert használó konstruktor implementálásként használja : this(name, initialBalance, 0) { }
. A : this()
kifejezés meghívja a másik konstruktort, az egyiket három paraméterrel. Ez a technika lehetővé teszi, hogy egyetlen implementációval inicializáljon egy objektumot, annak ellenére, hogy az ügyfélkód számos konstruktor közül választhat.
Ez a megvalósítás csak akkor hív meg MakeDeposit
, ha a kezdeti egyenleg nagyobb, mint 0
. Ez megőrzi azt a szabályt, hogy a betéteknek pozitívnak kell lenniük, de lehetővé teszi, hogy a hitelszámla egyenleggel 0
legyen megnyitva.
Most, hogy az BankAccount
osztály rendelkezik egy írásvédett mezővel a minimális egyenleghez, a végső változás az, hogy a kemény kódot 0
a MakeWithdrawal
metódusban a következőre minimumBalance
módosítja:
if (Balance - amount < _minimumBalance)
Az BankAccount
osztály kiterjesztése után módosíthatja a konstruktort LineOfCreditAccount
, hogy meghívja az új alapkonstruktort az alábbi kódban látható módon:
public LineOfCreditAccount(string name, decimal initialBalance, decimal creditLimit) : base(name, initialBalance, -creditLimit)
{
}
Figyelje meg, hogy a LineOfCreditAccount
konstruktor módosítja a creditLimit
paraméter jelét, hogy az megfeleljen a minimumBalance
paraméter jelentésének.
Különböző overdraft szabályok
Az utolsó hozzáadandó funkció lehetővé teszi, LineOfCreditAccount
hogy a tranzakció elutasítása helyett díjat számítsunk fel a hitelkeret túllépése után.
Az egyik módszer egy olyan virtuális függvény definiálása, amelyben megvalósítja a szükséges viselkedést. Az BankAccount
osztály két metódusba alakítja át a MakeWithdrawal
metódust. Az új módszer akkor hajtja végre a megadott műveletet, ha a visszavonás a minimum alatti egyenleget veszi fel. A meglévő MakeWithdrawal
metódus a következő kóddal rendelkezik:
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);
}
Cserélje le a következő kódra:
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;
}
}
A hozzáadott metódus az protected
, ami azt jelenti, hogy csak származtatott osztályokból hívható meg. Ez a deklaráció megakadályozza, hogy más ügyfelek meghívják a metódust. Emellett a virtual
származtatott osztályok is módosíthatják a viselkedést. A visszatérési típus egy Transaction?
. A ?
széljegyzet azt jelzi, hogy a metódus visszatérhet null
. Adja hozzá a következő megvalósítást, LineOfCreditAccount
hogy díjat számítsunk fel a visszavonási korlát túllépésekor:
protected override Transaction? CheckWithdrawalLimit(bool isOverdrawn) =>
isOverdrawn
? new Transaction(-20, DateTime.Now, "Apply overdraft fee")
: default;
A felülbírálás egy díjtranzakciót ad vissza, amikor a fiók túl van állítva. Ha a visszavonás nem lépi túl a korlátot, a metódus egy tranzakciót null
ad vissza. Ez azt jelzi, hogy nincs díj. A módosítások teszteléséhez adja hozzá a következő kódot a Main
metódushoz az Program
osztályban:
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());
Futtassa a programot, és ellenőrizze az eredményeket.
Összegzés
Ha elakadt, az oktatóanyag forrását a GitHub-adattárban tekintheti meg.
Ez az oktatóanyag számos, az objektumorientált programozásban használt technikát mutatott be:
- Az Absztrakciót használta, amikor osztályokat definiált az egyes fióktípusokhoz. Ezek az osztályok leírták az ilyen típusú fiók viselkedését.
- Az Encapsulation parancsot használta, amikor az egyes osztályokban sok részletet
private
tartott meg. - Az öröklést akkor használta, amikor az osztályban már létrehozott implementációt használta a
BankAccount
kód mentéséhez. - Polimorfizmust használt, amikor olyan metódusokat hozott létre
virtual
, amelyek származtatott osztályai felülbírálhatók az adott fióktípus adott viselkedésének létrehozásához.