• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

Asp.net设计模式笔记之二:应用程序分离与关注点分离

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

本次笔记主要涉及的内容如下:

1.将智能UI(SmartUI)反模式重构成分层方式的示例代码

2.分层设计与传统的Asp.net WebForm模型(代码后植)相比具有的优势

3.逻辑分层概念以及分离应用程序关注点

4.企业级Asp.net应用程序中各个不同层的责任

 

一、智能UI反模式

asp.net WebForm模型使得我们能够随意将控件拖放到设计页面上,然后进行代码书写。而其本身则使用代码后植技术,使得后台代码和前台代码分离开。后台代码包含了应用程序的所有事件处理,数据访问以及业务逻辑等,这样就会造成一种混乱:所有的关注点混杂在一起,这会引发测试问题并导致业务逻辑重复,同时也会造成代码无法重用等问题。原因就是,我们很难重用内在绑定到特定视图(ASPX页面)的逻辑。

不过Asp.net WebForm模型也有他好的一面,那就是,它非常适合原型设计以及一次性或者短期的应用程序。但问题是,那些成功的临时应用程序都有可能被后续的开发和维护,这样慢慢的就会发展成难以维护的关键任务应用程序。

为了演示这种模式,构建一个使用网格来显示商品页面,该页面将列出出售的商品,显示他们的名称,推荐零售价格,零售价格,折扣以及节省百分比。接下来的我们有个扩展需求,就是,过节了,我们会在页面新增一个全体商品打95折的选项,藉以此来看看Asp.net WebForm的扩展性。

首先创建一个名称为0620.DaemonPattern.Web的项目,然后在项目上右击,添加一个Sql Server数据库文件,名称为 Shop.mdf。

然后添加如下字段,之后随便添加一些内容:

最后将这个数据库中的表拖放到Default.aspx页面中,VS会自动给你创建一个GridView并包含数据库连接的可用数据显示列表。

然后我们在后台添加如下内容,以便于展示需求一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
       {
           if(e.Row.RowType==DataControlRowType.DataRow)
           {
               decimal recommandPrice = decimal.Parse(((DataRowView)e.Row.DataItem)["RecommandPrice"].ToString());
               decimal sellingPrice = decimal.Parse(((DataRowView)e.Row.DataItem)["SellingPrice"].ToString());
 
               Label lblDiscount = (Label)e.Row.FindControl("lblDiscount");
               Label lblSavings = (Label)e.Row.FindControl("lblSavings");
 
               lblSavings.Text = DisplaySavings(recommandPrice,sellingPrice);
               lblDiscount.Text = DisplayDiscount(recommandPrice, sellingPrice);
           }
       }
 
       protected string DisplayDiscount(decimal recommandPrice, decimal sellingPrice)
       {
           string discountText = string.Empty;
           if (recommandPrice > sellingPrice)
               discountText = string.Format("{0:C}", (recommandPrice - sellingPrice));
           return discountText;
       }
 
       protected string DisplaySavings(decimal recommandPrice,decimal sellingPrice)
       {
           string savingText = string.Empty;
           if (recommandPrice > sellingPrice)
               savingText = (1 - (sellingPrice / recommandPrice)).ToString("#%");
           return savingText;
       }

 

得到的页面如下:

现在我们做好了我们的工作,我们的商品列表拥有了折扣和节省选项,貌似一切都完结了。

