16-应用ABC概念
13.6.1 应用ABC概念
您可能希望看到一个完整的ABC示例,因此这里将这一概念用于Brass和BrassPlus账户,首先定义一个名为AcctABC的ABC。这个类包含Brass和BrassPlus类共有的所有方法和数据成员,而那些在BrassPlus类和Brass类中的行为不同的方法应被声明为虚函数。至少应有一个虚函数是纯虚函数,这样才能使AcctABC成为抽象类。
程序清单13.11的头文件声明了AcctABC类(ABC)、Brass类和BrassPlus类(两者都是具体类)。为帮助派生类访问基类数据,AcctABC提供了一些保护方法;派生类方法可以调用这些方法,但它们并不是派生类对象的公有接口的组成部分。AcctABC还提供一个保护成员函数,用于处理格式化(以前是使用非成员函数处理的)。另外,AcctABC类还有两个纯虚函数,所以它确实是抽象类。
程序清单13.11 acctabc.h
// acctabc.h -- bank account classes
#ifndef ACCTABC_H_
#define ACCTABC_H_
#include <iostream>
#include <string>
// Abstract Base Class
class AcctABC
{
private:
std::string fullName;
long acctNum;
double balance;
protected:
struct Formatting
{
std::ios_base::fmtflags flag;
std::streamsize pr;
};
const std::string & FullName() const {return fullName;}
long AcctNum() const {return acctNum;}
Formatting SetFormat() const;
void Restore(Formatting & f) const;
public:
AcctABC(const std::string & s = "Nullbody", long an = -1,
double bal = 0.0);
void Deposit(double amt) ;
virtual void Withdraw(double amt) = 0; // pure virtual function
double Balance() const {return balance;};
virtual void ViewAcct() const = 0; // pure virtual function
virtual ~AcctABC() {}
};
// Brass Account Class
class Brass :public AcctABC
{
public:
Brass(const std::string & s = "Nullbody", long an = -1,
double bal = 0.0) : AcctABC(s, an, bal) { }
virtual void Withdraw(double amt);
virtual void ViewAcct() const;
virtual ~Brass() {}
};
//Brass Plus Account Class
class BrassPlus : public AcctABC
{
private:
double maxLoan;
double rate;
double owesBank;
public:
BrassPlus(const std::string & s = "Nullbody", long an = -1,
double bal = 0.0, double ml = 500,
double r = 0.10);
BrassPlus(const Brass & ba, double ml = 500, double r = 0.1);
virtual void ViewAcct()const;
virtual void Withdraw(double amt);
void ResetMax(double m) { maxLoan = m; }
void ResetRate(double r) { rate = r; };
void ResetOwes() { owesBank = 0; }
};
#endif
接下来需要实现那些不是内联函数的方法,如程序清单13.12所示。
程序清单13.12 acctABC.cpp
// acctabc.cpp -- bank account class methods
#include <iostream>
#include "acctabc.h"
using std::cout;
using std::ios_base;
using std::endl;
using std::string;
// Abstract Base Class
AcctABC::AcctABC(const string & s, long an, double bal)
{
fullName = s;
acctNum = an;
balance = bal;
}
void AcctABC::Deposit(double amt)
{
if (amt < 0)
cout << "Negative deposit not allowed; "
<< "deposit is cancelled.\n";
else
balance += amt;
}
void AcctABC::Withdraw(double amt)
{
balance -= amt;
}
// protected methods for formatting
AcctABC::Formatting AcctABC::SetFormat() const
{
// set up ###.## format
Formatting f;
f.flag =
cout.setf(ios_base::fixed, ios_base::floatfield);
f.pr = cout.precision(2);
return f;
}
void AcctABC::Restore(Formatting & f) const
{
cout.setf(f.flag, ios_base::floatfield);
cout.precision(f.pr);
}
// Brass methods
void Brass::Withdraw(double amt)
{
if (amt < 0)
cout << "Withdrawal amount must be positive; "
<< "withdrawal canceled.\n";
else if (amt <= Balance())
AcctABC::Withdraw(amt);
else
cout << "Withdrawal amount of $" << amt
<< " exceeds your balance.\n"
<< "Withdrawal canceled.\n";
}
void Brass::ViewAcct() const
{
Formatting f = SetFormat();
cout << "Brass Client: " << FullName() << endl;
cout << "Account Number: " << AcctNum() << endl;
cout << "Balance: $" << Balance() << endl;
Restore(f);
}
// BrassPlus Methods
BrassPlus::BrassPlus(const string & s, long an, double bal,
double ml, double r) : AcctABC(s, an, bal)
{
maxLoan = ml;
owesBank = 0.0;
rate = r;
}
BrassPlus::BrassPlus(const Brass & ba, double ml, double r)
: AcctABC(ba) // uses implicit copy constructor
{
maxLoan = ml;
owesBank = 0.0;
rate = r;
}
void BrassPlus::ViewAcct() const
{
Formatting f = SetFormat();
cout << "BrassPlus Client: " << FullName() << endl;
cout << "Account Number: " << AcctNum() << endl;
cout << "Balance: $" << Balance() << endl;
cout << "Maximum loan: $" << maxLoan << endl;
cout << "Owed to bank: $" << owesBank << endl;
cout.precision(3);
cout << "Loan Rate: " << 100 * rate << "%\n";
Restore(f);
}
void BrassPlus::Withdraw(double amt)
{
Formatting f = SetFormat();
double bal = Balance();
if (amt <= bal)
AcctABC::Withdraw(amt);
else if ( amt <= bal + maxLoan - owesBank)
{
double advance = amt - bal;
owesBank += advance * (1.0 + rate);
cout << "Bank advance: $" << advance << endl;
cout << "Finance charge: $" << advance * rate << endl;
Deposit(advance);
AcctABC::Withdraw(amt);
}
else
cout << "Credit limit exceeded. Transaction cancelled.\n";
Restore(f);
}
保护方法FullName()和AcctNum()提供了对数据成员fullName和acctNum的只读访问,使得可以进一步定制每个派生类的ViewAcct()。
这个版本在设置输出格式方面做了两项改进。前一个版本使用两个函数调用来设置输出格式,并使用一个函数调用来恢复格式:
format initialState = setFormat();
precis prec = cout.precision(2);
...
restore(initialState, prec); // restore original format
这个版本定义了一个结构,用于存储两项格式设置;并使用该结构来设置和恢复格式,因此只需两个函数调用:
struct Formatting
{
std::ios_base::fmtfglas flag;
std::streamsize pr;
};
...
Formatting f = SetFormat();
...
Restore(f);
因此代码更整洁。
旧版本存在的问题是,setFormat()和restore()都是独立的函数,这些函数与客户定义的同名函数发生冲突。解决这种问题的方式有多种,一种方式是将这些函数声明为静态的,这样它们将归文件brass.cpp及其继任acctabc.cpp私有。另一种方式是将这些函数以及结构Formatting放在一个独立的名称空间中。但这个示例探讨的主题之一是保护访问权限,因此将这些结构和函数放在了类定义的保护部分。这使得它们对基类和派生类可用,同时向外隐藏了它们。
对于Brass和BrassPlus账户的这种新实现,使用方式与旧实现相同,因为类方法的名称和接口都与以前一样。例如,为使程序清单13.10能够使用新的实现,需要采取下面的步骤将usebrass2.cpp转换为usebrass3.cpp:
- 使用acctabc.cpp而不是brass.cpp来链接usebrass2.cpp。
- 包含文件acctabc.h,而不是brass.h。
- 将下面的代码:
Brass * p_clients[CLIENTS];
替换为:
AcctABC * p_clients[CLIENTS];
程序清单13.13是修改后的文件,并将其重命名为usebrass3.cpp。
程序清单13.13 usebrass3.cpp
// usebrass3.cpp -- polymorphic example using an abstract base class
// compile with acctacb.cpp
#include <iostream>
#include <string>
#include "acctabc.h"
const int CLIENTS = 4;
int main()
{
using std::cin;
using std::cout;
using std::endl;
AcctABC * p_clients[CLIENTS];
std::string temp;
long tempnum;
double tempbal;
char kind;
for (int i = 0; i < CLIENTS; i++)
{
cout << "Enter client's name: ";
getline(cin,temp);
cout << "Enter client's account number: ";
cin >> tempnum;
cout << "Enter opening balance: $";
cin >> tempbal;
cout << "Enter 1 for Brass Account or "
<< "2 for BrassPlus Account: ";
while (cin >> kind && (kind != '1' && kind != '2'))
cout <<"Enter either 1 or 2: ";
if (kind == '1')
p_clients[i] = new Brass(temp, tempnum, tempbal);
else
{
double tmax, trate;
cout << "Enter the overdraft limit: $";
cin >> tmax;
cout << "Enter the interest rate "
<< "as a decimal fraction: ";
cin >> trate;
p_clients[i] = new BrassPlus(temp, tempnum, tempbal,
tmax, trate);
}
while (cin.get() != '\n')
continue;
}
cout << endl;
for (int i = 0; i < CLIENTS; i++)
{
p_clients[i]->ViewAcct();
cout << endl;
}
for (int i = 0; i < CLIENTS; i++)
{
delete p_clients[i]; // free memory
}
cout << "Done.\n";
return 0;
}
该程序本身的行为与非抽象基类版本相同,因此如果输入与给程序清单13.10提供的输入相同,输出也将相同。