伍迷家园

让编程融入生活
随笔 - 92, 文章 - 0, 评论 - 2113, 引用 - 172
数据加载中……

小菜编程成长记(十三 有了门面,程序员的程序会更加体面!)

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

 1        private void Form1_Load(object sender, EventArgs e)
 2        {
 3            //读配置文件
 4            ds = new DataSet();
 5            ds.ReadXml(Application.StartupPath + "\\CashAcceptType.xml");
 6            //将读取到的记录绑定到下拉列表框中
 7            foreach (DataRowView dr in ds.Tables[0].DefaultView)
 8            {
 9                cbxType.Items.Add(dr["name"].ToString());
10            }

11            cbxType.SelectedIndex = 0;
12        }

13

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

 1        private void btnOk_Click(object sender, EventArgs e)
 2        {
 3            CashContext cc = new CashContext();
 4            //根据用户的选项,查询用户选择项的相关行
 5            DataRow dr = ((DataRow[])ds.Tables[0].Select("name='" + cbxType.SelectedItem.ToString()+"'"))[0];
 6            //声明一个参数的对象数组
 7            object[] args =null;
 8            //若有参数,则将其分割成字符串数组,用于实例化时所用的参数
 9            if (dr["para"].ToString() != "")
10                args = dr["para"].ToString().Split(',');
11            //通过反射实例化出相应的算法对象
12            cc.setBehavior((CashSuper)Assembly.Load("商场管理软件").CreateInstance("商场管理软件." + dr["class"].ToString(), 
13                                false, BindingFlags.Default, null, args, nullnull));
14            
15            double totalPrices = 0d;
16            totalPrices = cc.GetResult(Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtNum.Text));
17            total = total + totalPrices;
18            lbxList.Items.Add("单价:" + txtPrice.Text + " 数量:" + txtNum.Text + " "+cbxType.SelectedItem+ " 合计:" + totalPrices.ToString());
19            lblResult.Text = total.ToString();
20        }

     “这里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.Text;
using System.Data;

namespace 商场管理软件.DAL
{
    
public class CashAcceptType
    
{
        
public DataSet GetCashAcceptType()
        
{
            
//读配置文件到DataSet
            DataSet ds = new DataSet();
            ds.ReadXml(
"CashAcceptType.xml");
            
return ds;
        }

    }

}

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

namespace 商场管理软件.BLL
{
    
public class CashFacade
    
{
        
const string ASSEMBLY_NAME = "商场管理软件.BLL";
        
        
//得到现金收取类型列表,返回字符串数组
        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] 
= (string)ds.Tables[0].DefaultView[i]["name"];
            }

            
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();

            CashContext cc 
= new CashContext();
            DataRow dr 
= ((DataRow[])ds.Tables[0].Select("name='" + selectValue + "'"))[0];
            
object[] args = null;
            
if (dr["para"].ToString() != "")
                args 
= dr["para"].ToString().Split(',');

            cc.setBehavior((CashSuper)Assembly.Load(ASSEMBLY_NAME).CreateInstance(ASSEMBLY_NAME 
+ "." 
                                                           + dr["class"].ToString(), false, BindingFlags.Default, null, args, nullnull));
            
return cc.GetResult(startTotal);

        }

    }

}

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


        
double total = 0.0d;//用于总计
        CashFacade cf = new CashFacade();
            
        
private void Form1_Load(object sender, EventArgs e)
        
{
            
//读数据绑定下拉列表
            cbxType.DataSource=cf.GetCashAcceptTypeList();
            
            cbxType.SelectedIndex 
= 0;
        }


        
private void btnOk_Click(object sender, EventArgs e)
        
{
            
double totalPrices = 0d;
            
//传进下拉选择值和原价,计算实际收费结果
            totalPrices = cf.GetFactTotal(cbxType.SelectedItem.ToString(), Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtNum.Text));
            total 
= total + totalPrices;
            lbxList.Items.Add(
"单价:" + txtPrice.Text + " 数量:" + txtNum.Text + " "+cbxType.SelectedItem+ " 合计:" + totalPrices.ToString());
            lblResult.Text 
= total.ToString();
        }


项目文件结构图

 

       “大鸟,来看看这下怎么样,还有没有可修改的地方?”小菜问道。
       “小菜开始谦虚了吗!以前不是一直信誓旦旦,现在怎么,没信心了?”
       “越学越觉得自己知道的少,感觉代码重构没有最好,只有更好呀。”小菜诚心的答道
       “写得很不错。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\”目录下。

 

posted on 2007-03-28 09:17 伍迷 阅读(6238) 评论(41)  编辑 收藏 网摘 所属分类: 面向对象小菜编程成长记

评论

#1楼   回复  引用  查看    

粉丝来了
强占沙发
2007-03-28 09:36 | sekihin      

#2楼   回复  引用    

我不做老大已经很久了

2007-03-28 09:36 | sss[未注册用户]

#3楼   回复  引用    

生动,形象,so up
2007-03-28 09:51 | roydu[未注册用户]

#4楼   回复  引用  查看    

