您的位置:首页 > 其它

抽象类及其用法

2013-12-22 09:30 429 查看

抽象类及其用法

By SarveshShukla,
20 Dec 2013

Translated by
litdwg

Download source - 20 KB

简介

初学者对于抽象类会很迷惑。虽然抽象类的语法很简单,但何时、如何使用抽象类困扰着很多开发人员。本文尝试通过一个容易理解的简单实例来解释抽象类。希望有所帮助。

背景

对于抽象类已有很多定义。很多时候我发现相关的文章总是在解释抽象类的语法,而不是解释抽象类的概念。

本文将解释何时、如何使用抽象类。.

理解抽象类

抽象类是不能被实例化的类,也就是说不能创建一个抽象类的对象,只能作为基类被其他类继承。为什么需要不能创建对象的类呢?

我们以银行为例解释为什么。然后,我们实现相应的代码。

比如你去银行开一个账户。让我们模拟一下用户和银行工作人员之间的交流过程。

银行工作人员: 欢迎光临.

客户: 我想开个账户.

银行工作人员: 好的先生,您要开什么类型的账户?

客户: 我只是想开个账户.

银行工作人员: 你想开储蓄账户还是信用账户?

客户:我不想开什么特殊的账户,我只是想开个账户而已.

银行工作人员: 先生,这不可能的,我们需要创建某种具体类型的账户。除非你告诉我要开什么类型的账户我才能帮你。

通过上面的对话,我们可获悉银行程序的信息:

具有开通账户的功能
必须指明开通账户的类型(储蓄/信用)
不能开通用的账户

我们来创建一个银行程序,此程序只是作为演示不应当做设计的指南。

使用代码

创建
BankAccount
抽象类。无论何种账户,肯定有对所有类型账户都适用的数据信息和操作,这些应放在基类里,本例中
BankAccount
类。

银行账户通用的数据信息

开户人姓名

唯一的账号
最少余额
一次最大取款额
存款利率
操作记录

银行账户通用的操作

存钱
取钱
计算利息
查看操作记录.

存取款
: 两个抽象方法。储蓄账户和信用账户的存取款操作通常是一样的,但也并不一定总是如此。定义为抽象方法,这样子类可以拥有此方法自己的实现。(这两个方法也可以定义为virtual方法,但这里为了演示定义为抽象方法)

CalculateInterest()
方法实现计算利息,在抽象类中定义,子类可直接调用。显然这是抽象类比接口在代码重用方面有利的一点:


