PetShop4研究----架构设计浅谈

来源:互联网 发布:淘宝直通车新手操作 编辑:程序博客网 时间:2024/05/16 09:17
今天终于开始研究微软对于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层的具 体实现的,即使底层有变动,也很少会影响到上一层的业务细节,因此,层与层之间得到了较好的分离。
 
原创粉丝点击