高手!! ( 水晶报表杀手 -- e表, 它避免了大量的复杂SQL编写以及编程来准备数据。轻松实现复杂的统计报表,详见: http://*** )
2007-03-28 10:01 | e表      

#5楼   回复  引用  查看    

哪个用利用反射生成具体策略的抽象成一个简单工厂是不是更好些??
2007-03-28 10:35 | YanziMyWife      

#6楼[楼主]   回复  引用  查看    

@YanziMyWife
当然也是可以,不过吃馒头会饱,吃包子也能饱,就看哪个味道更好些了。
2007-03-28 10:50 | 伍迷      

#7楼   回复  引用    

在UI层直接调用了DAL层的代码
cbxType.DataSource=cf.GetCashAcceptTypeList();
我想请教的是,这是因为为了例子简单而做的简化,还是在实际项目中也能够这么做?这样不就是二层了么?
2007-03-28 11:04 | benbenkoala[未注册用户]

#8楼[楼主]   回复  引用  查看    

@benbenkoala
注意cf是 CashFacade cf = new CashFacade();实例化的,它可是BLL层的类,而真实的数据却是通过BLL层去读DAL的类做到的,这是三层,不是两层。
实际项目可以这样做的。
2007-03-28 11:12 | 伍迷      

#9楼   回复  引用    

终于完了~~可以在简历上写精通设计模式了哈哈
2007-03-28 14:28 | 航天奇侠

#10楼   回复  引用  查看    

@航天奇侠
然后lz来面试你,呵呵
2007-03-28 15:07 | Jeffrey Zhao      

#11楼[楼主]   回复  引用  查看    

@航天奇侠
伍迷:面试开始,请你先做一个自我介绍?
…………
伍迷:很好,请解释一下什么叫三层架构?
…………
伍迷:很好,你说你了解设计模式,请解释一下策略模式如何应用到实际中?
…………
伍迷:很好,看来你对设计模式很了解了,能不能再解释一下观察者模式?

航天奇侠:啊,小菜编程成长记没有提到,你能不能等等,过几天文章出来了,我再给你答案,工作先让我做着?

伍迷:啊?!尽管不懂,但没有装懂,诚实守信,是人才,录用!

受老赵提醒,:)开心一下
2007-03-28 15:28 | 伍迷      

#12楼   回复  引用    

所以下期是观察者模式咯,呵呵
2007-03-28 16:37 | eee[未注册用户]

#13楼[楼主]   回复  引用  查看    

@eee
若每次都被料中,那岂不是很没面子。:)
2007-03-28 16:39 | 伍迷      

#14楼   回复  引用  查看    

马克
2007-03-28 17:27 | 二手的程序员      

#15楼   回复  引用    

很有意思。:)
2007-03-28 18:01 | firefox[未注册用户]

#16楼   回复  引用    

http://***
2007-03-28 20:37 | duhaha[未注册用户]

#17楼   回复  引用    

读的很痛快。

但是我要显示一个简单的数据,比如我直接用了gridview,通过ods获取数据库中的数据,这和其他一些分层的放在一起合适吗?
2007-03-28 21:06 | mynull[未注册用户]

#18楼[楼主]   回复  引用  查看    

@mynull
为了遵循迪米特法则,其实主要是为了减少耦合,你还是需要通过BLL层去找DAL层拿数据,与数据库打交道永远只有DAL,而Gridview这种UI表示层的代码只能与BLL交往,这就把耦合减到最低了。
2007-03-28 21:26 | 伍迷      

#19楼   回复  引用  查看    

越写越有意思了。
2007-03-28 21:31 | Bention      

#20楼   回复  引用    

老大写写委托和代理 事件吧 总是不太明白 还是范型
2007-03-29 09:43 | Apple[未注册用户]

#21楼   回复  引用  查看    

gooooooood. studying...
2007-03-29 10:26 | Ame      

#22楼   回复  引用  查看    

我觉得你写的比我有深度多了,我只是一时兴起写了几篇,但我发现写连载性的随笔真不是件容易的事,所以我不敢再写下去了,现在我正在努力充电,以便给大家送上更好的作品.在技术上过分的追新没有太大意义,还是把最基础的东西搞搞明白才最重要.我正在系统把你的这个系列研读一遍.
2007-03-29 11:28 | 二手的程序员      

#23楼   回复  引用    

public double GetFactTotal(string selectValue, double startTotal)
{
CashAcceptType cat = new CashAcceptType();
DataSet ds = cat.GetCashAcceptType();

//CashContext cc = new CashContext();
DataRow dr = ((DataRow[])ds.Tables[0].Select("name='" + selectValue + "'"))[0];
object[] args = null;
if (dr["para"].ToString() != "")
args = dr["para"].ToString().Split(',');

CashSuper kk = (CashSuper)Assembly.Load(ASSEMBLY_NAME).CreateInstance(ASSEMBLY_NAME + "." + dr["class"].ToString(), false, BindingFlags.Default, null, args, null, null);
return kk.acceptCash(startTotal);

//cc.setBehavior((CashSuper)Assembly.Load(ASSEMBLY_NAME).CreateInstance(ASSEMBLY_NAME + "." + dr["class"].ToString(), false, BindingFlags.Default, null, args, null, null));
//return cc.GetResult(startTotal);
}