public abstract class BankAccount
{
// Name of the Account Owner, Its common for all derived classes
public string AccountOwnerName { get; set; }

// Account Number field is a common field for all the account types
public string AccountNumber { get; set; }

// A field to hold the Account Balance
public decimal AccountBalance { get; protected set; }

// A field to hold the MinimumAccount Balance
protected decimal MinAccountBalance { get; set; }

// A field to hold the Max Deposit Amount Balance
protected decimal MaxDepositAmount { get; set; }

protected decimal InteresetRate { get; set; }

// this variable will hold the summary of all the transaction that took place
protected string TransactionSummary { get; set; }

protected BankAccount(string accountOwnerName, string accountNumber)
{
AccountOwnerName = accountOwnerName;
AccountNumber = accountNumber;
TransactionSummary = string.Empty;
}

// Deposit is an abstract method so that Saving/Current Account must override
// it to give their specific implementation.
public abstract void Deposit(decimal amount);

// Withdraw is an abstract method so that Saving/Current Account must override
// it to give their specific implementation.
public abstract void Withdraw(decimal amount);

public decimal CalculateInterest()
{
return (this.AccountBalance * this.InteresetRate) / 100;
}

// This method adds a Reporting functionality
public virtual void GenerateAccountReport()
{
Console.WriteLine("Account Owner:{0}, Account Number:{1}, AccountBalance:{2}",
this.AccountOwnerName, this.AccountNumber, this.AccountBalance);

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

构造函数: 尽管
BankAccount
类是抽象类, 仍拥有构造函数. 抽象类不能实例化还要构造函数何用? 当创建
SavingAccount
或者CurrentAccount实例对象时会用到
, 所以在抽象类中定义的变量可通过抽象类的构造函数进行初始化。请注意:无论何时实例化一个子类,首先调用的是基类的构造函数,然后是子类的。

有些字段是
protected
有些是
public
.
TransactionSummary

protected
则子类可操作此字段

GenerateAccountReport()
方法显示账户详情和操作记录. 这是一个virtual方法. 设为 virtual后子类可重载此方法,但该方法的默认实现是由基类提供的。
我们再来看看子类:
SavingAccount
和CurrentAccount
:


public class SavingBankAccount : BankAccount
{
protected int withdrawCount = 0;

public SavingBankAccount(string accountOwnerName, string accountNumber)
:base(accountOwnerName,accountNumber)
{
this.MinAccountBalance = 20000m;
this.MaxDepositAmount = 50000m;
InteresetRate = 3.5m;
}

public override void Deposit(decimal amount)
{
if (amount >= MaxDepositAmount)
{
throw new Exception(string.Format("You can not deposit amount
greater than {0}", MaxDepositAmount.ToString()));
}

AccountBalance = AccountBalance + amount;

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

public override void Withdraw(decimal amount)
{
// some hard coded logic that withdraw count should not be greater than 3
if (withdrawCount > 3)
{
throw new Exception("You can not withdraw amount more than thrice");
}

if (AccountBalance - amount <= MinAccountBalance)
{
throw new Exception("You can not withdraw amount from your
Savings Account as Minimum Balance limit is reached");
}

AccountBalance = AccountBalance - amount;
withdrawCount++;

TransactionSummary = string.Format("{0}\n Withdraw:{1}",
TransactionSummary, amount);
}
// This method adds details to the base class Reporting functionality
public override void GenerateAccountReport()
{
Console.WriteLine("Saving Account Report");
base.GenerateAccountReport();

// Send an email to user if Savings account balance is less
// than user specified balance this is different than MinAccountBalance
if(AccountBalance > 15000)
{
Console.WriteLine("Sending Email for Account {0}", AccountNumber);
}
}
}


public class CurrentBankAccount : BankAccount
{
public CurrentBankAccount(string accountOwnerName, string accountNumber)
:base(accountOwnerName,accountNumber)
{
this.MinAccountBalance = 0m;
this.MaxDepositAmount = 100000000m;
InteresetRate = .25m;
}

public override void Deposit(decimal amount)
{
AccountBalance = AccountBalance + amount;
TransactionSummary = string.Format("{0}\n Deposit:{1}",
TransactionSummary, amount);
}

public override void Withdraw(decimal amount)
{
if (AccountBalance - amount <= MinAccountBalance)
{
throw new Exception("You can not withdraw amount from
your Current Account as Minimum Balance limit is reached");
}

AccountBalance = AccountBalance - amount;
TransactionSummary = string.Format("{0}\n Withdraw:{1}",
TransactionSummary, amount);
}
// This method adds details to the base class Reporting functionality
public override void GenerateAccountReport()
{
Console.WriteLine("Current Account Report");
base.GenerateAccountReport();
}
}

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.

我们来深入看看子类。子类的构造函数中初始化了一些变量,而有一些变量是在基类的构造函数中初始化的,这解释了为什么抽象类需要构造函数。

Withdraw
和Deposit
都很简单无需过多解释,二者都实现了自己的操作。

SavingAccount的
Deposit
当一次存款过多时抛出异常。


SavingAccount
Withdraw
在抛出异常之前查看取款次数.

SavingAccount
GenerateAccountReport
添加了报表的报头,调用基类方法,然后寄到账户邮箱。.

Main
方法演示了相关操作:

public static void Main(string[] args)
{
BankAccount savingAccount = new SavingBankAccount("Sarvesh", "S12345");
BankAccount currentAccount = new CurrentBankAccount("Mark", "C12345");

savingAccount.Deposit(40000);
savingAccount.Withdraw(1000);
savingAccount.Withdraw(1000);
savingAccount.Withdraw(1000);

// Generate Report
savingAccount.GenerateAccountReport();

Console.WriteLine();
currentAccount.Deposit(190000);
currentAccount.Withdraw(1000);
currentAccount.GenerateAccountReport();

Console.ReadLine();
}

输出结果

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

何时使用抽象类

我们再回头看一下这个银行程序.

虽然我们不能开一个基本账户,但所有类型的账户都具有一些共有的数据和操作。

总结一下,当有如下需求时可使用抽象类:

类表达的内容 不具体且不需要独立存在。如.,
BankAccount
.
一系列类型 形成了 继承关系. 基类和子类存在“IS-A” 关系.如:

Saving Account IS-A Bank Account
Current Account IS-A Bank Account

有一些数据和方法对所有类是共有的, 应放在抽象类中,如.,
AccountNumber
,
Deposit()
,.
如果有behaviors/attributes在所有类中需要实现或必须实现, 应在抽象类中声明为抽象方法, 如.,
CalculateInterest()
.

为什么不用接口代替 BankAccount Class?

也许你会说接口也可以满足上面的需求:


public class SavingBankAccount : IBankAccount
{
void Deposit(decimal amount);
void Withdraw(decimal amount);
decimal CalculateInterest();
}

首先
,BankAccount
和SavingAccount
有继承关系联系紧密.其次,我们在抽象类中可完成共有的数据和操作,这有利于代码重用。接口更像是类之间的契约。.

总结

通过示例解释了什么是抽象类,如何使用,何时使用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: