定义:(Facade Pattern)
(也称为外观模式)要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。
类图:
启示:
周末出去吃饭,饭罢付账,打开钱包,就干瘪瘪的几张十元大钞。这可不行,得去取几张毛爷爷啊,下周可不能不吃不喝Debug不是?
找到附近的ATM自动取款机,打开钱包,我擦,我工资卡哪去了?翻了半天,就找到身份证和社保卡,唉,我这猿脑子,忘带银行卡了。
正准备转身离去下次再取,旁边一银行保安说:忘带银行卡啦?试试无卡取款,我银行最近推出了这项新功能,不用插银行卡也能取款,你试试看。
真是山穷水尽疑无路,牧童遥指杏花村。
回到ATM机前,果然屏幕上有个无卡取款功能键。选择无卡取款,然后让输入银行预留手机号,银行发送取款验证码到手机上,回填验证码到ATM,选择银行卡号,就可以取款了,果然方便。
取完款,回家路上,就在暗自思量,这ATM机一般就提供查询余额、取款、存款、转账四大功能。但其实银行的业务可不止这四个,仅仅是银行只针对ATM机开放了这四个功能罢了。
灵光一闪,这不就是门面模式嘛!
取款者可以通过ATM与银行系统进行交互,也可以通过手机银行客户端与银行进行交互,但交互的业务却大不相同,比如手机就不能直接取现金。那咱们就用门面模式来简单实现看看。
代码:
依据面向对象的思想,我们对ATM取款这个简单场景进行抽象。
首先对银行进行简单抽象:
主要定义了查询余额、取款、存款、转账、手机充值功能。
/// <summary>
/// 银行现金业务子系统
/// </summary>
public interface IBankSubsystem
{
/// <summary>
/// 查询余额
/// </summary>
/// <param name="account">银行账户</param>
/// <returns></returns>
int CheckBalance(BankAccount account);
/// <summary>
/// 取款
/// </summary>
/// <param name="account">银行账户</param>
/// <param name="money">取多少钱</param>
/// <returns></returns>
bool WithdrewMoney(BankAccount account, int money);
/// <summary>
/// 存款
/// </summary>
/// <param name="account">银行账户</param>
/// <param name="money">存多少钱</param>
/// <returns></returns>
bool DepositMoney(BankAccount account, int money);
/// <summary>
/// 转账
/// </summary>
/// <param name="account">转出账户</param>
/// <param name="targetNo">目标账户</param>
/// <param name="money">转多少钱</param>
/// <returns></returns>
bool TransferMoney(BankAccount account, string targetNo, int money);
/// <summary>
/// 充值话费
/// </summary>
/// <param name="phoneNumber">手机号</param>
/// <param name="account">银行账户</param>
/// <param name="money">充值多少</param>
/// <returns></returns>
bool RechargeMobilePhone(BankAccount account, string phoneNumber, int money);
}
其中依赖了BankAccount
,BankAccount
主要定义了银行账户。
public class BankAccount
{
public BankAccount(string bankNo, string password, string name, string phone, int totalMoney)
{
BankNo = bankNo;
Password = password;
Name = name;
Phone = phone;
TotalMoney = totalMoney;
}
/// <summary>
/// 银行卡号
/// </summary>
public string BankNo { get; set; }
/// <summary>
/// 取款密码
/// </summary>
public string Password { get; set; }
/// <summary>
/// 持卡人姓名
/// </summary>
public string Name { get; set; }
/// <summary>
/// 手机
/// </summary>
public string Phone { get; set; }
/// <summary>
/// 总金额
/// </summary>
public int TotalMoney { get; set; }
}
紧接着咱们来实现IBankSubsystem
:
public class BankSubsystem : IBankSubsystem
{
/// <summary>
/// 查询余额
/// </summary>
/// <param name="account">银行账户</param>
/// <returns></returns>
public int CheckBalance(BankAccount account)
{
return account.TotalMoney;
}
/// <summary>
/// 取款
/// </summary>
/// <param name="account">银行账户</param>
/// <param name="money">取多少钱</param>
/// <returns>余额</returns>
public bool WithdrewMoney(BankAccount account, int money)
{
if (account.TotalMoney >= money)
account.TotalMoney -= money;
else
throw new Exception("余额不足!");
return true;
}
/// <summary>
/// 转账
/// </summary>
/// <param name="account">转出账户</param>
/// <param name="targetNo">目标账户</param>
/// <param name="money">转多少钱</param>
/// <returns></returns>
public bool TransferMoney(BankAccount account, string targetNo, int money)
{
var targetAccount = AccountSubsystem.GetAccount(targetNo);
if (targetAccount == null)
throw new Exception("目标账户不存在!");
if (account.TotalMoney < money)
throw new Exception("余额不足!");
account.TotalMoney -= money;
targetAccount.TotalMoney += money;
return true;
}
/// <summary>
/// 存款
/// </summary>
/// <param name="account">银行账户</param>
/// <param name="money">存多少钱</param>
/// <returns></returns>
public bool DepositMoney(BankAccount account, int money)
{
account.TotalMoney += money;
return true;
}
/// <summary>
/// 充值话费
/// </summary>
/// <param name="phoneNumber">手机号</param>
/// <param name="account">银行账户</param>
/// <param name="money">充值多少</param>
/// <returns></returns>
public bool RechargeMobilePhone(BankAccount account, string phoneNumber, int money)
{
throw new NotImplementedException();
}
}
其中『银行现金子系统』负责金额的查询流转。我们还需要引入『账户管理子系统』来管理银行账户:
/// <summary>
/// 账户管理子系统
/// </summary>
public static class AccountSubsystem
{
private static readonly List<BankAccount> Accounts = new List<BankAccount>
{
new BankAccount("123455", "555555", "圣杰", "138****9309", 1000000),
new BankAccount("123454", "444444", "产品汪", "157****9309", 2000000),
new BankAccount("123453", "333333", "运营喵", "154****9309", 3000000),
new BankAccount("123452", "222222", "程序猿", "187****9309", 4000000),
new BankAccount("123451", "111111", "设计狮", "189****9309", 5000000)
};
public static BankAccount Login(string bankNo, string password)
{
var bankAccount = Accounts.FirstOrDefault(a => a.BankNo == bankNo);
if (bankAccount == null)
throw new Exception("无效卡号!!!");
if (bankAccount.Password != password)
throw new Exception("密码错误!!!");
return bankAccount;
}
public static BankAccount GetAccount(string bankNo)
{
var bankAccount = Accounts.FirstOrDefault(a => a.BankNo == bankNo);
if (bankAccount == null)
throw new Exception("无效卡号!!!");
return bankAccount;
}
public static void Display(BankAccount account)
{
Console.WriteLine("卡号:{0},持卡人姓名:{1},手机号:{2},余额:{3}", account.BankNo, account.Name, account.Phone,
account.TotalMoney);
}
public static bool ChangePassword()
{
throw new NotImplementedException();
}
}
银行账户子系统中预置了一些账户供测试使用。
下面就引入我们这节要讲的门面AtmFacade
:
/// <summary>
/// ATM机专属门面
/// </summary>
public class AtmFacade
{
private readonly IBankSubsystem _bankSubsystem = new BankSubsystem();
private BankAccount _account;
public void Login(string no, string pwd)
{
_account = AccountSubsystem.Login(no, pwd);
}
public bool IsLogin()
{
return _account != null;
}
/// <summary>
/// 取款
/// </summary>
/// <param name="money"></param>
public void WithdrewCash(int money)
{
if (_bankSubsystem.WithdrewMoney(_account, money))
{
Console.WriteLine("取款成功!");
AccountSubsystem.Display(_account);
}
}
/// <summary>
/// 存款
/// </summary>
/// <param name="money"></param>
public void DepositCash(int money)
{
if (_bankSubsystem.DepositMoney(_account, money))
{
Console.WriteLine("存款成功!");
AccountSubsystem.Display(_account);
}
}
/// <summary>
/// 查余额
/// </summary>
public void QueryBalance()
{
if (_bankSubsystem.CheckBalance(_account) > 0)
AccountSubsystem.Display(_account);
}
/// <summary>
/// 转账
/// </summary>
/// <param name="targetNo"></param>
/// <param name="money"></param>
public void TransferMoney(string targetNo, int money)
{
if (_bankSubsystem.TransferMoney(_account, targetNo, money))
{
Console.WriteLine("转账成功!");
AccountSubsystem.Display(_account);
}
}
}
该门面就依赖了IBankSubsystem
和AccountSubsystem
,来负责处理ATM与银行的现金业务系统、银行的账户系统进行交互。
最后上我们的ATM:
public class ATM
{
public void DisplayUi()
{
var facade = new AtmFacade();
while (true)
try
{
if (!facade.IsLogin())
{
Console.WriteLine("请输入银行卡号:");
var bkNo = Console.ReadLine();
Console.WriteLine("请输入密码:");
var pwd = Console.ReadLine();
facade.Login(bkNo, pwd);
}
else
{
ShowBusiness(facade);
}
}
catch (Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(ex.Message);
Console.ResetColor();
}
}
private static void ShowBusiness(AtmFacade facade)
{
Console.WriteLine("==========================================");
Console.WriteLine("欢迎你!请选择服务项目:");
Console.WriteLine("1、取款");
Console.WriteLine("2、存款");
Console.WriteLine("3、转账");
Console.WriteLine("4、查询余额");
Console.WriteLine("5、清屏");
Console.WriteLine("==========================================");
var pressKey = Console.ReadKey();
switch (pressKey.Key)
{
case ConsoleKey.D1:
Console.WriteLine();
Console.WriteLine("请输入取款金额:");
var money = Convert.ToInt32(Console.ReadLine());
facade.WithdrewCash(money);
break;
case ConsoleKey.D2:
Console.WriteLine();
Console.WriteLine("请输入存款金额:");
var depositNum = Convert.ToInt32(Console.ReadLine());
facade.DepositCash(depositNum);
break;
case ConsoleKey.D3:
Console.WriteLine();
Console.WriteLine("请输入目标账号:");
var targetNo = Console.ReadLine();
Console.WriteLine("请输入转账金额:");
var transferNum = Convert.ToInt32(Console.ReadLine());
facade.TransferMoney(targetNo, transferNum);
break;
case ConsoleKey.D4:
Console.WriteLine();
facade.QueryBalance();
break;
case ConsoleKey.D5:
Console.Clear();
break;
default:
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("输入有误,请重新输入");
Console.ResetColor();
break;
}
}
代码太多,可能看的不太一目了然,咱们直接看类型依赖图:
总结:
从类型依赖图中,我们可以清晰的看出AtmFacade是ATM与银行两大子系统进行交互的唯一桥梁,ATM不依赖具体的子系统。且ATM仅能访问门面上提供的功能(查询、转账、取款、存款业务),子系统不用担心暴露过多的接口造成安全问题。
但同时引入一个新的问题,假如ATM需要加入『充值话费、无卡取款、二维码取款』新功能,则需要对门面进行更改,不符合OCP。
优缺点:
优点
- 减少了系统间的相互依赖,提高了灵活性
- 提高了安全性(子系统仅能访问门面上开通的方法)
缺点
- 不符合OCP(门面对象是外界与子系统交互的唯一桥梁)
应用场景:
- 为一个复杂的模块或子系统提供一个供外界访问的接口
- 子系统相对独立——外界对子系统的访问只要黑箱操作即可