设计模式学习笔记十三:外观模式(Facade Pattern)
1.概述
外观模式(Façade),为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层的接口,这个接口使得这个子系统更加容易使用。通过这个接口,其他系统可以方便的调用子系统中的功能,而忽略子系统内部发生的变化。
外观模式(Façade)是经常使用的模式之一,并且可以应用在任何层次和粒度的应用中,小到API的封装,大到封装整个系统。例如在使用ADO.NET时,为了执行SQL,需要使用Connection,Command和DataAdapter等,这样显然比较的麻烦,因此我们可以将整个数据库访问封装到一个类中,该类封装了访问数据库的过程,这个类就是一个外观模式。
下面我们看外观模式的结构:
结构图说明:
外观类(Facade):客户端可以调用这个类的方法。此类知晓相关的(一个或者多个)子系统的功能和责任。在正常情况下,本类会将所有从客户端发来的请求委派到相应的子系统去。
子系统(subsystem):可以同时有一个或者多个子系统。每一个子系统都不是一个单独的类,而是一个类的集合。每一个子系统都可以被客户端直接调用,或者被外观类调用。子系统并不知道门面的存在,对于子系统而言,外观类仅仅是另外一个客户端而已。
基本代码:(来自大话设计模式)
四个子系统类:

















































外观类:








































客户端调用(由于Facade的作用,客户端可以根本不知道四个子系统类的存在):



















2.实例
数据库访问外观模式:
在使用ADO.NET访问数据库时,我们通常需要编写下面的访问数据库的语句来访问数据库:











如上面的代码所示,为了执行一个SQL,需要使用Connection、Command和DataAdapter,这样显然比较麻烦,因此,我们可以利用外观模式,将数据库访问封装到一个类中,该类分装了访问数据库的过程。下面是给出一个通用的封装的数据访问层,代码如下:








































SQL语句的分装:












































































































































































































































































































































存储过程的封装:


