一起谈.NET技术,走向ASP.NET架构设计——第三章:分层设计,初涉架构(中篇)

简介:   1.阐明示例需求  本篇还是用之前的电子商务网站中的一个简单的场景来讲述:在页面上需要显示产品的列表信息。并且根据产品的类型不同,计算出相应的折扣。 在上篇中,我们已经设计项目的逻辑分层。我们再来回顾下:  可能有的朋友认为从Smart UI立刻跳到这种分层设计,似乎快了些。

  1.阐明示例需求

  本篇还是用之前的电子商务网站中的一个简单的场景来讲述:在页面上需要显示产品的列表信息。并且根据产品的类型不同,计算出相应的折扣。 在上篇中,我们已经设计项目的逻辑分层。我们再来回顾下:

  可能有的朋友认为从Smart UI立刻跳到这种分层设计,似乎快了些。其实也算是一个思想的跳跃吧。下面就来看看这种分层是如何解决之前Smart UI的问题的。

  2.业务层设计

  记得在之前的Smart UI的例子中,程序的业务逻辑是直接写在了ASPX页面后面的cs代码中的。现在,采用分层的方法,我们采用了领域模型来组织来电子商务中的业务逻辑。有关领域模型的一些东西,我们在后续的文章中会讲解的。注:领域模型模式被设计用来组织复杂的业务逻辑和关系。

  下面的类图就反映了我们之前的电子商务的需求中所用到的业务模型。

  Product类就代表了电子商务中的每一个产品。Price类将会包含可算折扣的业务逻辑,并且用策略模式来具体实现折扣的算法。在ASPPatterns.Chap3.Layerd.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.95M ;

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
= 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来与业务层的Service 类进行交互,业务层的Service会返回商品列表,然后我们现在添加的这个ProductService会把列表转为ProductViewModels。

  大家可能觉得奇怪:为什么这里添加了两个ProductService,之前在业务层加一个,现在又加一个,是否命名有问题或者功能重复?其实在上一篇已经提过:有时在业务层类添加一个service层,主要是用来组织业务流程的,常常要几个业务类组合在一起使用,这样主要是为了简化客户程序(也就是调用这个业务层的代码)的调用,实现类似Façade的作用。

  我们现在添加的ProductService就是业务层中service层的客户程序,因为我们调用了业务层的service,往往有时候,我们不想把自己系统的业务类的结构直接暴露给外界,如显示层,而且也希望提供更加符合显示层所需的数据结构,那么我们就添加了这个ProductService,提供从业务类到ViewModel的转换。而且在这个ProductSevice中,我们也可以实现一些异常处理机制,如果涉及到了分布式调用,那么我们还可以用这个ProductService类向显示层和UI那边隐藏分布式的信息:实现代理模式。

  今天就写在到这里,在写的过程中发现这篇有点长了,所以分为3篇(前,中,后)发布!不明白的地方大家多琢磨一下,也可以告诉我!下篇明天发布!见谅!

目录
相关文章
|
28天前
|
Kubernetes Cloud Native 微服务
探索云原生技术:容器化与微服务架构的融合之旅
本文将带领读者深入了解云原生技术的核心概念,特别是容器化和微服务架构如何相辅相成,共同构建现代软件系统。我们将通过实际代码示例,探讨如何在云平台上部署和管理微服务,以及如何使用容器编排工具来自动化这一过程。文章旨在为开发者和技术决策者提供实用的指导,帮助他们在云原生时代中更好地设计、部署和维护应用。
|
22天前
|
监控 安全 API
使用PaliGemma2构建多模态目标检测系统:从架构设计到性能优化的技术实践指南
本文详细介绍了PaliGemma2模型的微调流程及其在目标检测任务中的应用。PaliGemma2通过整合SigLIP-So400m视觉编码器与Gemma 2系列语言模型,实现了多模态数据的高效处理。文章涵盖了开发环境构建、数据集预处理、模型初始化与配置、数据加载系统实现、模型微调、推理与评估系统以及性能分析与优化策略等内容。特别强调了计算资源优化、训练过程监控和自动化优化流程的重要性,为机器学习工程师和研究人员提供了系统化的技术方案。
142 77
使用PaliGemma2构建多模态目标检测系统:从架构设计到性能优化的技术实践指南
|
28天前
|
运维 Cloud Native 持续交付
云原生技术深度探索:重塑现代IT架构的无形之力####
本文深入剖析了云原生技术的核心概念、关键技术组件及其对现代IT架构变革的深远影响。通过实例解析,揭示云原生如何促进企业实现敏捷开发、弹性伸缩与成本优化,为数字化转型提供强有力的技术支撑。不同于传统综述,本摘要直接聚焦于云原生技术的价值本质,旨在为读者构建一个宏观且具体的技术蓝图。 ####
|
1月前
|
数据库
分层架构
表现层(Presentation Layer):处理用户界面和用户交互逻辑。 业务逻辑层(Business Logic Layer):处理业务相关的逻辑和规则。 数据访问层(Data Access Layer):负责与数据库或其他数据源进行 [Something went wrong, please try again later.]。
|
2月前
|
Cloud Native 持续交付 云计算
云原生技术在现代IT架构中的转型力量####
本文深入剖析了云原生技术的精髓,探讨其在现代IT架构转型中的关键作用与实践路径。通过具体案例分析,展示了云原生如何赋能企业实现更高效的资源利用、更快的迭代速度以及更强的系统稳定性,为读者提供了一套可借鉴的实施框架与策略。 ####
26 0
|
前端开发 JavaScript .NET
一起谈.NET技术,ASP.NET MVC2实现分页和右键菜单
  右键菜单非常方便,很多时候会用到。这篇文章将使用一个JQUERY的插件在asp.net mvc中实现右键菜单。本文还将介绍一下在asp.net mvc中如何实现简单的分页。效果如下图:   首先,下载此插件。
1015 1
|
前端开发 NoSQL .NET
一起谈.NET技术,重构TekPub——从ASP.NET MVC框架迁移到Ruby on Rails
  TekPub是一个面向开发人员的站点,致力于为开发人员提供一系列主题的在线培训,主题范围非常广泛,从微软的O/R Mapping框架Microsoft Entity Framework,到如何使用Ruby on Rails技术编写自己的日志引擎等内容都有涉及。
1642 0
|
Web App开发 SQL 前端开发
一起谈.NET技术,鲜为人知的ASP.NET MVC 2.0框架高效之谜
  要想建立开发环境,你需要安装Visual Studio 2008/2010 Beta 2,以及SQL Express 2005(可免费从MSDN下载)和MVC 2.0框架。我把本文中的示例Web应用命名为“Employee Master Information”。
1018 0
|
存储 缓存 .NET
一起谈.NET技术,提高ASP.NET应用程序性能的十大方法
  一、返回多个数据集   检查你的访问数据库的代码,看是否存在着要返回多次的请求。每次往返降低了你的应用程序的每秒能够响应请求的次数。通过在单个数据库请求中返回多个结果集,可以减少与数据库通信的时间,使你的系统具有扩展性,也可以减少数据库服务器响应请求的工作量。
1233 0
|
.NET
一起谈.NET技术,ASP.NET 4.0 一些隐性的扩展
  ASP.NET 4.0在很多方面都做了改进,在这篇ASP.NET 4.0白皮书就描述了很多ASP.NET 4.0的机制改变和改进。在我的博客中,也有几篇关于ASP.NET4.0的特性修改的文章。但是作为一个全新的框架和运行时,内部肯定还会有很多API和扩展点不会暴露的那么明显。
852 0

热门文章

最新文章