走向.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);
                
else
                    
return   0 ;}
        }        
}
  
  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  Name {  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  ,如需转载请自行联系原作者


相关文章
|
19天前
|
敏捷开发 Java 测试技术
「架构」模型驱动架构设计方法及其运用
本文探讨了MDA在软件开发中的应用,从需求分析到测试,使用UML建模功能需求,通过PIM设计架构,自动生成代码以减少错误。MDA提升了可维护性、可扩展性和可移植性,通过工具如Enterprise Architect和Eclipse MDT支持自动化转换。虽然有挑战,如模型创建和平台转换,但结合敏捷方法和适当工具能有效解决,从而提高开发效率和软件质量。
17 0
「架构」模型驱动架构设计方法及其运用
|
6天前
|
存储 搜索推荐 API
分层架构中的四层定位是什么
分层架构中的四层定位是什么
|
6天前
|
缓存
DDD架构的分层架构中基础层作用是什么
DDD架构的分层架构中基础层作用是什么
|
7天前
|
存储 消息中间件 Kafka
细说数据仓库分层架构
【7月更文挑战第20天】数据仓库分层架构包括缓冲层、操作数据层、明细数据层、汇总数据层和数据集市层。
|
19天前
|
前端开发 Java 关系型数据库
「架构」分层架构
**分层架构**是软件设计的关键模式,它将应用划分为独立层,如表示层、业务逻辑层和数据访问层,强调**单一职责**和**松耦合**。优点包括**代码组织**、**技术多样性**、**团队协作**和**可扩展性**,但可能带来**性能影响**和**设计复杂性**。通过定义清晰接口和合理划分层次来管理。常用技术栈涉及Web前端、后端框架、数据库、ORM和通信协议等。
18 0
|
23天前
|
缓存 前端开发 安全
DDD中的分层架构
领域驱动设计(DDD)的分层架构演进为依赖倒置的四层模型,强调关注点分离。表现层(UI)展示信息并处理用户指令,应用程序层负责用例编排,与领域层交互但不含业务逻辑。领域层承载核心业务逻辑,包含领域模型和服务,确保业务正确性。基础设施层提供技术支撑,如数据库和缓存,服务于其他层。各层解耦,实现灵活的系统架构。
|
2月前
|
开发框架 前端开发 .NET
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
118 0
|
2月前
|
开发框架 前端开发 JavaScript
JavaScript云LIS系统源码ASP.NET CORE 3.1 MVC + SQLserver + Redis医院实验室信息系统源码 医院云LIS系统源码
实验室信息系统(Laboratory Information System,缩写LIS)是一类用来处理实验室过程信息的软件,云LIS系统围绕临床,云LIS系统将与云HIS系统建立起高度的业务整合,以体现“以病人为中心”的设计理念,优化就诊流程,方便患者就医。
47 0
|
2月前
|
开发框架 前端开发 .NET
C# .NET面试系列六:ASP.NET MVC
<h2>ASP.NET MVC #### 1. MVC 中的 TempData\ViewBag\ViewData 区别? 在ASP.NET MVC中,TempData、ViewBag 和 ViewData 都是用于在控制器和视图之间传递数据的机制,但它们有一些区别。 <b>TempData:</b> 1、生命周期 ```c# TempData 的生命周期是短暂的,数据只在当前请求和下一次请求之间有效。一旦数据被读取,它就会被标记为已读,下一次请求时就会被清除。 ``` 2、用途 ```c# 主要用于在两个动作之间传递数据,例如在一个动作中设置 TempData,然后在重定向到另
184 5
|
2月前
|
开发框架 前端开发 .NET
进入ASP .net mvc的世界
进入ASP .net mvc的世界