PetShop4研究----架構設計淺談

今天終於開始研究微軟對於ASP.NET2.0的產品PetShop4.0了,這個產品從架構設計到編碼,都有很多的想法值得去研究 ,而且此產品還引入了許多.net2.0的新特性。不過學習是個長期的過程,設計的思想不可能在段時間去領會,只能一個一個方面去學習和研究。今天研究了 架構,遇到了不少問題,理解起來比較抽象,但還是有一點心得的。
PetShop4.0採用了三層的架構,表現層、業務邏輯層和數據層。
分層的優勢:
1、使得各層相互獨立,減少依賴性
2、方便開發人員職責分離,僅僅負責其中的某一塊,而不用去考慮其它實現
3、方便管理和維護,其中一處的改動不會影響到其它的層
4、方便邏輯的複用
不足:
1、如果有新的功能加入到系統中,在自下而上的方法中,各個層都需要添加新的代碼,小系統一般不會有太大的工作量,但是大系統往往比較麻煩
2、本來可以直接操作數據庫完成對數據庫的操作,但是由於分層,系統性能受到了一定的影響,對小的應用,還是使用不分層來實現對數據庫的直接操作,可以取得較好的性能
3、分層之後,每層都有許多對應實現的模式,邏輯往往很抽象,給理解帶來了困難,特別對於許多沒有大型項目經驗的人
整個系統的結構如下:
ps01.gif
系統架構簡圖

ps05.gif
詳細的體系架構

表現層:系統的UI組件,直接面對使用者,主要負責最終用戶跟系統的交互,包含基本的服務器端控件和跟頁面有關的操作(js腳本)
業務邏輯層:是系統的核心層,所有的設計都是圍繞該層進行設計,因爲業務直接跟需求掛鉤,比如用戶的註冊、登錄等、用戶訂單的管理,寵物的管理等
數據訪問層:進行數據存儲並數據對象的持久化和各種操作(增、刪、改、查),因爲這層的設計比較重要,此層往往用工廠模式去實現支持不同的數據庫,因而是理解架構的關鍵部分。

現從數據訪問層入手,分析一下數據訪問底層的設計:
ps06.gif
數據訪問層的模塊結構圖
IDAL:抽象出數據訪問的方法,要引用Model裏面的實體對象
Model:包含實體對象
DBUtility:封裝訪問數據庫的方法,針對不同的數據庫有不同的實現(如SQLHelper.cs和OracleHelper.cs),是數據訪問輔助工具
SQLHelper:封裝ADO.NET的相關操作,用來優化操作
SQLServerDAL :以SQLServer的方式實現接口
OracleDAL:以Oracle的方式實現的接口

以ProductInfo爲例,以SQLServer作爲數據庫例子:

ProductInfo類對產品進行操作(增、刪、改、查),這些操作是通過DBUtility中的SQLHelper類來實現對數據庫的操作的。
例如:
public static readonly string ConnectionStringLocalTransaction = ConfigurationManager.ConnectionStrings["SQLConnString1"].ConnectionString;
public static readonly string ConnectionStringInventoryDistributedTransaction = ConfigurationManager.ConnectionStrings["SQLConnString2"].ConnectionString;
完成在配置文件中讀取相應的數據庫連接字符串
然後再PetShop.SQLServerDAL 命名空間下的Product類中通過調用SQLHelper類中的方法完成對數據庫的操作
例如:
        private const string SQL_SELECT_PRODUCTS_BY_CATEGORY = "SELECT Product.ProductId, Product.Name, Product.Descn, Product.Image, Product.CategoryId FROM Product WHERE Product.CategoryId = @Category";
        private const string SQL_SELECT_PRODUCTS_BY_SEARCH1 = "SELECT ProductId, Name, Descn, Product.Image, Product.CategoryId FROM Product WHERE ((";
  設置SQL命令,然後進行調用:
       public ProductInfo GetProduct(string productId) {
            ProductInfo product = null;
            SqlParameter parm = new SqlParameter(PARM_PRODUCTID, SqlDbType.VarChar, 10);
            parm.Value = productId;
            using (SqlDataReader rdr = SqlHelper.ExecuteReader(SqlHelper.ConnectionStringLocalTransaction, CommandType.Text, SQL_SELECT_PRODUCT, parm))
                if (rdr.Read())
                    product = new ProductInfo(rdr.GetString(0), rdr.GetString(1), rdr.GetString(2), rdr.GetString(3), rdr.GetString(4));
                else
                    product = new ProductInfo();
            return product;
        }
以上使用了SqlHelper.ExecuteReader()方法通過操作ADO.NET實現對數據庫記錄的更新.
程序片段:
using PetShop.IDAL;
public class Product : IProduct{      }
說明Product 實現了在命名空間PetShop.IDAL中的IProduct接口,因此最後對數據的操作最終要返回一個Product對象,然後去調用該對象實現的方法.

PetShop.IDAL:數據訪問層的接口
在此命名空間當中,定義了各種實體對象應該實現的接口,以下是定義在該命名空間中的IProduct接口。

using System;
using System.Collections.Generic;
using PetShop.Model;

namespace PetShop.IDAL{
   
    /// <summary>
    /// Interface for the Product DAL
    /// </summary>
    public interface IProduct{
   
        /// <summary>
        /// Method to search products by category name
        /// </summary>
        /// <param name="category">Name of the category to search by</param>
        /// <returns>Interface to Model Collection Generic of search results</returns>
        IList<ProductInfo>  GetProductsByCategory(string category);   

        /// <summary>
        /// Method to search products by a set of keyword
        /// </summary>
        /// <param name="keywords">An array of keywords to search by</param>
        /// <returns>Interface to Model Collection Generic of search results</returns>
        IList<ProductInfo>  GetProductsBySearch(string[] keywords);

