续上篇)
        大鸟说道:“实际上没有学过设计模式去理解三层架构会有失偏颇的,毕竟分层是更高一级别的模式,所谓的架构模式。不过在程序中,有意识的遵循设计原则,却也可以有效的做出好的设计。”
      “不要告诉我,刚才讲的‘迪米特法则’就会在分层中用得上?”小菜说。
     “当然用得上,否则讲它干吗,你当我是在安慰你而临时编个法则来骗骗你呀?来,再来看看你上次写的代码。”

先来看看之前用反射机制改良的pos程序

DataSet ds;
private void formLoad()
{
//读取配置文件
ds = new DataSet();
ds.ReadXml(Application.StartupPath + "\\CashAcceptType.xml"); //将读取到的记录绑定到下拉列表框中
foreach (DataRowView drv in ds.Tables[].DefaultView)
{
cbbType.Items.Add(drv["name"].ToString());
}
cbbType.SelectedIndex = ;
}

“上面代码,里面有没有什么与界面无关的东西?”大鸟问道。
       “第4、5行是读配置文件的代码,它应该属于DAL层。对吧?”
      “很好,再看下面的这段,里面又有哪些呢?”

double total = 0.0d;
private void button1_Click(object sender, EventArgs e)
{
try
{
//通过多态,可以得到收取费用的结果
if (string.IsNullOrEmpty(txtprice.Text.Trim()) || string.IsNullOrEmpty(txtnum.Text.Trim()))
{
MessageBox.Show("请输入正确的值!");
return;
}
CashStrategy cst = new CashStrategy(); //根据用户的选项,查询用户选择项的相关行
DataRow dr = ((DataRow[])ds.Tables[].Select("name='" + cbbType.SelectedItem.ToString() + "'"))[]; //声明一个参数的对象数组
object[] args = null; //若有参数,则将其分割成字符串数组,用于实例化时所用的参数
if (dr["para"].ToString() != "")
args = dr["para"].ToString().Split(','); //通过反射实例化出相应的算法对象
cst.setBehavior((CashSuper)Assembly.Load("wfPosApp").CreateInstance("wfPosApp.classHelper." + dr["class"].ToString(), false, BindingFlags.Default, null, args, null, null)); double singlePrices = 0d;
singlePrices = cst.GetResult(Convert.ToDouble(txtprice.Text.Trim()) * Convert.ToDouble(txtnum.Text.Trim()));
total += singlePrices;
listRecord.Items.Add(string.Format(@"单价:{0},数量:{1},合计:{2},(计算方式:{3})", txtprice.Text.Trim(), txtnum.Text.Trim(), singlePrices, cbbType.Text.Trim()));
//listRecord.Items.Add("");
lbtotal.Text = total.ToString();
}
catch (System.Exception ex)
{
MessageBox.Show("请输入正确的值!");
}
}

“这里3-13行,是为确定哪种算法而创建CashContext对象,其中用到了反射技术,为计算做准备。第16行是真正的计算打折价或返利,17-19是界面显示的部分。所以应该把3-16行都搬到BLL层去。不过,我还有些疑问,这样做会让配置文件的数据要先从DAL转到BLL,再转到表示层,多麻烦呀,什么不直接表示层读DAL,它想要数据就去读DAL,它想算结果就去请求BLL处理?”

“那是说明你没有真的了解什么叫迪米特法则,象你那样说,不就等于,你小菜又要认识小张,又要认识小李了,这不就耦合过度吗?本来你只需要认识一个人就可以了,这样依赖才会小呀!”
     “可是我就得在BLL里写一个专门返回从DAL里得到数据的方法,这个方法不属于现在的任何类,我就还得再写一个类来做这种传声筒的角色。而且由于界面还要涉及到其它的类,如CashContext,感觉UI和BLL耦合还是很高。”
     “说得没错,你的确是讲到点子上了,由于表示层UI需要与BLL有两个类进行交互,这是很麻烦,不过前辈们就想了了一个较好的办法,另一个设计模式,‘门面模式’(Facade)或叫外观模式”

(以下源自吕震宇 博客)
门面模式要求一个子系统的外部与其内部的通信必须通过一个统一的门面(Facade)对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。

门面模式的结构

门面模式是对象的结构模式。门面模式没有一个一般化的类图描述,下图演示了一个门面模式的示意性对象图:

在这个对象图中,出现了两个角色:

门面(Facade)角色:客户端可以调用这个角色的方法。此角色知晓相关的(一个或者多个)子系统的功能和责任。在正常情况下,本角色会将所有从客户端发来的请求委派到相应的子系统去。

