抽象类以及它的使用

Introduction

There is a lot of confusion among beginners about the abstract class. Knowing its syntax is easy, but when and why should we use an abstract class is something which has troubled most start up developers. I will try to explain the concept with an easy to understand example. Hope this proves to be useful!

Background

There are a lot of definitions for an abstract class, more often I find that instead of explaining the concept, articles deal with the actual syntax of the abstract class. For example, an abstract class can not have a private virtual member, blah blah.

This article will try to explain the What and the When.

Understanding the Abstract Class

An abstract class is a class which cannot be instantiated, which means you cannot create an object of an abstract class, it can only act as a base class and other classes can derive from it. So what is the rationale behind the existence of a class if we cannot create an object of it?

Let’s take an example of a bank to explain this. Later we will go through the code to implement it.

Suppose you go to a bank for opening an account. Let’s see below what transpires between an applicant and a bank staff.

Bank Staff: Welcome sir.

Applicant: I want to open an account.

Bank Staff: Sure sir, what kind of account do you want to open?

Applicant: I just want to open an account.

Bank Staff: Do you want to open a Savings Account or a Current Account?

Applicant: I do not want to open any specific account, just open an account.

Bank Staff: Sir, this is not possible, we need to create a specific type of account. Unless you tell me the type of the account, I cannot help you.

In the light of the above discussion we come up with the below inference for our banking application software.

  • Banks have a facility to open an account
  • Bank account should be of a specific type (Saving/Current)
  • Bank cannot open a generic account

To explain this concept, let's create a banking application. This application is just for demonstration purposes and should not be used as a design guide.

Using the Code

We have a BankAccount class which is an abstract class for the reason explained above.

Irrespective of the type of account there  are certain members/behaviors which are common to all types of accounts and thus should be part of the base class, in our case the BankAccount class.

Bank Account Common Members
  • Account Owner property will have the name of the account holder
  • Account Number uniquely identifies a bank account
  • Minimum Balance will have the minimum threshold for the account
  • Maximum Deposit Amount will have the maximum amount which can be deposited in one go
  • Interest Rate is required for all bank accounts
  • Transaction Summary records all the transactions taking place in an account
Bank Account Common Behavior
  • You can deposit money in it.
  • You can withdraw money from it.
  • There should be facility to calculate interest.
  • User can generate Report/ Summary to see the transactions.

Deposit and Withdraw: These are two abstract methods. It looks like Deposit/Withdraw functionality should be the same for a SavingAccount and CurrentAccount but might  not always be as in our case. These methods are abstract because we want that the child class should give its own implementation. (These two methods however can also be created as virtual methods but for the sake of our demonstration, let them be abstract methods.)

The CalculateInterest() method is implemented in the abstract class and the child class will reuse this functionality,  clearly an advantage of an abstract class over an interface regarding code reuse:

 1 public abstract class BankAccount : IAccountReportGenerator

 2 {

 3     // Name of the Account Owner, Its common for all derived classes

 4     public string AccountOwnerName { get; set; }

 5 

 6     // Account Number field is a common field for all the account types

 7     public string AccountNumber { get; set; }

 8 

 9     // A field to hold the Account Balance

10     public decimal AccountBalance { get; protected set; }

11 

12     // A field to hold the MinimumAccount Balance

13     protected decimal MinAccountBalance { get; set; }

14 

15     // A field to hold the Max Deposit Amount Balance

16     protected decimal MaxDepositAmount { get; set; }

17 

18     protected decimal InteresetRate { get; set; } 

19      

20     // this variable will hold the summary of all the transaction that took place

21     protected string TransactionSummary { get; set; }

22 

23     protected BankAccount(string accountOwnerName, string accountNumber)

24     {

25         AccountOwnerName = accountOwnerName;

26         AccountNumber = accountNumber;

27         TransactionSummary = string.Empty;

28     }

29 

30     // Deposit is an abstract method so that Saving/Current Account must override

31     // it to give their specific implementaion.

32     public abstract void Deposit(decimal amount);

33 

34     // Withdraw is an abstract method so that Saving/Current Account must override 

35     // it to give their specific implementaion.

36     public abstract void Withdraw(decimal amount);

37     

38     public decimal CalculateInterest()

39     {

40         return (this.AccountBalance * this.InteresetRate) / 100;

41     } 

42    

43     // This method comes from interface and adds a Reporting functionality 

44     public virtual void GenerateAccountReport()

45     {

46         Console.WriteLine("Account Owner:{0}, Account Number:{1}, AccountBalance:{2}",

47             this.AccountOwnerName, this.AccountNumber, this.AccountBalance);

48    

49         Console.WriteLine("Interest Amount:{0}", CalculateInterest());

50         Console.WriteLine("{0}", this.TransactionSummary);

51     }

52 } 

