Bagikan melalui


Pemrograman Berorientasi Objek (C#)

C# adalah bahasa pemrogram 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 sekumpulan 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 memperkenalkan Anda pada abstraksi dan enkapulasi. Kelas BankAccount memberikan abstraksi untuk konsep rekening bank. Anda dapat memodifikasi implementasinya tanpa memengaruhi kode apa pun yang menggunakan kelas BankAccount. Kelas BankAccount dan Transaction menyediakan enkapulasi komponen yang diperlukan untuk menggambarkan konsep-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, perlu perubahan, dan jenis rekening terkait diminta:

  • Rekening 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.
  • Rekening kartu hadiah prabayar yang dimulai dengan satu deposit, dan hanya dapat dilunasi. Ini dapat diisi ulang sekali pada awal setiap bulan.

Semua akun yang berbeda ini mirip dengan kelas BankAccount yang ditentukan dalam tutorial sebelumnya. Anda dapat menyalin kode tersebut, mengganti nama kelas, dan melakukan modifikasi. Teknik itu akan bekerja dalam jangka pendek, tetapi akan lebih berfungsi 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 BankAccount yang dibuat dalam tutorial sebelumnya. Kelas baru ini dapat memperluas kelas BankAccount 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 bersama mereka, kelas BankAccount. Tulis implementasi untuk fungsionalitas baru dan berbeda di setiap kelas turunan. Kelas turunan ini sudah memiliki semua perilaku yang ditentukan di kelas BankAccount.

Ini adalah praktik yang baik untuk membuat setiap kelas baru dalam file sumber yang berbeda. Di Visual Studio, Anda dapat mengeklik 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)

Pengompilasi tidak menghasilkan konstruktor default saat Anda menentukan konstruktor sendiri. 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 panggilan 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 mendapatkan kredit sebesar 2% dari saldo akhir bulan.
  • Garis 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 tertentu 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 di kelas BankAccount:

public virtual void PerformMonthEndTransactions() { }

Kode sebelumnya menunjukkan bagaimana Anda menggunakan kata kunci virtual untuk mendeklarasikan metode di kelas dasar bahwa kelas turunan dapat memberikan implementasi yang berbeda. Metode virtual adalah metode di mana setiap kelas turunan dapat memilih untuk melengkapi kembali. Kelas turunan menggunakan kata kunci override untuk menentukan implementasi baru. Biasanya Anda menyebutnya sebagai "mengambil alih implementasi kelas dasar". Kata kunci virtual menentukan bahwa kelas turunan dapat mengambil alih perilaku ini. Anda juga dapat mendeklarasikan metode abstract di mana kelas turunan harus mengambil alih perilaku. Kelas dasar tidak menyediakan implementasi untuk metode abstract. 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 Anda. Kode meniadakan saldo untuk menghitung biaya bunga positif yang ditarik dari rekening:

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 ini membutuhkan dua perubahan untuk mengimplementasikan fungsionalitas akhir bulannya. 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 memberikan nilai default untuk nilai monthlyDeposit sehingga pemanggil dapat menghilangkan 0 tanpa deposit bulanan. Selanjutnya, ambil alih metode PerformMonthEndTransactions 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 berlaku setoran bulanan yang ditetapkan di konstruktor. Tambahkan kode berikut ke metode Main 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 hasil. 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

Catatan

Output aktual mencakup jalur lengkap ke folder dengan proyek. Nama folder dihilangkan untuk keringkasan. 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 disertakan ke dalam kelas BankAccount adalah saldo tidak bisa negatif. Sebaliknya, setiap penarikan yang membanjiri rekening ditolak. Kedua asumsi tersebut perlu berubah. Lini akun kredit dimulai dari 0, dan umumnya akan memiliki saldo negatif. Selain itu, jika pelanggan meminjam terlalu banyak uang, dia akan dikenakan biaya. Transaksi diterima, biayanya lebih mahal. Aturan pertama dapat diimplementasikan dengan menambahkan argumen opsional ke konstruktor BankAccount yang menentukan saldo minimum. Default adalah 0. Aturan kedua memerlukan mekanisme yang memungkinkan kelas turunan untuk memodifikasi algoritma default. Dalam arti tertentu, kelas dasar "menanyakan" jenis turunan apa yang harus terjadi ketika ada overdraft. Perilaku defaultnya adalah menolak transaksi dengan menampilkan pengecualian.

Mari kita mulai dengan menambahkan konstruktor kedua yang menyertakan parameter minimumBalance opsional. Konstruktor baru ini melakukan semua tindakan yang dilakukan oleh konstruktor yang ada. Selain itu, ini menetapkan properti saldo minimum. Anda dapat menyalin isi konstruktor yang ada, tetapi itu berarti dua lokasi untuk diubah di masa mendatang. Sebagai gantinya, Anda dapat menggunakan penautan konstruktor untuk memiliki satu konstruktor memanggil yang lain. 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, bidang minimumBalance ditandai sebagai readonly. Itu berarti nilai tidak dapat diubah setelah objek dibangun. Setelah BankAccount dibuat, minimumBalance tidak dapat berubah. Kedua, konstruktor yang mengambil dua parameter menggunakan : this(name, initialBalance, 0) { } sebagai implementasinya. Ekspresi : this() memanggil konstruktor lainnya, 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 membiarkan akun kredit terbuka dengan saldo 0.

Sekarang setelah kelas BankAccount memiliki bidang baca-saja untuk saldo minimum, perubahan akhir adalah mengubah kode keras 0 menjadi minimumBalance dalam metode MakeWithdrawal:

if (Balance - amount < _minimumBalance)

Setelah memperluas kelas BankAccount, Anda dapat memodifikasi konstruktor LineOfCreditAccount 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 konstruktor LineOfCreditAccount mengubah tanda parameter creditLimit sehingga cocok dengan arti parameter minimumBalance.

Aturan overdraft yang berbeda

Fitur terakhir yang ditambahkan memungkinkan LineOfCreditAccount untuk membebankan biaya karena melebihi batas kredit alih-alih menolak transaksi.

Salah satu tekniknya adalah menentukan fungsi virtual tempat Anda menerapkan perilaku yang diperlukan. Kelas BankAccount merefaktor metode MakeWithdrawal menjadi dua metode. Metode baru melakukan tindakan yang ditentukan ketika penarikan mengambil saldo di bawah minimum. Metode MakeWithdrawal yang 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 yang ditampilkan adalah Transaction?. Anotasi ? menunjukkan bahwa metode dapat menampilkan 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;

Penimpaan menampilkan transaksi biaya ketika akun overdrawn. Jika penarikan tidak melebihi batas, metode menampilkan transaksi null. Itu menunjukkan bahwa tidak ada biaya. Uji perubahan ini dengan menambahkan kode berikut ke metode Anda Main di kelas Program:

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 eksperimen 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 Berorientasi Objek:

  • 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 Pewarisan saat memanfaatkan implementasi yang sudah dibuat di kelas BankAccount untuk menyimpan kode.
  • Anda menggunakan Polimorfisme saat membuat metode virtual yang membuat kelas turunan dapat mengambil alih untuk membuat perilaku tertentu untuk jenis akun tersebut.