子系统(subsystem)角色:可以同时有一个或者多个子系统。每一个子系统都不是一个单独的类,而是一个类的集合。每一个子系统都可以被客户端直接调用,或者被门面角色调用。子系统并不知道门面的存在,对于子系统而言,门面仅仅是另外一个客户端而已。

“哦,你这样一讲,我就明白了。”小菜说,“上篇所讲的IT部,其实可以由部门主管就是门面,我们只需要找到部门主管,就可以通过他安排相关的人来提供服务,我们不需要了解IT部的具体情况了。”
       “其实现实中这样的例子很多。比如以前上海市没有新闻发言人,当要到春运时,所有的记者都跑到交通部去了解信息,当有非典或禽流感时,所有的记者又跑到卫生部去打听情况,突然这时候楼市大跌,记者们又得马不停蹄前往建设部收集新闻。辛苦呀,有什么办法呢,吃这口饭的。但其实辛苦地又何止只是记者。各个政府部门都需要专人来应付这些记者,不能多说话,不能说错话,但也不能不说话。也辛苦呀,谁叫他们是政府呢。”大鸟仿佛自己感同身受似的描述着,“于是,新闻发言人横空出世,一位知识女性焦扬,代表上海市政府发言,从此,老记们不需要头顶骄阳奔跑于各大政府部门之间,只需要天天等在新闻发言厅门口守着就可以写出准确及时的新闻。而政府部门也不用专人来应付老记们的围追堵截,有更多的时间为人民做实事办好事。这里就只辛苦一个人。”
      “那一定是新闻发言人自己了,因为她需要先与政府部门沟通好,要说些什么、如何说、如何回答刁钻问题。然后要站在镁光灯下承受压力接受记者的访问。不过,干这一行就是需要辛苦的,这是政府的门面呀。”小菜感慨到。

“好了,去改写吧,你一定会感受到分层后代码的漂亮。”大鸟鼓励道。

过一小时后,小菜给出商场收银程序的第六份作业。。

DAL层代码(目前是读配置文件,以后可以很容易的修改为访问数据库)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.IO; namespace PosApp.DAL
{
public class CashAcceptType
{
public DataSet GetCashAcceptType()
{
//string strPath = Directory.GetCurrentDirectory();
//string strPath2 = Environment.CurrentDirectory.ToString();
//string strPath3 = Application.StartupPath.ToString();
string strPath4 = AppDomain.CurrentDomain.BaseDirectory;
//string strPath5 = AppDomain.CurrentDomain.SetupInformation.ApplicationBase; // string strPath6 = HttpRuntime.AppDomainAppPath.ToString();
// string strPath7 = Server.MapPath("~/");
//string strPath8 = Request.ApplicationPath; //string strPath9 = Environment.CurrentDirectory;
//string strPath10 = AppDomain.CurrentDomain.BaseDirectory; ; DataSet ds = new DataSet();
ds.ReadXml(strPath4 + "XML\\CashAcceptType.xml");
return ds;
}
}
}

DAL层代码

BLL层主要代码(Facade类代码)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PosApp.DAL;
using System.Data;
using PosApp.BLL.ClassHelper;
using System.Reflection; namespace PosApp.BLL
{
public class CashFacade
{
const string ASSEMBLY_NAME = "PosApp.BLL"; /// <summary>
/// 得到现金收据类型列表,返回字符串数组
/// </summary>
/// <returns></returns>
public string[] GetCashAcceptTypeList()
{
CashAcceptType cat = new CashAcceptType();
DataSet ds = cat.GetCashAcceptType();
int rowCount = ds.Tables[].DefaultView.Count;
string[] arrarResult = new string[rowCount]; for (int i = ; i < rowCount; i++ )
{
arrarResult[i] = ds.Tables[].DefaultView[i]["name"].ToString();
}
return arrarResult;
} /// <summary>
/// 用于根据商品活动的不同的原价格,计算此商品的实际收费
/// </summary>
/// <param name="selectValue">下拉类表选择的折价类型</param>
/// <param name="startTotal">原价</param>
/// <returns></returns>
public double GetFactTotal(string selectValue, double startTotal)
{
CashAcceptType cat = new CashAcceptType();
DataSet ds = cat.GetCashAcceptType(); CashStrategy cs = new CashStrategy();
DataRow dr = ((DataRow [])ds.Tables[].Select("name='" + selectValue + "'"))[];
object[] args = null;
if (!string.IsNullOrEmpty(dr["para"].ToString()))
{
args = dr["para"].ToString().Split(',');
}
cs.setBehavior((CashSuper)Assembly.Load(ASSEMBLY_NAME).CreateInstance(ASSEMBLY_NAME + ".ClassHelper." + dr["class"].ToString(), false, BindingFlags.Default, null, args, null, null));
return cs.GetResult(startTotal);
}
}
}