Constructor: Though the BankAccount class is an abstract class  it still it has a constructor. What is the use of a constructor if an instance of  the abstract class  can not be created? The constructor is used when we create an instance of SavingAccount or CurrentAccount, so variables defined in the abstract class could be initialized using  the abstract class constructor. Remember that whenever a child class is instantiated,  the constructor of its base class is called first and then the derived class constructor is called.

Some of the fields are protected and some are public, I have kept it like  that for no serious reason. TransactionSummaryis kept protected so that only a child class should be able to see and change it.

There is a separate method GenerateAccountReportwhich will show the details of  the account including the transaction summary.

IAccountReportGenerator is an interface implemented by BankAccount, this adds  reporting functionality to it.

1 public interface IAccountReportGenerator

2 {

3    void GenerateAccountReport();

4 } 

The Interface just defines what to be done, it's a contract, it does not explain how it should be done. It does not provide any implementation details  and that exercise is left to the implementing class. In our case, BankAccount will have to provide the implementation.

What we have done here is that we have implemented the interface method but have set it as virtual.

The GenerateAccountReport()method will show the details of  the account including the transaction summary. It is a virtual method. By setting it  as virtual we are declaring that any subclass can override it to give its own implementation; however,  the default implementation is provided by the base class.

Let's now move to our child classes, i.e.,SavingAccount and CurrentAccount:

 1 public class SavingBankAccount : BankAccount

 2 {

 3     protected int withdrawCount = 0;

 4 

 5     public SavingBankAccount(string accountOwnerName, string accountNumber)

 6         :base(accountOwnerName,accountNumber)

 7     {

 8         this.MinAccountBalance = 20000m;

 9         this.MaxDepositAmount = 50000m;

10         InteresetRate = 3.5m;

11     }

12 

13     public override void Deposit(decimal amount)

14     {

15         if (amount >= MaxDepositAmount)

16         {

17             throw new Exception(string.Format("You can not deposit amount

18                                  greater than {0}", MaxDepositAmount.ToString()));

19         }

20 

21         AccountBalance = AccountBalance + amount;

22 

23         TransactionSummary = string.Format("{0}\n Deposit:{1}",

24                                             TransactionSummary, amount);

25     }

26 

27     public override void Withdraw(decimal amount)

28     {

29         // some hard coded logic that withdraw count should not be greater than 3

30         if (withdrawCount > 3)

31         {

32             throw new Exception("You can not withdraw amount more than thrice");

33         }

34 

35         if (AccountBalance - amount <= MinAccountBalance)

36         {

37             throw new Exception("You can not withdraw amount from your 

38                                 Savings Account as Minimum Balance limit is reached");

39         }

40 

41         AccountBalance = AccountBalance - amount;

42         withdrawCount++;

43 

44         TransactionSummary = string.Format("{0}\n Withdraw:{1}", 

45                                             TransactionSummary, amount);

46     }

47     // This method comes from interface and adds a Reporting functionality 

48     public override void GenerateAccountReport()

49     {

50         Console.WriteLine("Saving Account Report");

51         base.GenerateAccountReport();

52         

53         // Send an email to user if Savings account balance is less 

54         // than user specified balance this is different than MinAccountBalance

55         if(AccountBalance > 15000) 

56         { 

57            Console.WriteLine("Sending Email for Account {0}", AccountNumber);

58         }

59     }

60 } 

Let's see our CurrentAccount class which also derives from the abstract base class.

 1 public class CurrentBankAccount : BankAccount

 2 {

 3     public CurrentBankAccount(string accountOwnerName, string accountNumber)

 4         :base(accountOwnerName,accountNumber)

 5     {

 6         this.MinAccountBalance = 0m;

 7         this.MaxDepositAmount = 100000000m;

 8         InteresetRate = .25m;

 9     }

10 

11     public override void Deposit(decimal amount)

12     {

13         AccountBalance = AccountBalance + amount;

14         TransactionSummary = string.Format("{0}\n Deposit:{1}",

15                                             TransactionSummary, amount);

16     }

17 

18     public override void Withdraw(decimal amount)

19     {

20         if (AccountBalance - amount <= MinAccountBalance)

21         {

22             throw new Exception("You can not withdraw amount from 

23                            your Current Account as Minimum Balance limit is reached");

24         }

25 

26         AccountBalance = AccountBalance - amount;

27         TransactionSummary = string.Format("{0}\n Withdraw:{1}",

28                                              TransactionSummary, amount);

29     }

30     // This method comes from interface and adds a Reporting functionality 

31     public override void GenerateAccountReport()

32     {

33         Console.WriteLine("Current Account Report");

34         base.GenerateAccountReport();

35     }

36 } 

Let's dig inside our child classes. The constructors of the SavingAccount as well as CurrentAccount are initializing some variable as per their requirements,  however certain common variables are set by the abstract class, which explains the rationale behind the need of a constructor in  the abstract class.

The Withdraw and Deposit methods are pretty simple and need no detailed explanation. Both classes have overridden them to provide their own implementation.

The Deposit method of SavingAccount throws an exception if  the amount deposited is greater than a specified limit.

The Withdraw method ofSavingAccount checks the number  of withdrawals before throwing an exception.

The GenerateAccountReport method of SavingAccount adds a report header, calls the base class method for the generic implementation,  and then sends the account report email.

To use the above code, here is a our Main method which creates an instance each of a Saving and Current  account. The variable taken to store  these instance is of type BankAccount, thus allowing us to have polymorphic behavior.

 1 public static void Main(string[] args)

 2 {

 3     BankAccount savingAccount = new SavingBankAccount("Sarvesh", "S12345");

 4     BankAccount currentAccount = new CurrentBankAccount("Mark", "C12345");

 5 

 6     savingAccount.Deposit(40000);

 7     savingAccount.Withdraw(1000);

 8     savingAccount.Withdraw(1000);

 9     savingAccount.Withdraw(1000);

10             

11     // Generate Report

12     savingAccount.GenerateAccountReport();

13 

14     Console.WriteLine();

15     currentAccount.Deposit(190000);

16     currentAccount.Withdraw(1000);

17     currentAccount.GenerateAccountReport();

18 

19     Console.ReadLine();

20 } 

Here is the output

Saving Account Report

Account Owner:Sarvesh, Account Number:S12345, AccountBalance:37000

Interest Amount:1295.0



 Deposit:40000

 Withdraw:1000

 Withdraw:1000

 Withdraw:1000

Sending Email for Account S12345



Current Account Report

Account Owner:Mark, Account Number:C12345, AccountBalance:189000

Interest Amount:472.50



 Deposit:190000

 Withdraw:1000 

When to Use Abstract Class

Let's go back to the example of our banking application.

Though we cannot open a generic account all accounts will have  a certain member and behavior as discussed above.  Also, we want that all types of accounts should conform to these attributes and behaviors.

To summarize,  create an abstract class if:

  1. Class expresses an idea which is too generic and whose independent (alone) existence is not required in your application, e.g., BankAccount.
  2. There is a family of types forming a hierarchy. An “IS-A” relation exists between a base class and other derived classes. E.g.:
    • Saving Account IS-A Bank Account
    • Current Account IS-A Bank Account
  3. If there are certain members/ behaviors which are common to all classes, these should be placed inside the abstract class, e.g., AccountNumber, Deposit(), etc.
  4. If there are behaviors/attributes which should be implemented or must be present in all classes, declare them as abstract methods in the abstract class, e.g., CalculateInterest().

Why not  an Interface in place of the BankAccount class?

In our example, you can argue that we can use  a Interface instead of the BankAccount abstract class, something like as shown below:

1 public class SavingAccount : IBankAccount

2 {

3     void Deposit(decimal amount);    

4     void Withdras(decimal amount);

5     decimal CalculateInterest();

6 } 

First of all, there is a hierarchy between BankAccount and SavingsAccount and a close relation exists. Also, we are able to figure  out certain common features present in all the child classes,  thus an abstract class will help in code reuse. An interface is more of a contract between classes. There are  a lot of syntactical differences between an abstract class and an interface,  a little Googling may be of great help so I haven't touched it in this article.

 

转载至:http://www.codeproject.com/Articles/698056/Abstract-class-and-its-usage

你可能感兴趣的:(抽象类)