设计模式学习笔记十三:外观模式(Facade Pattern)
1.概述
外观模式(Façade),为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层的接口,这个接口使得这个子系统更加容易使用。通过这个接口,其他系统可以方便的调用子系统中的功能,而忽略子系统内部发生的变化。
外观模式(Façade)是经常使用的模式之一,并且可以应用在任何层次和粒度的应用中,小到API的封装,大到封装整个系统。例如在使用ADO.NET时,为了执行SQL,需要使用Connection,Command和DataAdapter等,这样显然比较的麻烦,因此我们可以将整个数据库访问封装到一个类中,该类封装了访问数据库的过程,这个类就是一个外观模式。
下面我们看外观模式的结构:
结构图说明:
外观类(Facade):客户端可以调用这个类的方法。此类知晓相关的(一个或者多个)子系统的功能和责任。在正常情况下,本类会将所有从客户端发来的请求委派到相应的子系统去。
子系统(subsystem):可以同时有一个或者多个子系统。每一个子系统都不是一个单独的类,而是一个类的集合。每一个子系统都可以被客户端直接调用,或者被外观类调用。子系统并不知道门面的存在,对于子系统而言,外观类仅仅是另外一个客户端而已。
基本代码:(来自大话设计模式)
四个子系统类:
public class SubSystemOne
{
public void MethodOne()
{
Console.WriteLine(" 子系统方法一");
}
}
public class SubSystemTwo
{
public void MethodTwo()
{
Console.WriteLine(" 子系统方法二");
}
}
public class SubSystemThree
{
public void MethodThree()
{
Console.WriteLine(" 子系统方法三");
}
}
public class SubSystemFour
{
public void MethodFour()
{
Console.WriteLine(" 子系统方法四");
}
}
外观类:
public class Facade
{
SubSystemOne one;
SubSystemTwo two;
SubSystemThree three;
SubSystemFour four;
public Facade()
{
one = new SubSystemOne();
two = new SubSystemTwo();
three = new SubSystemThree();
four = new SubSystemFour();
}
public void MethodA()
{
Console.WriteLine("\n方法组A() ---- ");
one.MethodOne();
two.MethodTwo();
four.MethodFour();
}
public void MethodB()
{
Console.WriteLine("\n方法组B() ---- ");
two.MethodTwo();
three.MethodThree();
}
}
客户端调用(由于Facade的作用,客户端可以根本不知道四个子系统类的存在):
public class Program
{
static void Main(string[] args)
{
Facade facade = new Facade();
facade.MethodA();
facade.MethodB();
Console.Read();
}
}
2.实例
数据库访问外观模式:
在使用ADO.NET访问数据库时,我们通常需要编写下面的访问数据库的语句来访问数据库:
SqlConnection myConn = new SqlConnection();
myConn.ConnectionString = "Database=xxx;Server=local;User Id=sa;Password=123456;";
myConn.Open();
DataTable dt = new DataTable();
SqlCommand cmd = new SqlCommand(strSQL);
SqlDataAdapter da = new SqlDataAdapter(cmd);
cmd.Connection = myConn;
da.Fill(dt);
myConn.Close();
如上面的代码所示,为了执行一个SQL,需要使用Connection、Command和DataAdapter,这样显然比较麻烦,因此,我们可以利用外观模式,将数据库访问封装到一个类中,该类分装了访问数据库的过程。下面是给出一个通用的封装的数据访问层,代码如下:
using System;
using System.Data;
using System.Collections;
namespace DAL
{
/**//// <summary>
/// DALResult
/// Copyright peida 2008-7-15
/// </summary>
public class DALResult
{
public bool IsSucceed; //存储过程是否执行成功
public string errorMessage; //访问数据库失败
public int rowsCount; //结果集行数.
public object theFirst; //第一行第一列
public Hashtable OutputValues; //存储过程output值,放在(HashTable)表OutputValues里.
public DataTable datatable; //存储过程返回的结果集,放在(DataTable)表datatable里.
public DataSet dataSet; //存储过程返回的结果集,放在DataSet表中
public DALResult()
{
IsSucceed = false;
rowsCount = 0;
errorMessage = "";
theFirst = null;
OutputValues = new Hashtable();
datatable=new DataTable();
dataSet=new DataSet();
}
}
}
SQL语句的分装:
using System;
using System.IO;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
using System.Collections;
namespace DAL
{
/**//// <summary>
/// SQL_Base 调用SQL语句
/// Copyright peida 2008-7-15
/// </summary>
public class SQL_Base : IDisposable
{
private string strconn = "";
public SQL_Base() : this("")
{
strconn = ConfigurationSettings.AppSettings["DBpath"];
}
//重载
public SQL_Base(string sql_name)
{
strconn = ConfigurationSettings.AppSettings[sql_name];
}
//私有成员变量
private string sql_name;
private SqlConnection myConnection;
private SqlCommand myCommand;
//公共属性
public string SQLName
{
get
{
return this.sql_name;
}
set
{
this.sql_name = value;
}
}
/**//// <summary>
/// 执行SQL语句,返回数据集:DataTable或DataSet
/// </summary>
/// <returns>返回DALResult</returns>
public DALResult ExecuteSqlData()
{
DALResult result = new DALResult();
myConnection = new SqlConnection(strconn);
myCommand = new SqlCommand(this.sql_name, myConnection);
myCommand.CommandType = CommandType.Text;
SqlDataAdapter myAdapter = new SqlDataAdapter(myCommand);
myConnection.Open();
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;
//提交事物
trans.Commit();
}
catch(Exception e)
{
result.errorMessage = e.Message;
}
//如果捕捉了异常,但仍会执行包括在 finally 块中的输出语句
finally
{
myAdapter.Dispose();
myCommand.Dispose();
myConnection.Close();
myConnection.Dispose();
}
}
return result;
}
/**//// <summary>
/// 获取第一行第一列数据
/// </summary>
/// <returns>返回DALResult</returns>
public DALResult ExecuteSqlScalar()
{
DALResult result = new DALResult();
myConnection = new SqlConnection(strconn);
myCommand = new SqlCommand(this.sql_name, myConnection);
myCommand.CommandType = CommandType.Text;
myConnection.Open();
using(SqlTransaction trans = myConnection.BeginTransaction())
{
try
{
if(trans!=null)
{
myCommand.Transaction = trans;
}
object scalarresult = myCommand.ExecuteScalar();
if(Object.Equals(scalarresult,null))
{
result.errorMessage = "没有取到数据";
}
else
{
result.theFirst = scalarresult;
//取到数据
result.rowsCount=1;
}
result.IsSucceed = true;
//提交事物
trans.Commit();
}
catch(Exception e)
{
result.errorMessage = e.Message;
}
finally
{
myCommand.Dispose();
myConnection.Close();
myConnection.Dispose();
}
}
return result;
}
/**//// <summary>
/// 执行无返回结果的SQL语句
/// </summary>
/// <returns>返回DALResult</returns>
public DALResult ExecuteSqlNonQuery()
{
DALResult result = new DALResult();
myConnection = new SqlConnection(strconn);
myCommand = new SqlCommand(this.sql_name, myConnection);
myCommand.CommandType = CommandType.Text;
myConnection.Open();
using(SqlTransaction trans = myConnection.BeginTransaction())
{
try
{
if(trans!=null)
{
myCommand.Transaction = trans;
}
myCommand.ExecuteNonQuery();
result.IsSucceed = true;
//提交事物
trans.Commit();
}
catch(Exception e)
{
result.errorMessage = e.Message;
}
finally
{
myCommand.Dispose();
myConnection.Close();
myConnection.Dispose();
}
}
return result;
}
/**//// <summary>
/// ExecuteReader
///
/// </summary>
///
public SqlDataReader ExecuteReader(CommandType cmdType, string cmdText, params SqlParameter[] commandParameters)
{
SqlCommand cmd = new SqlCommand();
SqlConnection conn = new SqlConnection(strconn);
// we use a try/catch here because if the method throws an exception we want to
// close the connection throw code, because no datareader will exist, hence the
// commandBehaviour.CloseConnection will not work
try
{
PrepareCommand(cmd, conn, null, cmdType, cmdText, commandParameters);
SqlDataReader rdr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
cmd.Parameters.Clear();
return rdr;
}
catch
{
conn.Close();
throw;
}
}
private void PrepareCommand(SqlCommand cmd, SqlConnection conn, SqlTransaction trans, CommandType cmdType, string cmdText, SqlParameter[] cmdParms)
{
if (conn.State != ConnectionState.Open)
conn.Open();
cmd.Connection = conn;
cmd.CommandText = cmdText;
if (trans != null)
cmd.Transaction = trans;
cmd.CommandType = cmdType;
if (cmdParms != null)
{
foreach (SqlParameter parm in cmdParms)
cmd.Parameters.Add(parm);
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(true);
}
protected virtual void Dispose(bool disposing)
{
if (! disposing)
return;
if(myConnection != null)
{
myConnection.Dispose();
}
}
}
}
存储过程的封装:
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