BLL层主要代码

UI层代码(可以很容易的转换为Web页面)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using PosApp.BLL; namespace PosApp.Web
{
public partial class _Default : System.Web.UI.Page
{
double total = 0.0d;//用于总计
CashFacade cf = new CashFacade(); protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
//读数据绑定下拉列表
//cbbType.DataSource = cf.GetCashAcceptTypeList();
string[] arraList = cf.GetCashAcceptTypeList();
for (int i = ; i < arraList.Length; i++)
{
cbbType.Items.Add(arraList[i].ToString());
}
cbbType.SelectedIndex = ;
}
} protected void Button1_Click(object sender, EventArgs e)
{
try
{
//通过多态,可以得到收取费用的结果
if (string.IsNullOrEmpty(txtprice.Text.Trim()) || string.IsNullOrEmpty(txtnum.Text.Trim()))
{
Response.Write("<script>alert('请输入正确的值!');</script>");
return;
} double singlePrices = 0d;
//传进下拉选择值和原价,计算实际收费结果
singlePrices = cf.GetFactTotal(cbbType.SelectedItem.ToString(), Convert.ToDouble(txtprice.Text.Trim()) * Convert.ToDouble(txtnum.Text.Trim()));
total += singlePrices;
listRecord.Items.Add(string.Format(@"单价:{0},数量:{1},合计:{2},(计算方式:{3})", txtprice.Text.Trim(), txtnum.Text.Trim(), singlePrices, cbbType.Text.Trim()));
//listRecord.Items.Add("");
lbtotal.Text = total.ToString();
}
catch (System.Exception ex)
{
Response.Write("<script>alert('请输入正确的值!');</script>");
}
}
}
}

UI层代码

项目文件结构图

“大鸟,来看看这下怎么样,还有没有可修改的地方?”小菜问道。
       “小菜开始谦虚了吗!以前不是一直信誓旦旦,现在怎么,没信心了?”
       “越学越觉得自己知道的少,感觉代码重构没有最好,只有更好呀。”小菜诚心的答道
       “写得很不错。BLL层的CashFacade类其实就是新闻发言人,程序的门面;而应用程序或Web其实就类似CCTV和SMG,都是新闻单位,他们不应该也不需要关心门面后面的实现是如何的。现在用了门面模式以后,耦合比以前要少很多了,更改会更加方便,扩展也很容易了。你要是再回过头来看看最初的代码和现在的代码,你会体会更深刻,更加明白重构的魅力。”

大鸟接着说:“之前的代码,下拉控件的绑定是硬编码,所以只要改动需求就得改代码,现在是读配置文件,大大增加灵活性;之前的代码是根据用户选择,分支判断执行相应的算法,现在整个算法类全部搬走,做到了业务与界面的分离;之前的代码由于全写在form里,所以要更换成Web方式,即C/S改为B/S非常困难,要全部重新写(注意真实的软件系统不会这么简单,所以简单复制不能解决问题),现在的代码由于把业务运算分离,所以界面的更改不会影响业务的编写。还有就是现在的代码由于DAL与BLL分离,配置文件可以很容易的更换为数据库读取,且不需要影响表示层与业务逻辑层的代码。总的来讲,若是程序不会变化,原有的设计就没什么问题,运行结果正确足够了,但若是程序可能会时常随业务而变化,新的设计就大大提高了应变性,这其实就是应用设计模式的目的所在。”
       “我现在越来越有信心学好它,设计模式真的很有意思,学它不学它,写出来的代码大不一样。老大,跟你混,看来没有错。”
       “嗨,小菜,我不做老大已经很久了!”大鸟仰身长叹,扬长而去。

(待续)

本文源代码。其中分四个项目,DAL、BLL、WebUI和WinUI,可设置WebUI和WinUI为启动项目,注意由于只是学习源代码,配置路径没有做处理(实际应用需要config文件),WebUI配置文件CashAcceptType.xml在“\商场管理软件06分层\”根目录下,而WinUI的配置文件CashAcceptType.xml在“\商场管理软件06分层\商场管理软件\bin\Debug\”目录下。