我将CashFacad.cs中的这个函数改一下,我觉得那个CashConent类好像没有必要存在。三层的架构以及可扩展的特性也没有丢失。是否当两个设计模式合在一起的时候,总有些类是多余的。我刚刚开始学设计模式,以前没有用过。

还有就是我做的很多系统,每次变动都是需要在数据库中加字段的,这种变化一般有什么样的措施来改善呀,还烦给个方向。。。
2007-04-07 11:01 | swamper[未注册用户]

#24楼[楼主]   回复  引用  查看    

@swamper
你的想法是对的,因为我事先写的是策略模式,后面写了门面模式,所以用了不同的类,其实可以放在一起,也不影响结果,代码也比较清晰。

很好,说明你是真的在思考。继续加油呀。

数据库加字段,一方面说明之前的需求分析没有做好,另一方面也说明客户的需求变化较多。这种改动是无法避免三层都需要改动,不过好的分层和设计可以减少代码的更改,达到可维护的目的。
2007-04-07 11:51 | 伍迷      

#25楼   回复  引用    

受教ing```感谢
2007-04-23 14:51 | wenchicheng

#26楼   回复  引用    

写得真好!
我有个疑惑想请教下:
比如一个数据库管理系统,要修改某条记录(该记录字段:ID,ZD1,ZD2,ZD3),开始只需修改ZD1,ZD2的值,故设计三层模式UI提供2个文本框接受ZD1,ZD2的值,然后调用BLL.update(id,zd1,zd2),BLL的update再调用DAL.update(id,zd1,zd2),DAL.update 负责更新数据库,即sql语句"update table set ZD1=zd1,ZD2=zd2"。

现在ZD3也要求修改,那岂不是要改好多地方,首先UI调用BLL.update要加参数,BLL调用DAL.update也要加参数,DAL中sql语句加上ZD3..,这不是很麻烦吗,有没有好的办法呢?
2007-11-09 13:32 | 小菜菜[未注册用户]

#27楼[楼主]   回复  引用  查看    

@小菜菜
你可以考虑将整个表设计成一个类,这样每个字段都是类的属性。

在修改或新增时,方法这样写
insert(Table table);
update(Table table);

这样就算你要修改的字段有100个,这些方法也是可以不用变化的。
2007-11-09 16:26 | 伍迷      

#28楼   回复  引用    

看了你的门面的介绍后,我在想,操作系统(windows)的kernel.dll,user32.dll,gdi32.dll就是你说的各个政府角色,而程序语言就是提供给程序员的门面,可以通过这些门面来调用系统功能,而不必直接深入到kernel.dll,user32.dll,gdi32.dll来直接调用API。从面向对象的角色看,封装的更好的.net之类的程序语言作为门面来理解似乎会更好一些。哈哈,纯粹的乱放屁一下。
2007-12-04 14:03 | newrain[未注册用户]

#29楼   回复  引用  查看    

非常喜欢这种写作风格!
2008-01-20 16:10 | 赵俊      

#30楼   回复  引用    

研读中,受益匪浅啊!
2008-01-21 01:07 | 小有[未注册用户]

#31楼   回复  引用    

顶……
2008-05-23 11:48 | 念 时[未注册用户]

#32楼   回复  引用  查看    

老大,跟你混.
行不???
2008-06-13 14:57 | Small0426      

#33楼   回复  引用  查看    

看完这个系列简直太棒了,原来将课也这么有意思啊,对面向对象的认识更进一步了!!谢谢楼主大鸟分享
2008-07-24 15:39 | Simens      

#34楼   回复  引用    

赞美
2008-07-24 23:25 | cpf[未注册用户]

#35楼   回复  引用  查看    

如果我有很多张表,是不是每张表的BLL层都要有一个门面呢,因为本人用的是动软代码生成器生成的三层架构,所以很迷惑
2009-05-09 16:45 | nc      

#36楼[楼主]   回复  引用  查看    

@nc
视具体情形而定,建议学习petshop4的结构之后,再用人家的代码生成器,否则你根本无法理解它生成代码的意义是什么。
2009-05-10 13:56 | 伍迷      

#37楼   回复  引用  查看    

@伍迷
BLL层只起了一个转发DAL层的作用,不知怎么样架构才能复用呢,LZ能给一个比较好的例子吗?petshop似乎太大了,有点复杂,能写个简单点的例子吗
2009-05-10 21:11 | nc      

#38楼[楼主]   回复  引用  查看    

其实简单的例子很难展现三层的优势。事实上很多项目,就是数据库的存取,用三层的意义不算大。而一些大型的项目,数据存取只是当中的一部分,更多的是业务逻辑与算法设计和优化,这就是bll层需要好好设计的项目了。
2009-05-11 11:27 | 伍迷      



发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 689955




相关文章:

相关链接: