天天看點

走向.NET架構設計—第三章—分層設計,初涉架構(中篇)

走向.NET架構設計—第三章—分層設計,初涉架構(中篇) 

  前言:自從上篇釋出以後,大家回報了不少問題,因為前篇講的東西不是很深,可能大家看完之後沒有什麼感覺.本章(前篇,中篇,後篇)的主要目的其實首先是提出不好的設計,然後對比的提出一個相對比較合理的分層架構,同時本篇也為後續講述架構模式和設計模式等的文章做個鋪墊。

本篇的議題如下: 1. 闡明示例需求 2. 業務層設計 3. 服務層設計 4. 資料通路層設計 5. 顯示層設計 6. UI層設計

   

  1. 闡明示例需求

  本篇還是用之前的電子商務網站中的一個簡單的場景來講述:在頁面上需要顯示産品的清單資訊。并且根據産品的類型不同,計算出相應的折扣。 

  在上篇中,我們已經設計項目的邏輯分層。我們再來回顧下:

可能有的朋友認為從Smart UI立刻跳到這種分層設計,似乎快了些。其實也算是一個思想的跳躍吧。下面就來看看這種分層是如何解決之前Smart UI的問題的。 

  2.  業務層設計

  記得在之前的Smart UI的例子中,程式的業務邏輯是直接寫在了ASPX頁面後面的cs代碼中的。現在,采用分層的方法,我們采用了領域模型來組織來電子商務中的業務邏輯。

  有關領域模型的一些東西,我們在後續的文章中會講解的。

  注:領域模型模式被設計用來組織複雜的業務邏輯和關系。

  下面的類圖就反映了我們之前的電子商務的需求中所用到的業務模型。

  Product類就代表了電子商務中的每一個産品。

  Price類将會包含可算折扣的業務邏輯,并且用政策模式來具體實作折扣的算法-。

  在Model添加一個接口類:IDiscountStrategy:

  

public interface IDiscountStrategy

{

        decimal ApplyExtraDiscountsTo(decimal OriginalSalePrice);

}

  這個接口就用來實作不同打折的政策,這是政策模式的一種應用。這個模式允許我們在運作的時候更改不同的算法實作。在本例子中,Price類将會根據不同的産品來實作不同的打折政策。在我們之前的那個Smart UI例子中,其實這個打折的算法我們已經寫了,但是沒有分離出來,導緻了每次加一個打折的算法的政策,程式就需要改動,重新編譯,部署。也就是說打折的部分是個變化點,我們應該分離出來的。 

注:政策模式:用一個類來封裝一個算法的實作,并且通過切換算法的實作允許在運作時修改一個對象的行為。

在電子商務中,不是每種商品都會打折的,其實我們要實作的打折政策隻有一種。但是如果這樣,我們在寫代碼的時候就要if-else判斷是否是打折的商品,其實這裡還是暴露了變化點的:如果國慶那天,所有的商品都打折了,那麼我們就得修改代碼。其實我們可以這樣想想:不打折的情況也算是一種打折,其他的商品打折可能是7折,不打折的情況就是10折。 

代碼

 public class TradeDiscountStrategy : IDiscountStrategy 

{        

        public decimal ApplyExtraDiscountsTo(decimal OriginalSalePrice)

        {

            decimal price = OriginalSalePrice;            

            price = price * 0.6M;            

            return price;

        }     

public class NullDiscountStrategy : IDiscountStrategy 

        public decimal ApplyExtraDiscountsTo(decimal OriginalSalePrice)

        {

            return OriginalSalePrice;

        }

下面我們來看看Price類的實作。

public class Price

        private IDiscountStrategy _discountStrategy = new NullDiscountStrategy(); 

        private decimal _rrp;

        private decimal _sellingPrice;

        public Price(decimal RRP, decimal SellingPrice)

            _rrp = RRP;

            _sellingPrice = SellingPrice;

        }

        public void SetDiscountStrategyTo(IDiscountStrategy DiscountStrategy)

            _discountStrategy = DiscountStrategy; 

        public decimal SellingPrice

            get { return _discountStrategy.ApplyExtraDiscountsTo(_sellingPrice); }

        public decimal RRP

            get { return _rrp; }

        public decimal Discount

            get { 

                if (RRP > SellingPrice) 

                    return (RRP - SellingPrice); 

                else

                    return 0;}

        public decimal Savings

            get{

                if (RRP > SellingPrice)

                    return 1 - (SellingPrice / RRP);

        }        

  Price類在設計中就是用了“依賴倒置原則”,因為它沒有采用某一個具體的打折實作算法,而且依賴于接口抽象,至于之後到底會哪種的打折算法,其實是由商品的類型來決定的。 

  我們還是繼續的看,現在看看Product類。

public class Product

        public int Id { get; set; }

        public string Name { get; set; }

        public Price Price { get; set; }

         現在所有的業務實體就已經建立了。至于對商品是否打折,其實這是由客戶代碼來決定:根據客戶代碼傳入的商品的類型不同,然後調用不同的政策,選擇了不同的打折算法計算折扣。是以我們這裡來添加一個表示商品類型的枚舉:  

 public enum CustomerType

 {

        Standard = 0,

        Trade = 1

 }

  我們将會把選擇哪種打折的政策的邏輯寫在一個單獨的地方,也就是說:隻要客戶代碼傳入相應的參數資訊,我們就自動的建立一個合适的打折政策對象。很明顯,這裡可以采用工廠方法來實作,如下:  