using System;
using System.IO;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
namespace DAL
{
/// <summary>
///调用存储过程
///Copyright Peida 2008-7-15
/// </summary>
public class SP_Base : IDisposable
{
public SP_Base() : this("")
{
}
//重载
public SP_Base(string sql_name)
{
this.sp_name = sp_name;
}
//私有成员变量
private string strconn=ConfigurationSettings.AppSettings["DBpath"];
private string sp_name;
private SqlConnection myConnection;
private SqlCommand myCommand;
//存储过程参数
private SqlParameter myParameter;
//公共属性
public string ProcedureName
{
get
{
return this.sp_name;
}
set
{
this.sp_name = value;
}
}
//========begin=========
/// <summary>
/// 调用存储过程
/// </summary>
/// <param name="parameters">参数集合</param>
/// <returns></returns>
public DALResult Call_SP(params object[] parameters)
{
DALResult result = new DALResult();
myConnection = new SqlConnection(strconn);
myCommand = new SqlCommand(this.ProcedureName, myConnection);
myCommand.CommandType = CommandType.StoredProcedure;
SqlDataAdapter myAdapter = new SqlDataAdapter(myCommand);
myConnection.Open();
GetProcedureParameter(result,parameters);
//事物
using(SqlTransaction trans = myConnection.BeginTransaction())
{
try
{
if(trans!=null)
{
myCommand.Transaction = trans;
}
//填充数据,将结果填充到DALResult集中
myAdapter.Fill(result.dataSet);
if(result.dataSet.Tables.Count>0)
{
result.datatable=result.dataSet.Tables[0].Copy();
if(result.dataSet.Tables[0].Rows.Count>0)
{
result.rowsCount = result.dataSet.Tables[0].Rows.Count;
}
}
result.IsSucceed = true;
//将输出参数的值添加到Result
GetOutputValue(result);
//提交事物
trans.Commit();
}
catch(Exception e)
{
result.errorMessage = e.Message;
//事物回滚
trans.Rollback();
}
//如果捕捉了异常,但仍会执行包括在 finally 块中的输出语句
finally
{
myAdapter.Dispose();
myCommand.Dispose();
myConnection.Close();
myConnection.Dispose();
}
}
return result;
}
/// <summary>
/// 将参数添加到存储过程的参数集合
/// </summary>
/// <param name="parameters"></param>
private void GetProcedureParameter(DALResult result,params object[] parameters)
{
SqlCommand myCommand2 = new SqlCommand();
myCommand2.Connection = this.myConnection;
myCommand2.CommandText = "select * from INFORMATION_SCHEMA.PARAMETERS where SPECIFIC_NAME='" +this.ProcedureName+ "' order by ORDINAL_POSITION";
SqlDataReader reader = null;
try
{
reader = myCommand2.ExecuteReader();
int i = 0;
while(reader.Read())
{
myParameter = new SqlParameter();
myParameter.ParameterName = reader["PARAMETER_NAME"].ToString();
myParameter.Direction = reader["PARAMETER_MODE"].ToString()=="IN"?ParameterDirection.Input:ParameterDirection.Output;
switch(reader["DATA_TYPE"].ToString())
{
case "bit" :
if(myParameter.Direction == ParameterDirection.Input)
myParameter.Value = Convert.ToBoolean(parameters[i]);
myParameter.SqlDbType = SqlDbType.Bit;
break;
case "bigint":
if(myParameter.Direction == ParameterDirection.Input)
myParameter.Value = Convert.ToInt32(parameters[i]);
myParameter.SqlDbType = SqlDbType.BigInt;
break;
case "int" :
if(myParameter.Direction == ParameterDirection.Input)
myParameter.Value = Convert.ToInt32(parameters[i].ToString());
myParameter.SqlDbType = SqlDbType.Int;
break;
case "decimal" :
if(myParameter.Direction == ParameterDirection.Input)
myParameter.Value = (decimal)parameters[i];
myParameter.SqlDbType = SqlDbType.Decimal;
myParameter.Precision = (byte)reader["NUMERIC_PRECISION"];
myParameter.Scale = byte.Parse(reader["NUMERIC_SCALE"].ToString());
break;
case "nvarchar" :
if(myParameter.Direction == ParameterDirection.Input)
myParameter.Value = Convert.ToString(parameters[i]);
myParameter.Size = Convert.ToInt32(reader["CHARACTER_MAXIMUM_LENGTH"]);
myParameter.SqlDbType = SqlDbType.NVarChar;
break;
case "varchar" :
if(myParameter.Direction == ParameterDirection.Input)
myParameter.Value = (string)parameters[i];
myParameter.Size = Convert.ToInt32(reader["CHARACTER_MAXIMUM_LENGTH"]);
myParameter.SqlDbType = SqlDbType.VarChar;
break;
case "nchar" :
if(myParameter.Direction == ParameterDirection.Input)
myParameter.Value = (string)parameters[i];
myParameter.Size = Convert.ToInt32(reader["CHARACTER_MAXIMUM_LENGTH"]);
myParameter.SqlDbType = SqlDbType.NChar;
break;
case "char" :
if(myParameter.Direction == ParameterDirection.Input)
myParameter.Value = (string)parameters[i];
myParameter.Size = Convert.ToInt32(reader["CHARACTER_MAXIMUM_LENGTH"]);
myParameter.SqlDbType = SqlDbType.Char;
break;
case "ntext" :
if(myParameter.Direction == ParameterDirection.Input)
myParameter.Value = (string)parameters[i];
myParameter.SqlDbType = SqlDbType.NText;
break;
case "text" :
if(myParameter.Direction == ParameterDirection.Input)
myParameter.Value = (string)parameters[i];
myParameter.SqlDbType = SqlDbType.Text;
break;
case "datetime" :
if(myParameter.Direction == ParameterDirection.Input)
myParameter.Value = Convert.ToDateTime(parameters[i]);
myParameter.SqlDbType = SqlDbType.DateTime;
break;
case "smalldatetime" :
if(myParameter.Direction == ParameterDirection.Input)
myParameter.Value = Convert.ToDateTime(parameters[i]);
myParameter.SqlDbType = SqlDbType.DateTime;
break;
case "image" :
if(myParameter.Direction == ParameterDirection.Input)
{
myParameter.Value=(byte[])parameters[i];
}
myParameter.SqlDbType = SqlDbType.Image;
break;
case "real":
if(myParameter.Direction==ParameterDirection.Input)
myParameter.Value=Convert.ToSingle(parameters[i]);
myParameter.SqlDbType = SqlDbType.Real;
break;
case "varbinary":
if(myParameter.Direction==ParameterDirection.Input)
myParameter.Value=(byte[])parameters[i];
myParameter.SqlDbType = SqlDbType.VarBinary;
break;
default :
break;
}
i++;
myCommand.Parameters.Add(myParameter);
}
}
catch(Exception e)
{
result.errorMessage = e.Message;
}
finally
{
if(reader!=null)
{
reader.Close();
}
myCommand2.Dispose();
}
}
/// <summary>
/// 将输出的值添加到Result的OutputValues
/// </summary>
/// <param name="result"></param>
private void GetOutputValue(DALResult result)
{
foreach(SqlParameter parameter in myCommand.Parameters)
{
if(parameter.Direction == ParameterDirection.Output)
{
//Hashtab表是一个键值对
result.OutputValues.Add(parameter.ParameterName, parameter.Value);
}
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(true);
}
protected virtual void Dispose(bool disposing)
{
if (! disposing)
return;
if(myConnection != null)
{
myConnection.Dispose();
}
}
//=======end======
}
}
3.总结
何时采用外观模式:
从代码角度来说, 如果你的程序有多个类是和一组其它接口发生关联的话可以考虑在其中加一个外观类型。
从应用角度来说, 如果子系统的接口是非常细的,调用方也有大量的逻辑来和这些接口发生关系,那么就可以考虑使用Facade把客户端与子系统的直接耦合关系进行化解。你可能会说,子系统改了外观类不是照样改?的确是需要改,但是如果客户端本身的工作已经比较复杂,或者说可能有多个需要调用外观类的地方,这个时候外观类的好处就体现了。
效果及实现要点
1.Facade模式对客户屏蔽了子系统组件,因而减少了客户处理的对象的数目并使得子系统使用起来更加方便。
2.Facade模式实现了子系统与客户之间的松耦合关系,而子系统内部的功能组件往往是紧耦合的。松耦合关系使得子系统的组件变化不会影响到它的客户。
3.如果应用需要,它并不限制它们使用子系统类。因此你可以在系统易用性与通用性之间选择。
4.通过一个高层接口让子系统和客户端不发生直接关联,使客户端不受子系统变化的影响。
5.Facade不仅仅针对代码级别,在构架上,特别是WEB应用程序的构架上,Facade的应用非常普遍。如常常说的三层架构就是典型的外观模式。
参考资料:
大话设计模式
http://terrylee.cnblogs.com/archive/2006/03/17/352349.html
http://www.cnblogs.com/lovecherry/archive/2007/10/07/916202.html
关注 熵减黑客 ,一起学习成长

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端