C#设计模式之11——外观模式

来源:互联网 发布:阿里旺旺mac版如何退出 编辑:程序博客网 时间:2024/06/03 22:03

外观模式被用来把一组复杂的类包装在一个比较简单的封装接口中。

随着类的不断发展,可能会存在一些复杂的子系统,并且每个子系统有着自己复杂的接口。外观模式允许通过给这些子系统提供一个简化了的接口来降低这一复杂性。在降低复杂性的同时,用户也可以自己选择直接使用底层的方法。

在C#中ADO.NET的组件有着非常复杂的数据应用,我们这里的例子就是对ADO.NET组件中的类进行封装,提供一个比较简单的对数据库操作的接口。

 

我们有一个数据问价,存储着不同商店货物的价格,格式如下:

Stop and Shop,Apples,        0.27Stop and Shop,Oranges,        0.36Stop and Shop,Hamburger,1.98Stop and Shop,Butter,        2.39Stop and Shop,Milk,        1.98Stop and Shop,Cola,        2.65Stop and Shop,Green beans,2.29Village Market,Apples,        0.29Village Market,Oranges,        0.29Village Market,Hamburger,2.45


 

在我们的程序中,要实现的功能是,选择一种货物,然后显示不同商店的价格,用户界面如图:

 

我们的程序封装了一个处理创建、连接以及使用数据的新外观类。

我们先创建一个代表了到数据库连接的抽象DBase类,该类封装了连接的简历、表的打开和SQL查询:

