PetShop4.0採用了三層的架構,表現層、業務邏輯層和數據層。
分層的優勢:
1、使得各層相互獨立,減少依賴性
2、方便開發人員職責分離,僅僅負責其中的某一塊,而不用去考慮其它實現
3、方便管理和維護,其中一處的改動不會影響到其它的層
4、方便邏輯的複用
不足:
1、如果有新的功能加入到系統中,在自下而上的方法中,各個層都需要添加新的代碼,小系統一般不會有太大的工作量,但是大系統往往比較麻煩
2、本來可以直接操作數據庫完成對數據庫的操作,但是由於分層,系統性能受到了一定的影響,對小的應用,還是使用不分層來實現對數據庫的直接操作,可以取得較好的性能
3、分層之後,每層都有許多對應實現的模式,邏輯往往很抽象,給理解帶來了困難,特別對於許多沒有大型項目經驗的人
整個系統的結構如下:
系統架構簡圖
詳細的體系架構
業務邏輯層:是系統的核心層,所有的設計都是圍繞該層進行設計,因爲業務直接跟需求掛鉤,比如用戶的註冊、登錄等、用戶訂單的管理,寵物的管理等
數據訪問層:進行數據存儲並數據對象的持久化和各種操作(增、刪、改、查),因爲這層的設計比較重要,此層往往用工廠模式去實現支持不同的數據庫,因而是理解架構的關鍵部分。
現從數據訪問層入手,分析一下數據訪問底層的設計:
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層的是什麼類型的對象呢?
在實現數據訪問層的過程當中,使用了抽象工廠模式:對於用戶來說,工廠裏產品如何生產的你不用知道,僅僅只去用工廠裏生產出來的產品就可以了。
用工廠模式來實現了對SqlServer和Oracle數據庫訪問的操作,而BLL層不用知道也不用關心後臺用的是哪一種數據庫,它只要用接口就行了,接口中定義了要用的方法,當調用接口時會根據具體的情況再去調用底層數據訪問操作。
DALFactory是關鍵,當BLL層要操作數據庫時,DALFactory會根據具體情況再去使用SqlServerDAL和OracleDAL中的一個。這樣系統上層只管調用,而下層實現細節。底下的數據層的實現細節對於上層來說被隱藏了。這樣做到了分離的目標,儘量減少層與層之間的聯繫程度,保持各層的獨立性。
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層的具 體實現的,即使底層有變動,也很少會影響到上一層的業務細節,因此,層與層之間得到了較好的分離。