抽象类及其用法

15 篇文章 0 订阅

抽象类及其用法

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当一次存款过多时抛出异常。

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

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

 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

何时使用抽象类

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

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

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

  1. 类表达的内容 不具体且不需要独立存在。如., BankAccount.
  2. 一系列类型 形成了 继承关系. 基类和子类存在“IS-A” 关系.如:
    • Saving Account IS-A Bank Account
    • Current Account IS-A Bank Account
  3. 有一些数据和方法对所有类是共有的, 应放在抽象类中,如.,AccountNumber, Deposit(),.
  4. 如果有behaviors/attributes在所有类中需要实现或必须实现, 应在抽象类中声明为抽象方法, 如., CalculateInterest().

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

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

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

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

总结

通过示例解释了什么是抽象类,如何使用,何时使用。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值