應用程序分層與關注點分離
規划一個好的體系結構。如果基礎不牢,就無法構建可維護、伸縮的應用程序。
反模式:智能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 }
使用了設置器注入。

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 }

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 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:單個商品和一組商品的轉換

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列表。

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); } } }
程序各層間的交互和每層的責任