public static class DiscountFactory

        public static IDiscountStrategy GetDiscountStrategyFor(CustomerType customerType)

            switch (customerType)

            {

                case CustomerType.Trade:

                    return new TradeDiscountStrategy(); 

                default:

                    return new NullDiscountStrategy(); 

            }

  在上面的邏輯分層中,我們建立了一個Repository的類庫,其實我們就是想采用Repository模式來實作”持久化無關性”-----業務類完全不用管如何儲存和擷取資料。而且由Repository決定資料的來源和儲存的地方,可能是資料庫,也可能就是記憶體,但是不管怎麼,業務類是不用管這些的。是以下面用一個接口來實作靈活性:  

 public interface IProductRepository

        IList<Product> FindAll();

  如果現在有很多的商品,我們想知道他們的折扣價格,最簡單的方法就是周遊他們,判斷類型,然後應用不同的打折政策。為了更加的可讀,我們可以為商品清單建立擴充方法,如下:

 public static class ProductListExtensionMethods

      public static void Apply(this IList<Product> products, IDiscountStrategy discountStrategy)

      {

            foreach (Product p in products)

                p.Price.SetDiscountStrategyTo(discountStrategy);

      }

  為了簡化客戶代碼的調用工作,我們提供一個類似門戶(gateway),或者是Façade的概念:把複雜的操作邏輯隐藏,留給客戶代碼一個簡單易用的API。我們這裡建立一個Service類,如下:

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;

    }    

  隻要客戶代碼(如顯示層中的代碼)直接調用上面的方法就可以了,而且商品的折扣也根據傳入的商品類型不同來計算。

  3.       服務層設計

  服務層就充當應用程式的入口的角色。有時候,可以被認為是façade.不僅如此,因為service分為領域邏輯的service和門戶的service。門戶的service常常為顯示層提供強類型的View Model(有時也稱為Presentation Model)。 一個View Model就是給一個專門的View來使用的。在本例中,我們将會建立Product的View Model來顯示商品的資訊。一般情況下,我們不要把業務類直接暴露給顯示層,這樣很容易緊耦合,是以在中間就上一個View Model,其實View Model和業務類的結構差不多,隻是View Model做了一些調整,便于最後的顯示。關于View Model詳細的,後文講述。

  注:Façade模式:為内部負責的子系統提供一個簡單的接口供外部通路。

  下面我們就來看看Product的View Model是如何寫的:

public class ProductViewModel

    {

        public int ProductId { get; set; }

        public string RRP { get; set; }

        public string SellingPrice { get; set; }

        public string Discount { get; set; }

        public string Savings { get; set; }

    }

  可以看到,其實View Model就是做了一些顯示邏輯的處理。在這裡就是多加了一些字段,這些字段就是在UI的GridView中顯示用的。我們之前的Smart UI的方法中,還建立了模闆列來顯示Product類中沒有的字段,其實就相當于在UI中作了一定的顯示邏輯的處理。這裡我們直接顯示ViewModel.

  大家應該很熟悉Web Service:在用戶端和服務使用請求/響應的消息機制進行通信的。我們這裡的客戶代碼和Service也采用這種方法,因為很有可能我們在部署的時候Service的代碼和客戶代碼(顯示層)在不同機器上。

  請求的消息的結構如下:  

 public class ProductListRequest

        public CustomerType CustomerType { get; set; }

  服務在響應請求的時候也要定義格式,而且我們可以在響應中加入更多的屬性來判斷這個請求是否成功。是以在下面的代碼中,我們加入了Message屬性,用來在請求失敗的時候顯示錯誤資訊,還添加了一個Success屬性用來判斷請求的狀态:  

public class ProductListResponse

   public bool Success { get; set; }

   public string Message { get; set; }

   public IList<ProductViewModel> Products { get; set; }

  還有一點不要忘記了:因為Product和它對應的View Model結構不同的,而Service傳回的又是ViewModel的響應,那麼就需要把擷取到的Product轉換為View Model的結構。可以把轉換的代碼寫在一個特定的地方(可以認為是個Mapping的過程),為了閱讀的友善,我們可以為List<Product>添加擴充方法,直接調用,如下:

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 =product.Price.RRP;

            productViewModel.SellingPrice =product.Price.SellingPrice;

            if (product.Price.Discount > 0)

                productViewModel.Discount = product.Price.Discount;

            if (product.Price.Savings < 1 && product.Price.Savings > 0)

                productViewModel.Savings = product.Price.Savings.ToString("#%");

            return productViewModel;

  最後,我們加入一個ProductService來與業務層的Service 類進行互動,業務層的Service會傳回商品清單,然後我們現在添加的這個ProductService會把清單轉為ProductViewModels。

大家可能覺得奇怪:為什麼這裡添加了兩個ProductService,之前在業務層加一個,現在又加一個,是否命名有問題或者功能重複?其實在上一篇已經提過:有時在業務層類添加一個service層,主要是用來組織業務流程的,常常要幾個業務類組合在一起使用,這樣主要是為了簡化客戶程式(也就是調用這個業務層的代碼)的調用,實作類似Façade的作用。

我們現在添加的ProductService就是業務層中service層的客戶程式,因為我們調用了業務層的service,往往有時候,我們不想把自己系統的業務類的結構直接暴露給外界,如顯示層,而且也希望提供更加符合顯示層所需的資料結構,那麼我們就添加了這個ProductService,提供從業務類到ViewModel的轉換。而且在這個ProductSevice中,我們也可以實作一些異常處理機制,如果涉及到了分布式調用,那麼我們還可以用這個ProductService類向顯示層和UI那邊隐藏分布式的資訊:實作代理模式。

今天就寫在到這裡,在寫的過程中發現這篇有點長了,是以分為3篇(前,中,後)釋出!不明白的地方大家多琢磨一下,也可以告訴我!下篇明天釋出!見諒!

本文轉自yanyangtian51CTO部落格,原文連結:http://blog.51cto.com/yanyangtian/411766 ,如需轉載請自行聯系原作者