using System;using System.Data ;using System.Data.SqlClient ;using System.Data.OleDb ;namespace Facade{/// <summary>/// Summary description for DBase./// </summary>public abstract class DBase {protected OleDbConnection conn;private void openConnection() {if (conn.State == ConnectionState.Closed){conn.Open ();}}//------private void closeConnection() {if (conn.State == ConnectionState.Open ){conn.Close ();}}//------public DataTable openTable (string tableName) {OleDbDataAdapter adapter = new OleDbDataAdapter ();DataTable dtable = null;string query = "Select * from " + tableName;adapter.SelectCommand = new OleDbCommand (query, conn);DataSet dset = new DataSet ("mydata");try {openConnection();adapter.Fill (dset);dtable = dset.Tables [0];}catch(Exception e) {Console.WriteLine (e.Message );}return dtable;}//------public DataTable openQuery(string query) {OleDbDataAdapter dsCmd = new OleDbDataAdapter ();DataSet dset = new DataSet ();//create a datasetDataTable dtable = null;//declare a data tabletry {//create the commanddsCmd.SelectCommand = new OleDbCommand(query, conn);openConnection();//open the connection//fill the datasetdsCmd.Fill(dset, "mine");//get the tabledtable = dset.Tables[0];closeConnection();//always close itreturn dtable;//and return it}catch (Exception e) {Console.WriteLine (e.Message);return null;}}//------public void openConnection(string connectionString) {conn = new OleDbConnection(connectionString);}//------public OleDbConnection getConnection() {return conn;}}}


我们根据需要根据使用的不同的数据库生成这个类的一些派生类,比如Access数据库的派生类版本:

using System;using System.Data ;using System.Data.SqlClient ;using System.Data.OleDb ;namespace Facade{/// <summary>/// Summary description for AxsDatabase./// </summary>public class AxsDatabase :DBase{public AxsDatabase(string dbName){ string connectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + dbName; openConnection(connectionString);}}}


 

SQL Server的数据库的派生类版本:

using System;using System.Data ;using System.Data.SqlClient ;using System.Data.OleDb ;namespace Facade{/// <summary>/// Summary description for SQLServerDatabase./// </summary>public class SQLServerDatabase:DBase {string connectionString;//-----public SQLServerDatabase(String dbName) { connectionString = "Persist Security Info = False;" +                "Initial Catalog =" + dbName + ";" +                "Data Source = myDataServer;User ID = myName;" +                "password=";openConnection(connectionString);}//-----public SQLServerDatabase(string dbName, string serverName, string userid, string pwd) { connectionString = "Persist Security Info = False;" +                "Initial Catalog =" + dbName + ";" +                "Data Source =" + serverName + ";" +                "User ID =" + userid + ";" +                "password=" + pwd;openConnection(connectionString);        }}}


这两个不同的派生类提供了不同的数据库连接字符串。

用到的另外一个类是DBTable类,封装了打开、加载以及更新单个数据表的操作。我们通过派生这个类可以实现商品类,商店类,价格类。

DBTable:

using System;using System.Data ;using System.Data.SqlClient ;using System.Data.OleDb ;using System.Collections ;namespace Facade{/// <summary>/// Summary description for DBTable./// </summary>public class DBTable {protected DBase db;protected string tableName;private bool filled, opened;private DataTable dtable;private int rowIndex;private Hashtable names;private string columnName;private DataRow row;private OleDbConnection conn;private int index;//-----public DBTable(DBase datab, string tb_Name) {db = datab;tableName = tb_Name;filled =false;opened = false;names = new Hashtable();}//-----public void createTable() {try {dtable = new DataTable(tableName);dtable.Clear();}catch (Exception e) {Console.WriteLine (e.Message );}}//-----public bool hasMoreElements() {if(opened)return (rowIndex < dtable.Rows.Count) ;elsereturn false;}//-----public virtual string getValue(string cname) { //returns the next name in the table        //assumes that openTable has already been calledif (opened) {DataRow row = dtable.Rows[rowIndex++];return row[cname].ToString().Trim ();}elsereturn "";         }//-----public int getKey(string nm, string keyname){DataRow row;int key;if(! filled)return (int)names[ nm];else {string query = "select * from " + tableName + " where " + columnName + "=\'" + nm + "\'";dtable = db.openQuery(query);row = dtable.Rows[0];key = Convert.ToInt32 (row[keyname].ToString());return key;}}//-----public virtual void makeTable(string cName) {columnName = cName;//stores current hash table values in data tableDataSet dset = new DataSet(tableName);   //create the data setdtable = new DataTable(tableName);   //and a datatable        dset.Tables.Add(dtable);             //add to collection    conn = db.getConnection();openConn();                      //open the connectionOleDbDataAdapter adcmd = new OleDbDataAdapter();//open the tableadcmd.SelectCommand = new OleDbCommand("Select * from " + tableName, conn);OleDbCommandBuilder olecb = new OleDbCommandBuilder(adcmd);adcmd.TableMappings.Add("Table", tableName);//load current data into the local table copyadcmd.Fill(dset, tableName);//get the Enumerator from the HashtableIEnumerator ienum = names.Keys.GetEnumerator();//move through the table, adding the names to new rowswhile (ienum.MoveNext()) {string name = (string)ienum.Current;row = dtable.NewRow();     //get new rowsrow[columnName] = name;dtable.Rows.Add(row);    //add into table}        //Now update the database with this tabletry {adcmd.Update(dset);closeConn();filled = true;}catch (Exception e) {Console.WriteLine (e.Message);}}//-----private void closeConn() { if( conn.State == ConnectionState.Open) { conn.Close(); }        }//-----private void openConn() {if(conn.State == ConnectionState.Closed ) {conn.Open();}}//-----public void addTableValue(string nm) {//accumulates names in hash tabletry {names.Add(nm, index++);}catch (ArgumentException) {}            //do not allow duplicate names to be added          }public void openTable() {dtable = db.openTable(tableName);rowIndex = 0;if(dtable != null)opened = true;}//-----public void delete() {//deletes entire tableconn = db.getConnection();openConn();if (conn.State == ConnectionState.Open ) {OleDbCommand adcmd = new OleDbCommand("Delete * from " + tableName, conn);try{adcmd.ExecuteNonQuery();closeConn();}catch (Exception e) {Console.WriteLine (e.Message);}}}}}


然后我们就可以派生这个类生成商店类,商品类和价格类。

using System;namespace Facade{/// <summary>/// Summary description for Stores./// </summary>public class Stores :DBTable {public Stores(DBase db):base(db, "Stores"){}   //-----    public void makeTable() {base.makeTable ("Storename");}}}


 

using System;namespace Facade{/// <summary>/// Summary description for Foods./// </summary>public class Foods: DBTable {public Foods(DBase db):base(db, "Foods"){}//-----public void makeTable() {base.makeTable ("Foodname");}//-----public string getValue() {return base.getValue ("FoodName");}}}


Price类的构建有点特殊,因为Price类包含的其他两个表的引用,所以需要添加一个其他的表示在Price表格中一行的对象:

using System;namespace Facade{/// <summary>/// Summary description for StoreFoodPrice./// </summary>public class StoreFoodPrice {private int storeKey, foodKey;private float foodPrice;//-----public StoreFoodPrice(int sKey, int fKey, float fPrice) {storeKey = sKey;foodKey = fKey;foodPrice = fPrice;}//-----public int getStore() {return storeKey;}//-----public int getFood() {return foodKey;}//-----public float getPrice() {return foodPrice;}}}


 

这样Price类中添加一行数据,就实际需要添加StoreFoodPrice 类的一个实例。

using System;using System.Data ;using System.Data.SqlClient ;using System.Data.OleDb ;using System.Collections ;namespace Facade{/// <summary>/// Summary description for Prices./// </summary>public class Prices : DBTable {private ArrayList priceList;public Prices(DBase db) : base(db, "Prices") {priceList = new ArrayList ();}//-----public void makeTable() {//stores current array list values in data table        OleDbConnection adc = new OleDbConnection();                DataSet dset = new DataSet(tableName);        DataTable dtable = new DataTable(tableName);        dset.Tables.Add(dtable);        adc = db.getConnection();        if (adc.State == ConnectionState.Closed) adc.Open();        OleDbDataAdapter adcmd = new OleDbDataAdapter();        //fill in price table        adcmd.SelectCommand = new OleDbCommand("Select * from " + tableName, adc);        OleDbCommandBuilder custCB = new OleDbCommandBuilder(adcmd);        adcmd.TableMappings.Add("Table", tableName);        adcmd.Fill(dset, tableName);        IEnumerator ienum = priceList.GetEnumerator();        //add new price entrieswhile (ienum.MoveNext() ) {StoreFoodPrice fprice = (StoreFoodPrice)ienum.Current;DataRow row = dtable.NewRow();row["foodkey"] = fprice.getFood();row["storekey"] = fprice.getStore();row["price"] = fprice.getPrice();dtable.Rows.Add(row);    //add to table}        adcmd.Update(dset);      //send back to database        adc.Close();        }//-----public DataTable getPrices(string food) {string query=        "SELECT Stores.StoreName, " +        "Foods.Foodname, Prices.Price " +        "FROM (Prices INNER JOIN Foods ON " +        "Prices.Foodkey = Foods.Foodkey) " +        "INNER JOIN Stores ON Prices.StoreKey = Stores.StoreKey " +        "WHERE(((Foods.Foodname) = \'" + food + "\')) " +        "ORDER BY Prices.Price";        return db.openQuery(query);}//-----public void addRow(int storeKey, int foodKey, float price) {priceList.Add (new StoreFoodPrice (storeKey, foodKey, price));}}}

这三个相应的类都把本地数据更新到了数据库中。

现在所有的派生类都设计好了,就需要编写一个从数据文件中加载表的类了,该类第一次构建Stores和Foods这两个数据表,接着再次读取数据文件构造Prices这个表。

using System;using CsharpPats;namespace Facade{/// <summary>/// Summary description for DataLoader./// </summary>public class DataLoader {private csFile vfile;private Stores store;private Foods fods;private Prices price;private DBase db;//-----public DataLoader(DBase datab) {db = datab;store = new Stores(db);fods = new Foods (db);price = new Prices(db);}//-----public void load(string dataFile) {string sline;int storekey, foodkey;StringTokenizer tok;//delete current table contentsstore.delete();fods.delete();price.delete();//now read in new onesvfile = new csFile(dataFile);vfile.OpenForRead();sline = vfile.readLine();while (sline != null){tok = new StringTokenizer(sline, ",");store.addTableValue(tok.nextToken());   //store namefods.addTableValue(tok.nextToken());   //food namesline = vfile.readLine();}vfile.close();//construct store and food tablesstore.makeTable();fods.makeTable();vfile.OpenForRead();sline = vfile.readLine();while (sline != null) {//get the gets and add to storefoodprice objectstok = new StringTokenizer(sline, ",");storekey = store.getKey(tok.nextToken(), "Storekey");foodkey = fods.getKey(tok.nextToken(), "Foodkey");price.addRow(storekey, foodkey, Convert.ToSingle (tok.nextToken()));sline = vfile.readLine();}        //add all to price table        price.makeTable();        vfile.close();}}}

这个类中调用了三个表类的makeTable()方法,把数据更新到了数据库。

最终调用的主程序:

private DBase db;private Stores shops;private Prices prc;//-----------private void init() { db = new AxsDatabase("Groceries.mdb");        shops = new Stores(db);        prc = new Prices(db);        loadFoodTable();        ToolTip tips = new ToolTip();        tips.SetToolTip(btLoad, "Reload data from groceries.txt file");}public Form1(){InitializeComponent();init();}private void loadFoodTable() { Foods fods =new Foods(db);        fods.openTable();        while (fods.hasMoreElements()){lsFoods.Items.Add(fods.getValue());}}


 

private void btLoad_Click(object sender, System.EventArgs e) { lsFoods.Items.Clear();        Cursor.Current = Cursors.WaitCursor;        DataLoader dload = new DataLoader(db);        dload.load("groceries.txt");        loadFoodTable();        Cursor.Current = Cursors.Default;}private void lsFoods_SelectedIndexChanged(object sender, System.EventArgs e) { string food  = lsFoods.Text;        DataTable dtable = prc.getPrices(food);        lsPrices.Items.Clear();        foreach (DataRow rw in dtable.Rows) {            lsPrices.Items.Add(rw["StoreName"].ToString().Trim() +"\t" + rw["Price"].ToString());}}

 

外观模式帮助客户端避开复杂的子系统组件,并未普通用户提供了一个比较简单的编程接口。不过,其并不阻止高级用户在需要的时候直接使用较底层的更加复杂的类。外观模式允许在无需要求客户端代码变更的情况下对底层子系统做出修改,这降低了编译依赖。