        /// <summary>
        /// Query for a product
        /// </summary>
        /// <param name="productId">Product Id</param>
        /// <returns>Interface to Model ProductInfo for requested product</returns>
        ProductInfo  GetProduct(string productId);
    }
}
作爲上層的BLL層,只管去調用這個接口,但是不管接口是怎麼實現的,但是當BLL層調用的時候,通過的這個接口層最終返回給BLL層的是什麼類型的對象呢?
在實現數據訪問層的過程當中,使用了抽象工廠模式:對於用戶來說,工廠裏產品如何生產的你不用知道,僅僅只去用工廠裏生產出來的產品就可以了。

用工廠模式來實現了對SqlServerOracle數據庫訪問的操作,而BLL層不用知道也不用關心後臺用的是哪一種數據庫,它只要用接口就行了,接口中定義了要用的方法,當調用接口時會根據具體的情況再去調用底層數據訪問操作。

DALFactory是關鍵,當BLL層要操作數據庫時,DALFactory會根據具體情況再去使用SqlServerDALOracleDAL中的一個。這樣系統上層只管調用,而下層實現細節。底下的數據層的實現細節對於上層來說被隱藏了。這樣做到了分離的目標,儘量減少層與層之間的聯繫程度,保持各層的獨立性。

DALFactory工廠模式是如何決定應該用SqlServerDAL還是用OracleDAL的呢?
以下是
DALFactory關於IProduct接口的實現代碼:
using System.Reflection;
using System.Configuration;

namespace PetShop.DALFactory {
    /// <summary>
    /// This class is implemented following the Abstract Factory pattern to create the DAL implementation
    /// specified from the configuration file
    /// </summary>
    public sealed class DataAccess {
        // Look up the DAL implementation we should be using
        private static readonly string path = ConfigurationManager.AppSettings["WebDAL"];
      
        //Return interface of
IProduct
        public static PetShop.IDAL.IProduct CreateProduct() {
            string className = path + ".Product";
            return (PetShop.IDAL.IProduct)Assembly.Load(path).CreateInstance(className);
        }
    }
}
在web.config裏面讀取的配置文件(該配置文件設置了用戶使用的數據庫的類型):
    <appSettings>
        <!-- Pet Shop DAL configuration settings. Possible values: PetShop.SQLServerDAL for                     SqlServer, PetShop.OracleServerDALfor Oracle. -->
        <add key="WebDAL" value="PetShop.SQLServerDAL"/>
        <add key="OrdersDAL" value="PetShop.SQLServerDAL"/>
        <add key="ProfileDAL" value="PetShop.SQLProfileDAL"/>
   
<appSettings>
在上面的程序片段中,

private static readonly string path = ConfigurationManager.AppSettings["WebDAL"];

該句用來在配置文件中讀取AppSettings節點的值,該配置文件說明了系統應該使用的是那一個數據庫(SQLServer或者Oracle或者其它),讀取的過程是通過讀取key,然後的到該key所對應的value。

該句:
public static PetShop.IDAL.IProduct CreateProduct() {
            string className = path + ".Product";
            return (PetShop.IDAL.IProduct)Assembly.Load(path).CreateInstance(className);
        }
是問題的關鍵之處,通過該方法最終返回了
IProduct接口類型。
但是
該句 string className = path + ".Product"; 返回了 PetShop.SQLServerDAL.Product對象
然後使用:
Assembly.Load(PetShop.SQLServerDAL).CreateInstance(PetShop.SQLServerDAL.Product);利用反射特性動態加載PetShop.SQLServerDAL.dll,同時創建了PetShop.SQLServerDAL.Product對象的實例,最終以接口PetShop.IDAL.IProduct類型返回。
當BLL層調用數據訪問層的時候,通過IDAL接口使用了
類PetShop.SQLServerDAL.Product,進而去使用了該類的代碼,本質還是調用了對象的實例,不像簡單的New一個對象的實例,如果使用 Product aProduct=new Product(); 則表示沒有完全將對象完全分離,對象和對象的實例化有着太強的聯繫。而使用返回接口的方法,很好的將實例化和對象之間的關係弱化,達到了較好的分離。

然後看一下BLL層是如何調用IDAL層的:
BLL層的代碼:
using System.Collections.Generic;
using PetShop.Model;
using PetShop.IDAL;

namespace PetShop.BLL {

    /// <summary>
    /// A business component to manage products
    /// </summary>
    public class Product {

        // Get an instance of the Product DAL using the DALFactory
        // Making this static will cache the DAL instance after the initial load
        private static readonly IProduct dal = PetShop.DALFactory.DataAccess.CreateProduct();   
        /// <summary>
        /// Query for a product
        /// </summary>
        /// <param name="productId">Product Id</param>
        /// <returns>ProductInfo object for requested product</returns>
        public ProductInfo GetProduct(string productId) {
           
            // Return empty product if the string is empty
            if(string.IsNullOrEmpty(productId))
                return new ProductInfo();

            // Get the product from the data store
            return dal.GetProduct(productId);
        }
    }
}
使用該句:
private static readonly IProduct dal = PetShop.DALFactory.DataAccess.CreateProduct();
使用工廠得到
Product DAL的一個實例化的對象,然後通過該對象去調用相應的方法,如下:
dal.GetProduct(productId);
這樣,BLL層就可以直接調用DAL層的接口完成對數據庫的操作,但是BLL層並不知道它操作的數據庫是那個數據庫,而這些都是由DAL Factory去實現的,因此BLL層只管去調用接口,而對底層訪問數據庫的實現細節一概不知,如果BLL層有較大的變動,基本是不會影響到DAL層的具 體實現的,即使底層有變動,也很少會影響到上一層的業務細節,因此,層與層之間得到了較好的分離。
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章