有了门面,程序会更加体面!- pos软件基于三层架构 -09的更多相关文章

  1. 学习笔记_Java_day13_三层的HelloWorld程序(15)--不错,整体三层架构学习

    分三层写:养成一个架构的习惯,如何编写一个大型网站 DAO数据层 service业务层 servlet web表述层

  2. 应用程序框架实战二十二 : DDD分层架构之仓储(层超类型基础篇)

    前一篇介绍了仓储的基本概念,并谈了我对仓储的一些认识,本文将实现仓储的基本功能. 仓储代表聚合在内存中的集合,所以仓储的接口需要模拟得像一个集合.仓储中有很多操作都是可以通用的,可以把这部分操作抽取到 ...

  3. 部署Bookinfo示例程序详细过程和步骤(基于Kubernetes集群+Istio v1.0)

    部署Bookinfo示例程序详细过程和步骤(基于Kubernetes集群+Istio v1.0) 部署Bookinfo示例程序   在下载的Istio安装包的samples目录中包含了示例应用程序. ...

  4. 程序员是天生的软件UI设计师

    一个软件项目,谁才是软件开发的主体,是软件UI设计师?还是程序员? 这还用问吗?当然是程序员拉.引用以下alienbat知友的一段评论:对于软件开发而言,码农的工作是必需的.设计师的工作是可选的. 举 ...

  5. 最老程序猿创业开发实训1---Android应用架构之MVC

    我们都知道Android中基本组件是Activity,每一个界面都是一个Activity,自从2.3版本号開始.又添加了Fragment组件,提供了适应于各种屏幕方法.可是因为Android系统仅仅是 ...

  6. 微软代码示例:ASP.NET 2.0 三层架构应用程序教程系列

    本文转自:http://www.codeusing.com/hi/uephee.wen/resource/view/170.aspx 资源分类:微软代码示例               更新日期:20 ...

  7. Java程序员如何从码农晋升为架构师,你跟架构师的差别在哪里?

    一.如何定义架构师 Java架构师,首先要是一个Java程序员,熟练使用各种框架,并知道它们实现的原理.jvm虚拟机原理.调优,懂得jvm能让你写出性能更好的代码;池技术,什么对象池,怎么解决并发量. ...

  8. 做SaaS的程序员们,是时候关注企业架构了

    SaaS赛道是一个超大赛道,足够容纳上万家服务商,不太可能有哪个服务商能满足所有场景,大部分SaaS服务商在某个垂直领域,提供差异化的产品和服务.SaaS产品大部分都是面向B端客户,少部分面向C端客户 ...

  9. 盘点销售一体机 打印POS一体的设备。 打印,盘点,销售PDA(手持终端)+移动销售POS软件

    一.产品介绍 本产品为一个PDA(手持终端)+移动销售POS管理软件组合.可以实现功能如下: 可以实现移动销售(销售退货).盘点.配送.移库.打印小票的功能. 销售时,可以选择客户.业务员.库房,并且 ...

随机推荐

  1. ASP.NET WEB API路由机制

    (一)路由原理 (二)路由设计架构分析 RouteBase

  2. linux用命令删除重复行

    文本处理时,经常要删除重复行,下面是三种方法 第一,用sort+uniq,注意,单纯uniq是不行的. sort -n test.txt | uniq 第二,用sort+awk命令,注意,单纯awk同 ...

  3. oracle merge into用法

    转载:http://blog.163.com/duanpeng3@126/blog/static/885437352011724104741817/ 在 平时更新数据时,经常有这样一种更新,即将目标表 ...

  4. c# 网络

    http://www.cnblogs.com/fuchongjundream/p/4079128.html http://stackoverflow.com/questions/21728773/th ...

  5. 深入理解Java中的final关键字

    Java中的final关键字非常重要,它可以应用于类.方法以及变量.这篇文章中我将带你看看什么是final关键字?将变量,方法和类声明为final代表了什么?使用final的好处是什么?最后也有一些使 ...

  6. Android联系人数据库

    转载自http://www.2cto.com/kf/201406/309356.html 通信录是一个3层的数据存储模型,这三个数据模型就是ContactsContact.Data,ContactsC ...

  7. Android网络请求通信之Volley

    一.Volley简介 Volley网络框架是Google公司在2013年发布的一款Android平台上的网络请求通信库.以下是对Volley的简单归纳. Volley的优点: 使网络通信更快.更简单. ...

  8. php代码规范—2

    如何写出好的 PHP 代码? 在本文中,我们将探讨一些良好的编程习惯,这将帮助你避免代码中的缺陷. 1- 编写模块化代码 良好的PHP代码应该是模块化代码.PHP的面向对象的编程功能是一些特别强大的工 ...

  9. #define #include #undef的其中一个用法(目的)

    一.背景 最近在跟一段系统级的代码,和原来单纯的下位机代码相比,真的是刘姥姥进大观园--看花了眼.相较于 之前所常见的各种下位机代码,系统级代码常常会出现深层次结构体嵌套,结构体内的各种回调函数导致对 ...

  10. jvm指令调试

    监控GC的工具分为2种:命令行工具和图形工具: 常用的命令行工具有: 注:下面的命令都在JAVA_HOME/bin中,是java自带的命令.如果您发现无法使用,请直接进入Java安装目录调用或者先设置 ...