但是,十一国庆节来了,这段时间游客数量大增,我们得加一些促销手段让游客们多买我们的东西,于是决定将所有产品打95折销售,那么,我们就需要拿现在的代码开刀了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
       {
           if(e.Row.RowType==DataControlRowType.DataRow)
           {
               decimal recommandPrice = decimal.Parse(((DataRowView)e.Row.DataItem)["RecommandPrice"].ToString());
               decimal sellingPrice = decimal.Parse(((DataRowView)e.Row.DataItem)["SellingPrice"].ToString());
 
               Label lblSellingPrice = (Label)e.Row.FindControl("lblSellingPrice");
               Label lblDiscount = (Label)e.Row.FindControl("lblDiscount");
               Label lblSavings = (Label)e.Row.FindControl("lblSavings");
 
               lblSavings.Text = DisplaySavings(recommandPrice, ApplyExtraDiscountsTo(sellingPrice));
               lblDiscount.Text = DisplayDiscount(recommandPrice, ApplyExtraDiscountsTo(sellingPrice));
               lblSellingPrice.Text = string.Format("{0:C}", ApplyExtraDiscountsTo(sellingPrice));
 
           }
       }
 
       protected decimal ApplyExtraDiscountsTo(decimal originalSellingPrice)
       {
           decimal price = originalSellingPrice;
           int discountType = Int16.Parse(this.ddlDiscountType.SelectedValue);
           if (discountType == 1)
           {
               price = price * 0.95M;
           }
           return price;
       }
 
       protected string DisplayDiscount(decimal recommandPrice, decimal sellingPrice)
       {
           string discountText = string.Empty;
           if (recommandPrice > sellingPrice)
               discountText = string.Format("{0:C}", (recommandPrice - sellingPrice));
           return discountText;
       }
 
       protected string DisplaySavings(decimal recommandPrice,decimal sellingPrice)
       {
           string savingText = string.Empty;
           if (recommandPrice > sellingPrice)
               savingText = (1 - (sellingPrice / recommandPrice)).ToString("#%");
           return savingText;
       }
 
       protected void ddlDiscountType_SelectedIndexChanged(object sender, EventArgs e)
       {
           GridView1.DataBind();
       }

 

得到的结果如下图所示:

现在,你能看出来我们的问题所在了吗?当然,如果这个程序很小,我们不会遇到什么问题,但是当程序很大的时候,我们就不得不触动之前的业务逻辑部分,在修改过程中,我们不知道我们会引入什么样的Bug,这,理所当然的违反了封闭开放原则,并且不利于项目的后续开发和维护。

所以,如果想要应对这种智能UI的反模式,最好的办法就是对应用程序分层。应用程序分层属于分离关注点的一种形式。可以通过命名空间,文件夹或采用单独的项目来实现。下图给出了企业级分层设计的Asp.net应用程序的典型体系结构:

领域模型和领域服务(聚合根)是整个应用程序的中心点,其上是Application Service,再上则是Presentation层,最上面则是User Interface交互层。

为了演示分层设计的效果和好处,我们将对上面的项目进行重构

首先,我们创建项目框架:

其引用关系为:

Repository引用Model

Service引用Repository和Model

Presentation引用Service和Model

WebUI引用Repository,Model,Service和Presentation

下面来讲解下各层的关注点:

1.业务层

在之前的智能UI反模式中,业务逻辑与表示逻辑混为一谈。但是本层的关注点则是业务逻辑,将不会映入任何的表示逻辑。简而言之,可以将其看做是一个包括了所有相关实体以及关系的系统概念模型(这里不得不提到Domain Model模式的概念,它主要是用来组织复杂的业务逻辑和关系)。

下面,我们将在0620.DaemonPattern.Model层中创建领域模型。向本层中添加名为 IDiscountStrategy的新接口,定义如下:

1
2
3
4
public class IDiscountStrategy
   {
       decimal ApplyExtraDiscountTo(decimal originalSellingPrice);
   }

之所以将接口命名为IDiscountStrategy是因为它实际上会匹配Strategy设计模式(Stategy模式将算法封装到一个类中,并可以在运行时转换,从而改变对象的行为)。应用这个模式的原因在于,它能够支持可在运行时选择和改变的算法。

既然已经有了接口,就可以添加折扣策略的两种实现。

首先,创建一个TradeDiscountStategy的新类,定义如下:

1
2
3
4
5
6
7
8
9
public class TradeDiscountStategy:IDiscountStrategy
    {
        public decimal ApplyExtraDiscountTo(decimal originalSellingPrice)
        {
            decimal price = originalSellingPrice;
            price = price * 0.95M;
            return price;
        }
    }

 

 

 

 

之后再利用Null Object模式创建一个无操作行为的类NullDiscountStategy:

1
2
3
4
5
6
7
public class NullDiscountStategy:IDiscountStrategy
   {
       public decimal ApplyExtraDiscountTo(decimal originalSellingPrice)
       {
           return originalSellingPrice;
       }
   }

