有了门面,程序会更加体面!- pos软件基于三层架构 -09

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

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

DataSet ds;
private void formLoad()
{
//读取配置文件
ds = new DataSet();
ds.ReadXml(Application.StartupPath + "\\CashAcceptType.xml");

//将读取到的记录绑定到下拉列表框中
foreach (DataRowView drv in ds.Tables[0].DefaultView)
{
cbbType.Items.Add(drv["name"].ToString());
}
cbbType.SelectedIndex = 0;
}

“上面代码,里面有没有什么与界面无关的东西?”大鸟问道。
      
“第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[0].Select("name=‘" + cbbType.SelectedItem.ToString() + "‘"))[0];

//声明一个参数的对象数组
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[0].DefaultView.Count;
string[] arrarResult = new string[rowCount];

for (int i = 0; i < rowCount; i++ )
{
arrarResult[i] = ds.Tables[0].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[0].Select("name=‘" + selectValue + "‘"))[0];
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 = 0; i < arraList.Length; i++)
{
cbbType.Items.Add(arraList[i].ToString());
}
cbbType.SelectedIndex = 0;
}
}

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

时间: 2024-10-05 11:04:57

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

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

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

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

我们都知道Android中基本组件是Activity,每一个界面都是一个Activity,自从2.3版本号開始.又添加了Fragment组件,提供了适应于各种屏幕方法.可是因为Android系统仅仅是提供了各种可用的编程技术,并没有相应用架构进规定.实践中,非常多项目将全部功能都加入到了Activity中,使Activity变得十分臃肿.不便于进行后期改动和维护.假设我们要一个人来开发一个系统.那么必须採用高效的应用架构,方便调试及维护. 在开发应用中,採用MVC架构无疑是一种最安全的选择.将应

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

本文转自:http://www.codeusing.com/hi/uephee.wen/resource/view/170.aspx 资源分类:微软代码示例               更新日期:2008-10-11 主页:http://msdn.microsoft.com/en-us/library/aa581769.aspx 语言:英文  授权形式:免费 微软 Scott Mitchell 为大家准备的 ASP.NET 2.0 三层架构应用程序系列教程,共35讲,分别有C#和VB.NET版本

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

部署Bookinfo示例程序详细过程和步骤(基于Kubernetes集群+Istio v1.0) 部署Bookinfo示例程序 在下载的Istio安装包的samples目录中包含了示例应用程序. Bookinfo应用 部署一个样例应用,它由四个单独的微服务构成,用来演示多种 Istio 特性.这个应用模仿在线书店的一个分类,显示一本书的信息.页面上会显示一本书的描述,书籍的细节(ISBN.页数等),以及关于这本书的一些评论. Bookinfo 应用分为四个单独的微服务: productpage

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

一.如何定义架构师 Java架构师,首先要是一个Java程序员,熟练使用各种框架,并知道它们实现的原理.jvm虚拟机原理.调优,懂得jvm能让你写出性能更好的代码;池技术,什么对象池,怎么解决并发量.连接池,线程池. Java反射技术,写框架必备的技术,但是有严重的性能问题,替代方案Java字节码技术:nio,没什么好说的,值得注意的是"直接内存"的特点,使用场景;java多线程同步异步;java各种集合对象的实现原理,了解这些可以让你在解决问题时选择合适的数据结构,高效的解决问题,比

资深程序员冒死揭开软件潜规则:无法维护的代码

原始博文发布于: Roedy Green's Mindproducts (http://mindprod.com/unmain.html ). 翻译链接: 点击打开链接 2014年11月25日 03:11 如何编写无法维护的代码 让自己稳拿铁饭碗 ;-) Roedy Green 简介 永远不要(把自己遇到的问题)归因于(他人的)恶意,这恰恰说明了(你自己的)无能. -- 拿破仑 为了造福大众,在Java编程领域创造就业机会,兄弟我在此传授大师们的秘籍.这些大师写的代码极其难以维护,后继者就是想对

Qt程序发行Linux版,软件打包知识(patchelf 工具修改依赖库,确认 qmake -v 是自己使用的Qt版本,否则用export PATH进行修改)good

patchelf 工具可以修改已编译运行程序的依赖库位置和指定库链接器 patchelf --set-rpath patchelf --set-interpreter 通过这个工具 https://github.com/probonopd/linuxdeployqt 如果自己编译不了,也可以下载现成的 https://github.com/probonopd/linuxdeployqt/releases 运行 ./linuxdeployqt-2-x86_64.AppImage ShanbayDi

在远程服务器上完成本地设备的程序烧写和调试(基于vivado ,SDK软件)

在使用vivado和SDK进行设计开发的时候,通常需要登录到远程服务器上进行,但是会遇到一个问题就是,所使用的开发板通常是连接在自己的电脑上(local-PC),那要怎么才能让运行在服务器上的设计软件检测到连接在本地的设备呢?Vivado开发套件提供了一个解决方法,就是使用Hardware Server,具体实现步骤如下: 1. 在local-PC上找到Vivado或SDK的安装目录,并在该目录下找到hw_server程序,具体路径为: <Vivado_install_root>\bin或者&

资深程序猿冒死揭开软件潜规则:无法维护的代码

原始博文公布于: Roedy Green's Mindproducts (http://mindprod.com/unmain.html ). 翻译链接: 点击打开链接 2014年11月25日 03:11 怎样编写无法维护的代码 让自己稳拿铁饭碗 ;-) Roedy Green 简单介绍 永远不要(把自己遇到的问题)归因于(他人的)恶意,这恰恰说明了(你自己的)无能. -- 拿破仑 为了造福大众,在Java编程领域创造就业机会,兄弟我在此传授大师们的秘籍.这些大师写的代码极其难以维护.后继者就是