代码改变世界

ASP.NET 设计模式 读书摘记3

2013-03-10 16:51  Hejin.Wong  阅读(1170)  评论(8编辑  收藏  举报

应用程序分层与关注点分离

规划一个好的体系结构。如果基础不牢,就无法构建可维护、伸缩的应用程序。

反模式:智能UI
微软的RAD(Rapid application development)开发工具Visual Studio.NET快速开发表单式Web应用程序。通过简单的拖曳和所见即所得的应用程序设计界面。非常适用于原型设计 、一次性短期的应用程序。但临时应用程序往往会被修改作为构建基础,最终成为难以维护的关键任务应用程序。
code-behind包含了应用程序的事件处理、数据访问以及业务逻辑。所有的关注点混杂在一起,导致业务逻辑重复。

分离关注点
应用程序分层是分类关注点的一种形式。可以通过命名空间、文件夹或采用独立的项目来实现。

解决方案目录结构:

 

1.业务层(ASPPatterns.Chap3.Layered.Model):


Domain Model模式专门用来组织复杂业务逻辑和关系。


Strategy模式将算法封装到一个类中,并可以在运行中转换,从而改变对象的行为。

    /// <summary>
    /// 匹配Strategy设计模式
    /// </summary>
    public interface IDiscountStrategy
    {
        decimal ApplyExtraDiscountsTo(decimal OriginalSalePrice);
    }

    public class NullDiscountStrategy : IDiscountStrategy
    {
        public decimal ApplyExtraDiscountsTo(decimal OriginalSalePrice)
        {
            return OriginalSalePrice;
        }
    }

    public class TradeDiscountStrategy : IDiscountStrategy
    {
        public decimal ApplyExtraDiscountsTo(decimal OriginalSalePrice)
        {
            decimal price = OriginalSalePrice;

            price = price * 0.95M;

            return price;
        }
    }

    public enum CustomerType
    {
        Standard = 0,
        Trade = 1
    }

 

使用了设置器注入。

Class Price
 1     public class Price
 2     {
 3         private IDiscountStrategy _discountStrategy = new NullDiscountStrategy(); 
 4         private decimal _rrp;
 5         private decimal _sellingPrice;
 6 
 7         public Price(decimal RRP, decimal SellingPrice)
 8         {
 9             _rrp = RRP;
10             _sellingPrice = SellingPrice;
11         }
12 
13         public void SetDiscountStrategyTo(IDiscountStrategy DiscountStrategy)
14         {
15             _discountStrategy = DiscountStrategy; 
16         }
17 
18         public decimal SellingPrice
19         {
20             get { return _discountStrategy.ApplyExtraDiscountsTo(_sellingPrice); }
21         }
22 
23         public decimal RRP
24         {
25             get { return _rrp; }
26         }
27 
28         public decimal Discount
29         {
30             get { 
31                 if (RRP > SellingPrice) 
32                     return (RRP - SellingPrice); 
33                 else
34                     return 0;}
35         }
36 
37         public decimal Savings
38         {
39             get{
40                 if (RRP > SellingPrice)
41                     return 1 - (SellingPrice / RRP);
42                 else
43                     return 0;}
44         }        
45     }

 

class Product
1     public class Product
2     {
3         public int Id { get; set; }
4         public string Name { get; set; }
5         public Price Price { get; set; }
6     }

为给定的CustomerType返回一个匹配的折扣策略。

Factory模式可以让类来委托创建有效对象的责任。

    public static class DiscountFactory
    {
        public static IDiscountStrategy GetDiscountStrategyFor(CustomerType customerType)
        {
            switch (customerType)
            {
                case CustomerType.Trade:
                    return new TradeDiscountStrategy(); 
                default:
                    return new NullDiscountStrategy(); 
            }
        }
    }

 

服务层与数据存储交互,以检索商品。使用Repository模式实现该功能。,但只能指定资源库接口,不希望model牵涉到具体实现(什么数据库 具体查询语句)

Repository模式充当业务实体的内存集合或仓库。完全将底层数据此基础设施抽象出来。

    public interface IProductRepository
    {
        IList<Product> FindAll();
    }

 

将给定的折扣策略应用到一组商品。可以创建一个自定义集合来实现该功能。也可以使用扩展方法更灵活。

Separated Interface(独立接口)模式要求将接口放在一个独立于具体实现的程序集或命名空间中。这确保客户端完全不知道具体实现,而且能够遵循面向抽象编程(而不是面向实现)以及依赖倒置原则。

    public static class ProductListExtensionMethods
    {
        public static void Apply(this IList<Product> products, IDiscountStrategy discountStrategy)
        {
            foreach (Product p in products)
            {
                p.Price.SetDiscountStrategyTo(discountStrategy);
            }
        }
    }

 

客户端用来与领域交互的服务类。

    public class ProductService
    {
        private IProductRepository _productRepository;

        public ProductService(IProductRepository productRepository)
        {
            _productRepository = productRepository;
        }

        public IList<Product> GetAllProductsFor(CustomerType customerType)
        {
            IDiscountStrategy discountStrategy = DiscountFactory.GetDiscountStrategyFor(customerType);
            IList<Product> products = _productRepository.FindAll();

            products.Apply(discountStrategy);

            return products;
        }    
    }

 

业务层没有绑定到特定的数据存储,使用接口对资源库进行访问来完成所有的持久化需要。业务层不会受其他层变化的影响。

 

2.服务层(ASPPatterns.Chap3.Layered.Service)

服务层是应用程序的入口。服务层为表示层提供了强类型视图模型,称为表示模型。视图模型是为特定视图优化的强类型的类,并包含用来辅助完成数据表示的逻辑。

Facade模式为一系列复杂的接口和子系统提供了一个简单的接口并控制对其的访问。

View Code
View Code 
     public class ProductViewModel
     {
         public int ProductId { get; set; }
         public string Name { get; set; }
         public string RRP { get; set; }
         public string SellingPrice { get; set; }
         public string Discount { get; set; }
         public string Savings { get; set; }
     }
     /// <summary>
     /// 客户端与服务层交互 使用Request/Response消息模式。Request部分客户端提供。
     /// </summary>
     public class ProductListRequest
     {
         public CustomerType CustomerType { get; set; }
     }
 
     public class ProductListResponse
     {
         public bool Success { get; set; }
         public string Message { get; set; }
         public IList<ProductViewModel> Products { get; set; }
     }

将Product实体转换成ProductViewModel:单个商品和一组商品的转换

View Code
    public static class ProductMapperExtensionMethods
    {
        public static IList<ProductViewModel> ConvertToProductListViewModel(this IList<Model.Product> products)
        {
            IList<ProductViewModel> productViewModels = new List<ProductViewModel>();

            foreach(Model.Product p in products)
            {
                productViewModels.Add(p.ConvertToProductViewModel());  
            }

            return productViewModels;
        }

        public static ProductViewModel ConvertToProductViewModel(this Model.Product product)
        { 
            ProductViewModel productViewModel = new ProductViewModel();
            productViewModel.ProductId = product.Id;
            productViewModel.Name = product.Name;
            productViewModel.RRP = String.Format("{0:C}", product.Price.RRP);
            productViewModel.SellingPrice = String.Format("{0:C}", product.Price.SellingPrice);
            
            if (product.Price.Discount > 0)
                productViewModel.Discount = String.Format("{0:C}", product.Price.Discount);

            if (product.Price.Savings < 1 && product.Price.Savings > 0)
                productViewModel.Savings = product.Price.Savings.ToString("#%");

            return productViewModel;
        }
    }

 ProductService类,将与领域模型服务交互,以检索商品列表;然后将其转换成ProductViewModels列表。

ProductService
    public class ProductService
    {
        private Model.ProductService _productService;

        public ProductService(Model.ProductService ProductService)
        {
            _productService = ProductService;
        }

        public ProductListResponse GetAllProductsFor(ProductListRequest productListRequest)
        {
            ProductListResponse productListResponse = new ProductListResponse();

            try
            {
                IList<Model.Product> productEntities = _productService.GetAllProductsFor(productListRequest.CustomerType);

                productListResponse.Products = productEntities.ConvertToProductListViewModel();
                productListResponse.Success = true;
            }
            catch (System.Data.SqlClient.SqlException ex)
            {
                // Log the exception...

                productListResponse.Success = false;
                // Return a friendly error message
                productListResponse.Message = "Check that your database is in the correct place. Hint: Check the AttachDbFilename section within App.config in the project ASPPatterns.Chap3.Layered.Repository.";
            }
            catch (Exception ex)
            {
                // Log the exception...

                productListResponse.Success = false;
                // Return a friendly error message
                productListResponse.Message = "An error occured";
            }

            return productListResponse;
        }

3.数据访问层(ASPPatterns.Chap3.Layered.Repository)

IProductRepository是在业务层(Model项目中创建)定义的接口。

    public class ProductRepository : IProductRepository 
    {        
        public IList<Model.Product> FindAll()
        {
            var products = from p in new ShopDataContext().Products
                           select new Model.Product
                               {
                                   Id = p.ProductId, 
                                   Name = p.ProductName,                                   
                                   Price = new Model.Price(p.RRP, p.SellingPrice)                                                                        
                               };            

            return products.ToList();
        }
     
    }

4.表示层(ASPPatterns.Chap3.Layered.Presentation)

将表示逻辑与用户体验(用户界面)分离,采用Model-View-Presenter(模式-视图-呈现器)模式。(MVP模式

表示层的好处:很容易测试数据的表示以及用户和系统之间的交互,而不用担心难以测试的Web表单。还可以在应用程序之上添加任何形式的用户体验(WPF Winform Web表单应用)

    //该接口由ASPX Web表单实现。通过使用接口,可以在测试时将试图分离出来。
    public interface IProductListView
    {
        void Display(IList<ProductViewModel> Products);
        Model.CustomerType CustomerType { get; }
        string ErrorMessage { set; }
    }
    //呈现器负责获取数据、处理用户事件并通过视图的接口更新视图。
    public class ProductListPresenter
    {
        private IProductListView _productListView;
        private Service.ProductService _productService;

        public ProductListPresenter(IProductListView ProductListView, Service.ProductService ProductService)
        {
            _productService = ProductService;
            _productListView = ProductListView;
        }

        public void Display()
        {
            ProductListRequest productListRequest = new ProductListRequest();
            productListRequest.CustomerType = _productListView.CustomerType;

            ProductListResponse productResponse = _productService.GetAllProductsFor(productListRequest);

            if (productResponse.Success)
            {
                _productListView.Display(productResponse.Products);
            }
            else
            {
                _productListView.ErrorMessage = productResponse.Message;
            }

        }
    }

 5.用户体验层(ASPPatterns.Chap3.Layered.WebUI)

使用了IoC容器 StructureMap

    /// <summary>
    /// 向StructureMap注册所有的具体依赖类。当客户端代码使用StructureMap来解析某个类时,StructureMap检查该类的依赖类,并根据选中的具体实现(ProductRegistry指定)自动注入这些依赖类。
    /// </summary>
    public class BootStrapper
    {
        public static void ConfigureStructureMap()
        {            
            ObjectFactory.Initialize(x =>
            {
                x.AddRegistry<ProductRegistry>();

            });
        }   
    }

    public class ProductRegistry : Registry
    {
        public ProductRegistry()
        {
            ForRequestedType<IProductRepository>().TheDefaultIsConcreteType<ProductRepository>();            
        }

    }

应用程序启动时需要运行ConfigureStructureMap方法。添加Global.asax。

   public class Global : System.Web.HttpApplication
    {

        protected void Application_Start(object sender, EventArgs e)
        {
            BootStrapper.ConfigureStructureMap();
        }

    }

default.aspx页面

代码视图
       <asp:DropDownList AutoPostBack="true" ID="ddlCustomerType" runat="server" >
            <asp:ListItem Value="0">Standard</asp:ListItem> 
            <asp:ListItem Value="1">Trade</asp:ListItem> 
        </asp:DropDownList>
                
        <asp:Label ID="lblErrorMessage" runat="server" ></asp:Label> 
    
        <asp:Repeater ID="rptProducts" runat="server" >
            <HeaderTemplate>
                <table>
                    <tr>
                        <td>Name</td>
                        <td>RRP</td>
                        <td>Selling Price</td>
                        <td>Discount</td>
                        <td>Savings</td>
                    </tr>
                    <tr>
                        <td colspan="5"><hr /></td>
                    </tr>
            </HeaderTemplate> 
            <ItemTemplate>
                    <tr>
                        <td><%# Eval("Name") %></td>
                        <td><%# Eval("RRP")%></td>
                        <td><%# Eval("SellingPrice") %></td>
                        <td><%# Eval("Discount") %></td>
                        <td><%# Eval("Savings") %></td>
                    </tr>
            </ItemTemplate> 
            <FooterTemplate>
                </table>
            </FooterTemplate> 
        </asp:Repeater>

后台代码

    public partial class _Default : System.Web.UI.Page, IProductListView 
    {
        private ProductListPresenter _presenter;

        protected void Page_Init(object sender, EventArgs e)
        {
            _presenter = new ProductListPresenter(this, ObjectFactory.GetInstance<Service.ProductService>());
            this.ddlCustomerType.SelectedIndexChanged += delegate { _presenter.Display();};
        }


        protected void Page_Load(object sender, EventArgs e)
        {
            if (Page.IsPostBack != true) 
                _presenter.Display(); 
        }
        
        public void Display(IList<ProductViewModel> Products)
        {
            rptProducts.DataSource = Products;
            rptProducts.DataBind(); 
        }

        public CustomerType CustomerType
        {
            get { return (CustomerType)Enum.ToObject(typeof(CustomerType), int.Parse(this.ddlCustomerType.SelectedValue) ); }
        }
   
        
        public string ErrorMessage
        {
            set { lblErrorMessage.Text = String.Format("<p><strong>Error</strong><br/>{0}<p/>", value); }
        }
        
    }

 

 

 程序各层间的交互和每层的责任