Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
C# adalah bahasa pemrograman berorientasi objek. Empat prinsip dasar pemrograman berorientasi objek adalah:
- Abstraksi Memodelkan atribut dan interaksi entitas yang relevan sebagai kelas untuk menentukan representasi abstrak sistem.
- Enkapsulasi Menyembunyikan status internal dan fungsionalitas objek dan hanya mengizinkan akses melalui serangkaian fungsi publik.
- Warisan Kemampuan untuk membuat abstraksi baru berdasarkan abstraksi yang ada.
- Polimorfisme Kemampuan untuk menerapkan properti atau metode yang diwariskan dengan cara yang berbeda di beberapa abstraksi.
Dalam tutorial sebelumnya, pengenalan kelas, Anda telah melihat abstraksi dan enkapulasi.
BankAccount
kelas menyediakan abstraksi untuk konsep rekening bank. Anda dapat memodifikasi implementasinya tanpa memengaruhi kode apa pun yang menggunakan BankAccount
kelas . Kelas BankAccount
dan Transaction
menyediakan enkapsulasi komponen yang diperlukan untuk menggambarkan konsep tersebut dalam kode.
Dalam tutorial ini, Anda akan memperluas aplikasi tersebut untuk menggunakan warisan dan polimorfisme untuk menambahkan fitur baru. Anda juga akan menambahkan fitur ke kelas BankAccount
, memanfaatkan teknik abstraksi dan enkapulasi yang Anda pelajari dalam tutorial sebelumnya.
Membuat berbagai jenis akun
Setelah membangun program ini, Anda mendapatkan permintaan untuk menambahkan fitur ke dalamnya. Ini berfungsi dengan baik dalam situasi di mana hanya ada satu jenis rekening bank. Seiring waktu, kebutuhan berkembang, dan jenis akun terkait diminta.
- Akun penghasilan bunga yang mengumpulkan bunga pada akhir setiap bulan.
- Garis kredit yang dapat memiliki saldo negatif, tetapi ketika ada saldo, ada biaya bunga setiap bulan.
- Akun kartu hadiah prabayar yang dimulai dengan satu deposit, dan hanya dapat dilunasi. Ini dapat diisi ulang sekali pada awal setiap bulan.
Semua akun berbeda ini mirip dengan kelas BankAccount
yang telah ditentukan dalam tutorial sebelumnya. Anda dapat menyalin kode tersebut, mengganti nama kelas, dan membuat modifikasi. Teknik itu akan bekerja dalam jangka pendek, tetapi akan lebih banyak pekerjaan dari waktu ke waktu. Setiap perubahan akan disalin di semua kelas yang terpengaruh.
Sebagai gantinya, Anda dapat membuat jenis rekening bank baru yang mewarisi metode dan data dari kelas yang BankAccount
dibuat dalam tutorial sebelumnya. Kelas baru ini dapat memperluas BankAccount
kelas dengan perilaku spesifik yang diperlukan untuk setiap jenis:
public class InterestEarningAccount : BankAccount
{
}
public class LineOfCreditAccount : BankAccount
{
}
public class GiftCardAccount : BankAccount
{
}
Masing-masing kelas ini mewarisi perilaku bersama dari kelas dasar mereka, yaitu kelas BankAccount
. Tulis implementasi untuk fungsionalitas baru dan berbeda di setiap kelas turunan. Kelas turunan ini sudah memiliki semua perilaku yang ditentukan di BankAccount
kelas .
Ini adalah praktik yang baik untuk membuat setiap kelas baru dalam file sumber yang berbeda. Di Visual Studio, Anda dapat mengklik kanan proyek, dan memilih tambahkan kelas untuk menambahkan kelas baru dalam file baru. Di Visual Studio Code, pilih File lalu Baru untuk membuat file sumber baru. Di salah satu alat, beri nama file agar sesuai dengan kelas: InterestEarningAccount.cs, LineOfCreditAccount.cs, dan GiftCardAccount.cs.
Saat membuat kelas seperti yang ditunjukkan dalam sampel sebelumnya, Anda akan menemukan bahwa tidak ada kelas turunan yang dikompilasi. Konstruktor bertanggung jawab untuk menginisialisasi objek. Konstruktor kelas turunan harus menginisialisasi kelas turunan, dan memberikan instruksi tentang cara menginisialisasi objek kelas dasar yang disertakan dalam kelas turunan. Inisialisasi yang tepat biasanya terjadi tanpa kode tambahan. Kelas BankAccount
mendeklarasikan satu konstruktor publik dengan tanda tangan berikut:
public BankAccount(string name, decimal initialBalance)
Pengkompilasi tidak menghasilkan konstruktor default saat Anda menentukan sendiri konstruktor. Itu berarti setiap kelas turunan harus secara eksplisit memanggil konstruktor ini. Anda mendeklarasikan konstruktor yang dapat meneruskan argumen ke konstruktor kelas dasar. Kode berikut menunjukkan konstruktor untuk InterestEarningAccount
:
public InterestEarningAccount(string name, decimal initialBalance) : base(name, initialBalance)
{
}
Parameter untuk konstruktor baru ini cocok dengan jenis parameter dan nama konstruktor kelas dasar. Anda menggunakan sintaks : base()
untuk menunjukkan pemanggilan ke konstruktor kelas dasar. Beberapa kelas menentukan beberapa konstruktor, dan sintaks ini memungkinkan Anda memilih konstruktor kelas dasar mana yang Anda panggil. Setelah memperbarui konstruktor, Anda dapat mengembangkan kode untuk setiap kelas turunan. Persyaratan untuk kelas baru dapat dinyatakan sebagai berikut:
- Akun penghasilan bunga:
- Akan menerima kredit sebesar 2% dari saldo akhir bulan.
- Fasilitas kredit
- Dapat memiliki saldo negatif, tetapi tidak lebih besar dalam nilai absolut daripada batas kredit.
- Akan dikenakan biaya bunga setiap bulan di mana saldo akhir bulan bukan 0.
- Akan dikenakan biaya pada setiap penarikan yang melebihi batas kredit.
- Akun kartu hadiah:
- Dapat diisi ulang dengan jumlah yang ditentukan setiap bulan sekali, pada hari terakhir dalam sebulan.
Anda dapat melihat bahwa ketiga jenis akun ini memiliki tindakan yang terjadi pada akhir setiap bulan. Namun, setiap jenis akun melakukan tugas yang berbeda. Anda menggunakan polimorfisme untuk mengimplementasikan kode ini. Buat satu metode virtual
dalam kelas BankAccount
public virtual void PerformMonthEndTransactions() { }
Kode sebelumnya menunjukkan bagaimana Anda menggunakan virtual
kata kunci untuk mendeklarasikan metode di kelas dasar tempat kelas turunan dapat memberikan implementasi yang berbeda. Metode virtual
adalah metode di mana setiap kelas turunan dapat memilih untuk mengimplementasikan ulang. Kelas turunan menggunakan override
kata kunci untuk menentukan implementasi baru. Biasanya Anda menyebutnya sebagai "mengesampingkan implementasi kelas dasar". Kata virtual
kunci menentukan bahwa kelas turunan dapat mengambil alih perilaku. Anda juga dapat mendeklarasikan abstract
metode di mana kelas turunan harus mengambil alih perilaku. Kelas dasar tidak menyediakan implementasi untuk abstract
metode . Selanjutnya, Anda perlu menentukan implementasi untuk dua kelas baru yang telah Anda buat. Mulailah dengan InterestEarningAccount
:
public override void PerformMonthEndTransactions()
{
if (Balance > 500m)
{
decimal interest = Balance * 0.02m;
MakeDeposit(interest, DateTime.Now, "apply monthly interest");
}
}
Tambahkan kode berikut ke LineOfCreditAccount
. Kode meniadakan saldo untuk menghitung biaya bunga positif yang ditarik dari akun:
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");
}
}
Kelas GiftCardAccount
membutuhkan dua perubahan untuk mengimplementasikan fungsionalitas akhir bulan. Pertama, ubah konstruktor untuk menyertakan jumlah opsional untuk ditambahkan setiap bulan:
private readonly decimal _monthlyDeposit = 0m;
public GiftCardAccount(string name, decimal initialBalance, decimal monthlyDeposit = 0) : base(name, initialBalance)
=> _monthlyDeposit = monthlyDeposit;
Konstruktor menyediakan nilai default untuk monthlyDeposit
sehingga pemanggil dapat tidak menyertakan 0
jika tidak ada setoran bulanan. Selanjutnya, ambil alih PerformMonthEndTransactions
metode untuk menambahkan setoran bulanan, jika diatur ke nilai bukan nol di konstruktor:
public override void PerformMonthEndTransactions()
{
if (_monthlyDeposit != 0)
{
MakeDeposit(_monthlyDeposit, DateTime.Now, "Add monthly deposit");
}
}
Penimpaan menerapkan setoran bulanan yang ditetapkan dalam konstruktor. Tambahkan kode berikut ke Main
metode untuk menguji perubahan ini untuk GiftCardAccount
dan 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());
Verifikasi hasilnya. Sekarang, tambahkan sekumpulan kode pengujian serupa untuk 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());
Saat menambahkan kode sebelumnya dan menjalankan program, Anda akan melihat sesuatu seperti kesalahan berikut:
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
Nota
Hasil keluaran yang sebenarnya mencakup jalur lengkap ke folder proyek. Nama folder dihilangkan demi kejelasan. Selain itu, tergantung pada format kode Anda, nomor baris mungkin sedikit berbeda.
Kode ini gagal karena BankAccount
mengasumsikan bahwa saldo awal harus lebih besar dari 0. Asumsi lain yang tertanam dalam BankAccount
kelas adalah bahwa saldo tidak bisa negatif. Sebaliknya, setiap penarikan yang menyebabkan akun terlampaui ditolak. Kedua asumsi tersebut perlu berubah. Akun kredit bergulir dimulai dari 0, dan umumnya akan memiliki saldo negatif. Juga, jika pelanggan meminjam terlalu banyak uang, mereka dikenakan biaya. Transaksi diterima, biayanya lebih mahal. Aturan pertama dapat diimplementasikan dengan menambahkan argumen opsional ke BankAccount
konstruktor yang menentukan keseimbangan minimum. Defaultnya adalah 0
. Aturan kedua memerlukan mekanisme yang memungkinkan kelas turunan untuk memodifikasi algoritma default. Dalam arti tertentu, kelas dasar "menanyakan" jenis turunan apa yang seharusnya terjadi ketika ada kekurangan dana. Perilaku defaultnya adalah menolak transaksi dengan melemparkan pengecualian.
Mari kita mulai dengan menambahkan konstruktor kedua yang menyertakan parameter opsional minimumBalance
. Konstruktor baru ini melakukan semua tindakan yang dilakukan oleh konstruktor yang ada. Selain itu, ini mengkonfigurasi properti saldo minimum. Anda bisa menyalin isi dari konstruktor yang ada, tetapi itu berarti nanti ada dua tempat yang harus diubah. Sebagai gantinya, Anda dapat menggunakan penautan konstruktor agar satu konstruktor memanggil konstruktor lainnya. Kode berikut menunjukkan dua konstruktor dan bidang tambahan baru:
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");
}
Kode sebelumnya menunjukkan dua teknik baru. Pertama, minimumBalance
kolom ditandai sebagai readonly
. Itu berarti nilai tidak dapat diubah setelah objek dibangun.
BankAccount
Setelah dibuat, minimumBalance
tidak dapat berubah. Kedua, konstruktor yang mengambil dua parameter menggunakan : this(name, initialBalance, 0) { }
sebagai implementasinya. Ekspresi : this()
memanggil konstruktor lain, yang memiliki tiga parameter. Teknik ini memungkinkan Anda memiliki satu implementasi untuk menginisialisasi objek meskipun kode klien dapat memilih salah satu dari banyak konstruktor.
Implementasi ini hanya memanggil MakeDeposit
jika saldo awal lebih besar dari 0
. Itu mempertahankan aturan bahwa deposito harus positif, namun memungkinkan akun kredit terbuka dengan 0
saldo.
Sekarang setelah kelas BankAccount
memiliki field yang bersifat baca-saja untuk saldo minimum, perubahan terakhir adalah mengubah kode 0
yang dibuat secara hardcode menjadi minimumBalance
dalam metode MakeWithdrawal
.
if (Balance - amount < _minimumBalance)
Setelah memperluas BankAccount
kelas, Anda dapat memodifikasi LineOfCreditAccount
konstruktor untuk memanggil konstruktor dasar baru, seperti yang ditunjukkan dalam kode berikut:
public LineOfCreditAccount(string name, decimal initialBalance, decimal creditLimit) : base(name, initialBalance, -creditLimit)
{
}
Perhatikan bahwa LineOfCreditAccount
konstruktor mengubah tanda creditLimit
parameter sehingga cocok dengan arti minimumBalance
parameter.
Aturan overdraft yang berbeda
Fitur terakhir yang ditambahkan memungkinkan untuk membebankan LineOfCreditAccount
biaya untuk melewati batas kredit alih-alih menolak transaksi.
Salah satu tekniknya adalah menentukan fungsi virtual tempat Anda menerapkan perilaku yang diperlukan. Kelas BankAccount
merefaktorkan metode MakeWithdrawal
menjadi dua metode. Metode baru melakukan tindakan yang ditentukan ketika penarikan terjadi dan membawa saldo kurang dari minimum. Metode MakeWithdrawal
yang sudah ada memiliki kode berikut:
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);
}
Ganti dengan kode berikut:
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;
}
}
Metode yang ditambahkan adalah protected
, yang berarti bahwa metode tersebut hanya dapat dipanggil dari kelas turunan. Deklarasi itu mencegah klien lain memanggil metode . Ini juga virtual
agar kelas turunan dapat mengubah perilaku. Jenis pengembalian adalah Transaction?
. Anotasi ?
menunjukkan bahwa metode dapat mengembalikan null
. Tambahkan implementasi berikut dalam LineOfCreditAccount
untuk membebankan biaya ketika batas penarikan terlampaui:
protected override Transaction? CheckWithdrawalLimit(bool isOverdrawn) =>
isOverdrawn
? new Transaction(-20, DateTime.Now, "Apply overdraft fee")
: default;
Penggantian mengembalikan transaksi biaya ketika saldo akun terlampaui. Jika penarikan tidak melebihi batas, metode tersebut akan mengembalikan transaksi null
. Itu menunjukkan tidak ada biaya. Uji perubahan ini dengan menambahkan kode berikut ke metode Anda Main
di Program
kelas :
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());
Jalankan program, dan periksa hasilnya.
Ringkasan
Jika Anda kebingungan, Anda dapat melihat sumber untuk tutorial ini di repositori GitHub kami.
Tutorial ini menunjukkan banyak teknik yang digunakan dalam pemrograman Object-Oriented:
- Anda menggunakan Abstraksi saat menentukan kelas untuk setiap jenis akun yang berbeda. Kelas-kelas tersebut menjelaskan perilaku untuk jenis akun tersebut.
- Anda menggunakan Enkapsulasi saat menyimpan banyak detail
private
di setiap kelas. - Anda menggunakan Warisan saat memanfaatkan implementasi yang sudah dibuat di
BankAccount
kelas untuk menyimpan kode. - Anda menggunakan Polymorphism ketika Anda membuat metode yang kelas turunan dapat mengganti untuk menciptakan perilaku khusus bagi jenis akun tersebut.