在建立折扣策略之后,我们来创建Price对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class Price
   {
       public Price(decimal recommandPrice, decimal sellingPrice)
       {
           this.recommandPrice = recommandPrice;
           this.sellingPrice = sellingPrice;
       }
      
       private IDiscountStrategy discountStategy = new NullDiscountStategy();
       private decimal recommandPrice;
       private decimal sellingPrice;
 
       public void SetDiscountStategyTo(IDiscountStrategy aDiscountStrategy)
       {
           discountStategy = aDiscountStrategy;
       }
 
       public decimal SellingPrice
       {
           get
           {
               return discountStategy.ApplyExtraDiscountTo(sellingPrice);
           }
       }
 
       public decimal RecommandPrice
       {
           get
           {
               return recommandPrice;
           }
       }
 
       public decimal Discount
       {
           get
           {
               if (RecommandPrice > SellingPrice)
                   return RecommandPrice - SellingPrice;
               else
                   return 0;
           }
       }
 
       public decimal Savings
       {
           get
           {
               if (RecommandPrice > SellingPrice)
                   return 1 - (SellingPrice / RecommandPrice);
               else
                   return 0;
           }
       }
   }

之后,再创建一个简单的Product类:

 

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

 

 

好了,现在业务实体都创建的差不多了。但是为了演示是否有折扣行为,我们还需要创建一个枚举类型:

1
2
3
4
5
public enum CustomType
   {
       Standard = 0,
       Trade = 1
   }

为了确定哪一种折扣策略用哪种价格,我们还得创建一个工厂类,它唯一的职责就是为给定的CustomType返回一个匹配的折扣策略。

创建一个名为DiscountFactory的新类,定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class DiscountFactory
    {
        public static IDiscountStrategy GetDiscountStategyFor(CustomType customType)
        {
            switch (customType)
            {
                case CustomType.Trade:
                    return new TradeDiscountStategy();
                default:
                    return new NullDiscountStategy();
            }
        }
    }

由于服务层将与数据存储交互,以检索商品。使用Repository模式来实现此功能,但只指定资源库接口,这是因为不希望model项目牵涉到诸如使用什么类型的数据存储或使用什么类型的技术来查询等细节。创建一个名为IProductRepository的接口,接口中只有一个方法:

 

1
2
3
4
public interface IProductRepository
   {
       IList<Product> FindAll();
   }

 

服务类需要能够将给定的折扣策略应用到一组商品,可以创建一个自定义集合来实现该功能。这里我们使用扩展方法来进行。

创建一个名为ProductListExtensionMethods的新类,其定义如下:

1
2
3
4
5
6
7
8
9
10
public class ProductListExtensionMethods
   {
       public static void Apply(this IList<Product> products,IDiscountStrategy discountStategy)
       {
           foreach(var product in products)
           {
               product.Price.SetDiscountStategyTo(discountStategy);
           }
       }
   }

现在可以创建客户端用来与领域交互的服务类。创建一个新的ProductService类,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ProductService
   {
       public ProductService(IProductRepository productRepository)
       {
           this.productRepository = productRepository;
       }
 
       private IProductRepository productRepository;
 
       public IList<Product> GetAllProductsFor(CustomType customType)
       {
           IDiscountStrategy discountStrategy = DiscountFactory.GetDiscountStategyFor(customType);
           IList<Product> products = productRepository.FindAll();
           products.Apply(discountStrategy);
           return products;
       }
   }

现在我们已经将所有的业务逻辑创建完毕。注意,业务逻辑并没有绑定到特定的数据存储,并使用接口对资源库进行访问来完成所有持久化需要。现在可以以完全与应用程序其他部分隔离的方式来测试业务层,而且业务层不会受到其他层变化的影响。要讨论的下一个层次是服务层,他将作为应用程序的入口。

2.服务层

服务层的作用就是充当应用程序的入口,有时候又被称为门面(Facade:为一系列复杂的接口和子系统提供了一个简单的接口并控制对其的访问)。服务层为表示层提供了强类型视图模型,有时候被称为视图模型。视图模型是为特定试图优化的强类型的类。将要创建的视图模型用来显示商品(视图模型是为特定视图优化的强类型的类,并包含用来辅助完成数据表示的逻辑)。

向0620.DaemonPattern.Service层添加一个名为ProductViewModel的新类,代码如下:

1
2
3
4
5
6
7
8
9
public class ProductViewModel
    {
        public int ProductId { get; set; }
        public string Name { get; set; }
        public string RecommandPrice { get; set; }
        public string SellingPrice { get; set; }
        public string Discount { get; set; }
        public string Savings { get; set

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
Asp.net MVC 自定义ViewEngine的简单实现发布时间:2022-07-10
下一篇:
ASP.NETJavaScript显示动态时间发布时间:2022-07-10
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap