.NET 技术

WF,WCF,Software Factory,Design Pattern,etc.
随笔 - 16, 文章 - 0, 评论 - 140, 引用 - 7
数据加载中……

2008年8月14日

引用vividboy朋友的回复

今天早上看到了vividboy的回复,觉得说的很好,应该可以解释很多人的疑问。特发一文。:-)

看来我表达上还是有问题。呵呵。


以下为vividboy的回复:

 

1. 疑问一:分层架构中的面向接口
---------------

接口的修改,相当于契约的修改,这样引起的变化自然是难免的。规则都改变了,相关的内容自然也需要改变。我们在进行系统架构设计的时候坚持的根本 原则应该是“开-闭”原则,即对新增开放对修改关闭。由于对代码的修改将对原有系统造成冲击,这样对原有系统的功能也会造成一定的副作用,所以我们在修改 原有代码的时候要尽量的位置集中,并减少波及范围(这个就是耦合度的问题了。);同时针对新需求我们追求的是通过纯新增的方式来达到满足需求变化的目的, 而不需要对原有代码进行过多的修改,稳定的接口和易变的具体实现应该就是针对这个问题的。

 

说的很到位,这也是我没有说出来的话,谢谢vividboy!

 

posted @ 2008-08-14 09:45 赵俊其 阅读(298) | 评论 (0)编辑

2008年8月13日

对姜敏朋友的回复

没想到我的一篇拙文引起姜敏朋友的深入思考,并且写了一篇博文交流,在此先表示感谢!

姜敏博文地址:对于项目架构的疑惑

以下为回复:

1.     疑问一:分层架构中的面向接口

博文作者按照我的思路做了一个小Demo,并质疑对数据存取层的修改是否真的不会避免业务逻辑层的修改。

首先,要澄清的一点是,在面向接口的编程中,共享的是接口(或契约)而屏蔽的是实现。所以这里涉及到一个问题,如果是对接口(或契约)的修改的话,那双方就都需要修改代码,而如果仅仅是对实现的修改的话,就不用修改接口的另一端。不知我说明白没有。

Demo里,其实修改的是接口,而不是实现。所以肯定会影响到接口的另一端。

作者还提到了工厂模式。其实工厂模式是创建型模式,不是行为性或结构性模式,所以它解决的不是修改代码时对接口另一端的影响问题,而是如果方便地创建新的产品系的问题。例如工厂方法可以解决IDataAccess _Parter = new OrderAccess();这一的类名硬编码的问题。而抽象工厂可以解决增加一个产品系不会影响之前存在的代码的问题。只是增加一个产品,并增加一个产品的消费者而已(注意是“增加”,而不是修改,这是最重要的)。

2.     疑问二:强类型的DataSet为什么不能在层与层之间传递呢?

这个问题其实很简单,强类型DataSet中直接包括了数据库的结构,如果它被整个系统的各个层都消费的话,就意味着各个层都对数据库结构有直接的了解。那和Client-Server模式又有什么区别呢?分层的意义何在呢?“架构”这个词的意义就是是系统处于一种能用最小的代价应对变化的状态中(我个人的理解)。而Client-Server模式中的一个很小的数据库修改,就会涉及到难以想象的代码范围。呵呵。

 

不知我的回复能否让姜敏朋友满意,如有不正指出,欢迎大家指正。

posted @ 2008-08-13 15:56 赵俊其 阅读(2301) | 评论 (50)编辑

实战项目分析续(解答问题)

拙文《实战项目分析》发表之后,很多朋友留言,讨论比较热烈,我也对一些朋友的留言做个回复,一起交流,共同提高!

 

1.     关于避免业务实体贯穿多层的问题

首先强调,分层架构下,我们要尽量避免的是牵一发而动全身,但某些时候这是必然的,任何方案只能解决部分问题,而不是所有问题,而且往往一个新的方案会带来新的问题。

我列出我能想到的几种解决方案,并且使用一个典型的“加字段”的例子各自说明优缺点:

a.       每个层使用自己的模型,传递时翻译。比如使用DataContractBusinessEntityDataTable分别对应表现层,业务逻辑层,数据存取层的领域模型,传递时在各层之间翻译。这样可以起到分离的作用。WCF在这方面就做的很好,它强制开发者使用DataContract约定系统的数据接口,而屏蔽其实现。

这种方案是最常用的,但不能很好地解决加字段的问题,当需要加一个字段时,DTBEDC都需要修改。而且翻译动作会带来一些效率上的损失。

b.       各层使用同一个模型,例如都使用业务对象。利用C#partial类,各层只定义自己关心的数据,也可以起到隔离的效果。我见过一个老外的项目这么设计。

这个方案可以部分解决“加字段”的问题。首先我们在数据库表中增加一个字段,在数据存取层的partial类中加一个Property,然后加一行赋值语句,最后在展现层加一个控件。这样就避免了修改业务逻辑层(当然是业务逻辑层不处理该字段的情况下)。这个方案虽然能解决加字段的问题,但会由于带来partial类太分散造成代码阅读困难。

c.       让数据自描述。即数据本身包含元数据,如使用XML。但这也会带来另一个问题,这样的会走向另一个极端,就是弱化了面向对象。任何的数据都是XML,就没有了面向对象的优势。当然你也可以再搭建一个XML到对象的转化平台,但会增加很多工作量。

如果平台搭建的好的话,可以解决“加字段”的问题,因为XML是自描述的,可以加很多元数据进去,描述数据在各层的属性(例如数据结构,是否被转换为对象,是否在界面上可见等)

以上是我目前想到的和见过的几个办法,肯定有问题,这里抛砖引玉,也希望大家多多指正。

 

2.     面向接口编程的问题

大家对这个也比较关注。我在这里澄清一下:我没有说接口是万能的,接口肯定不是万能的,也不可能解决所有的问题。我说的只是接口是消除依赖的基础,是现阶段面向对象领域实现消除依赖的主要手段。

在现在这个面向对象为主要理论基础的时期,包括设计模式也好,架构模式也好,其中一个核心的思想就是消除依赖,封装变化,而接口正是其主要手段。

欢迎大家继续讨论!:-)

posted @ 2008-08-13 15:04 赵俊其 阅读(1480) | 评论 (12)编辑

实战项目分析(二)

(接上文

 

4.     强类型DataSet

上面讲到了业务对象和强类型DataSet两种领域模型的使用问题。其实强类型DataSet.NET中很好的一种方案,它集成了数据库和面向对象两种优点,如果使用的好的话,会事半功倍,但使用不好的话,麻烦也很大。

在本系统中,强类型DataSet被赋予很多使命:从数据库中获取信息(数据存取层)、业务处理(业务逻辑层)和数据展现(展现层),贯穿了整个系统。这样就使得整个系统对强类型DataSet的数据结构非常依赖,一旦数据库发生变化,所有的代码(从数据存取到展现层)都要修改代码来。并且最要命的是强类型DataSet可以自动感知数据库的变化,自动更新同步。试想,如果你是这个系统的编码人员,会不会时时都提心吊胆呢?

很显然,这是一种糟糕的设计。在分层结构中,任何数据结构都不能贯穿始终,特别是与数据库结构。这回带来难以置信的麻烦。分层,其实就是要隔离这种变化给系统带来的连锁反应。使底层的修改不影响到顶层,反之亦然。

当然这是不是意味着强类型DataSet就不能使用了呢?当然不是的。强类型DataSet是非常好的连接数据存取层和业务逻辑层的纽带,因为它既有数据库结构又有对象特性。所以,只要我们能在两个层次中各自屏蔽细节,依赖于抽象而不是实现,强类型DataSet就可以在系统中发挥重要的作用。

5.     展现层太臃肿

本系统的很大一部分UI都是B/S的,采用ASP.NET构建。但我发现很多的WebPage中包含有大量的界面逻辑和业务逻辑,基本每个WebPage的代码都在几百行,有点甚至上千行。试想,这样的UI维护起来

对于每一个开发者来说,大概都经历过这种痛苦,为了数据库的一个字段的修改,要从底层到顶层,全部修改一便,而且UI的修改是最麻烦的,往往是越改越烦!

其实对于UI的设计模式已经很成熟了,大家都知道MVC模式吧。就是一个很成熟,很实用的UI设计模式。另外还有MVP模式,这个是MVC的基础之上提出来的,跟MVC思想相同,但细节上有所不同而已。MVC模式网上有很多的资料,也有很多有名的应用案例。MVP则被广泛应用在微软P&P团队的很多项目中,诸如:Software Factory系列中都有应用。

下面是MVC模式和MVP模式的对比:

 


另外,关于两种模式的详细对比,可以参考另一位MVP:TreeLee的文章:ASP.NET MVC FrameworkWCSFMVP模式之小小比较

6.     自定义事务

.Net framework 2.0中内置了对事务的支持,不但可以管理进程内的事务(包括SQLServer事务),还可以自动提升至MSDTC来管理分布式事务(包括WCF事务)。所以我们无需再编写任何事物的管理代码。

本系统中使用了Enterprise Library中的Data Access Application Block作为数据存取方案。但却没有很好地利用.Net framework 2.0的事务功能,而是自己写了很多管理事务的代码。例如使用一个TransactionContext类管理事务的执行,在很多数据存取的方法上支持传入TransactionContext类型的参数,用来管理事务边界。

这样不仅需要花费精力维护TransactionContext类,管理事务的执行,也使数据存取接口变的很复杂,臃肿。

其实我们完全可以利用TransactionScope这一.Net framework2.0中的事务处理类还管理事务。最简单的方式是:

Using(TransactionScope cpe = new TranscationScope())

{

           数据操作方法1();

           数据操作方法2();

           …

           数据操作方法N();

}

这样就可以自动提交和回滚事务了,而且可以根据实际情况,如果其中某个方法调用了分布式事务的话,可以自动升级为MSDTC事务。

关于如何使用.Net framework2.0中的事务功能,可以参考:Introducing System.Transactions in the .NET Framework 2.0

7.     其它问题

还有一些其它的小问题,虽然不涉及到系统架构,但也会带来一些负面的影响,包括:

A. 代码重复

a)         很多数据查询方法功能相同,只是返回的数据“格式” 不同(有的返回DataSet,有的返回DataRow,有的返回实体对象),为了调用方便,做了很多重载的方法,但没有考虑代码重复利用,造成大量的代码冗余。

B. 公共模块

a)         设计者把系统中使用到的公共代码和枚举等等组织在一起,放在一个文件中,为各个项目所使用。其实这本来很好,但是我却发现,公共模块同时被三个层次引用,这样也是很不好的。这样会使得系统的分层结构遭到破坏,公共模块难于理解和使用。应该把属于各层专属使用的功能集成到各自的层中,每个层中设一个公共模块,这样既保障了分层的清晰,同时又很好地规划了公共模块。


 (完)

posted @ 2008-08-13 11:31 赵俊其 阅读(2309) | 评论 (24)编辑

实战项目分析(一)

      最近接到一个临时任务:帮外国某知名公司分析一个项目架构。这个项目是两年前开发的,并且经过了几次升级。主要功能是管理客户、合作伙伴资料,提供在线业务等等,具体细节不用多说。

据客户说,他们在使用本系统的过程中发现了很多的问题,觉得已经不再满足他们的需求,希望我们能帮助他们评估一下当前的系统有哪些架构上的问题,并帮助他们发现未来可能发生的问题,从而决定是否需要开发新的系统

客户提供了很详细的文档,包括业务说明,系统架构,技术要点,部署方案等等。看完文档,对系统和客户期望有了一定的了解之后,开始干活儿!

系统是采用.Net技术构建的,基于.Net Framework 2.0,使用了ASP.NET, WinForm, WebService等技术,并使用了Enterprise Library中的Data Access CacheLog等功能。

我本人负责的是架构的分析。结合文档和源代码,没用1个小时,系统架构就很清晰了。其中发现了一些很普遍的问题,在这里跟大家分享一下:

1.     分层架构

分层架构是绝大部分企业软件都普遍采用的方案,但由于架构师水平的参差不齐,导致很简单的一个分层,出现了很大的差异。

大家都知道“3层架构”或者“多层架构”。有的理论里分3层,有的理论里分5层,还有分7层的。其实,在我看来分几层不重要,重要的是分层的目的。分层是为了什么的?简单的说就一句话:为了便于维护。

大家都知道,软件开发中“变化”才是永恒的。开发周期是死的(尽管可以一拖再拖,但总有一个发布的截至日期吧!),但后期的维护却是很不变得。不管是发布补丁也好,更新版本也好,其实都是为了能适应软件发布之后面临的各种各样的“变化”。

好,我们回到分层上来。分层,就是为了使系统结构更清晰,系统耦合性变小,使修改一处代码时,对其它的部分影响最小,这样就能以最小的代价应对变化带来的麻烦!所以,分层的第一要素,就是各层之间屏蔽细节,降低依赖,使各层具体实现变得透明(这也是SOA的其中一个重要思想)。我们可以通过各种办法来达到这个目的,面向接口编程,设计模式,架构模式等等,都可以帮助我们。

而我在此项目中看到的第一个重要的问题就是,系统分层不清楚。下面是分层的简图:

 

由上图可见,系统共分了 三层。但有一个问题,业务对象竟然贯穿了三层,这严重违反了分层的初衷。由于业务对象对数据存取层和展现层都可见,导致如果我们的业务对象发生了变化,就 要修改从数据存取、业务逻辑到展现层,这三个层次的所有相关代码。而且这个方案违反了分层的两个设计原则:

a.            下层对上层隐藏细节,只暴露接口。再此,本应属于业务逻辑层的业务对象被暴露到了展现层。

b.            上层对下层不可见。即下层不知道上层的存在,只提供接口。这里业务逻辑层的业务对象被数据存取层操作,会导致两个层之间纠缠不清,以至于会出现改动业务逻辑会影响数据存取方式的荒谬现象。

另外,强类型DataSet也有同样的问题(本应是属于数据存取层的,却被传递到业务逻辑层,甚至是展现层)

软件设计中有一个很重要的原则就是:依赖倒置原则(DIP

依赖倒置的意思是:调用者依赖被调用者的接口,而不是实现。

具体到此处就是:业务逻辑层应该依赖数据存取层的接口,而不是具体实现(强类型DataSet)。由于被调用者编程了抽象,而调用者变成了“实现”,所以与普通的面向对象的观念来说,依赖关系被倒置了,因此被称作依赖倒置原则。

依赖倒置在分层结构中是很重要的原则。希望每一个设计者都时刻把它记在心间吧,呵呵。

 

2.     面向接口编程

其实这跟上一个问题有密切联系。面向接口编程,是“依赖于抽象,而不是实现”的具体手段。不管是模块内部,还是个层次之间,面向接口是消除依赖的基础。

举一个简单的例子:本系统中业务逻辑层会调用数据存取层的方法,得到一些数据。比如调用一个PartnerAccess类的GetPartner的方法。PartnerAccess是数据存取层的一个具体类,负责Patrner表的所有增删改查操作。而业务逻辑层到处充斥着这样的语法:PartnerAccess partnerac = new PartnerAccess();partnerac.getPartner();

这就是一个典型的依赖于具体实现的方案。这样的后果是,业务逻辑层知道每一个数据表的数据结构,甚至是无需知道的细节,并且对数据层的每一个方法都了如指掌,到处都在使用。当我们开始修改PartnerAccess的其中一个方法的时候(比如增加一个参数)都要修改业务逻辑层的相关代码,但谁知道那些代码都在哪呢?只好重新编译吧,让编译器告诉我们。

而面向接口编程可以使我们避免这种问题。我们不再依赖于千千万万个PartnerAccess或者什么别的Access类,而是依赖一个IDataAccess的接口。这样,所有的数据存取都被标准化,我们的调用代码便的更简单,不会依赖任何数据库的结构,甚至不需要知道表的名字,有多少个字段等等。当我们增加一个OrderAccess类时,只需在数据存取层增加一个文件,一个类就好了,而不需要更改业务逻辑层的任何代码。

记住这个原则吧,它也可以说是面向对象的核心思想。会让你受益匪浅的!

3.     领域模型不清晰

从上面的图中可以看出来,本系统同时使用了两种领域模型,一种是业务对象(Business Object),一种是强类型DataSetStrong Type DataSet),并且在每个层次中都使用了。

举个简单的例子:强类型DataSet被应用到ASP.NET的控件绑定上,用来显示数据。而业务对象被WebService暴露给客户端。

如果有人看过马丁。福勒的那本《企业架构模式》的话,应该会记得对领域模型的选择上有几种方案。其中业务对象和强类型DataSet都被提到了,并且说明了什么时候适用哪个模型。这里我不多说,感兴趣的朋友可以去看看那本书。

我想说的是,这里使用了两个模型并存的方案。这样就使得系统的领域模型不清晰,而且存在很多的冗余,例如出现了Partner业务对象和PartnerDS强类型DataSet并存的现象,尽管他们各有各的优缺点,但这样势必会造成领域模型的难于维护和代码可读性差的问题。

其实,特殊情况下,也可以两个同时使用,但要注意,由于业务对象是属于业务逻辑层的,而强类型DataSet是数据存取层的,所以他们都要在自己的范围内活动,不能被其它的层次存取。

到这里,有人可能会发现一个矛盾就是:使用单一业务对象的话,则会对数据存取层带来额外的开销,因为数据存取层不能知道业务对象的存在,就需要使用抽象,会带来一些代价。但如果使用单一的强类型DataSet的话,就会对业务逻辑层和展现层保留很多的内部数据细节,也会对系统架构造成一些影响,而且也不利于WebService的传输。

其实,一个合格的设计师,需要对这两种方案做各自的调整,都为自己所用,但只取他们的优势,而避免他们的劣势多带来的麻烦。

软件设计,何尝又不是一种取舍的艺术呢!

 (下一篇

 

posted @ 2008-08-13 11:23 赵俊其 阅读(2668) | 评论 (29)编辑

2008年6月10日

使用Premiere编辑MTS高清视频

找了好一阵子,还是这里的实用。哈哈。私人笔记:如何把rmvb或rm导入到prmiere中(最新版本!)

posted @ 2008-06-10 11:42 赵俊其 阅读(287) | 评论 (0)编辑

2008年6月3日

Programming WCF Services 学习笔记五、Operations

     摘要: 1. 传统的面向对象或面向组件只提供Client到Server的一种调用方式,同步调用。WCF提供了更多的调用方式,允许Service回调Client。而且可以支持更大的负载。  阅读全文

posted @ 2008-06-03 16:18 赵俊其| 编辑

Programming WCF Services 学习笔记四、Instance Management

     摘要: 1. Behaviors
i. Service的实例模型是客户端无关的,即Client端不会知道Service的实例模型,Service的实例模型也不会影响Client端  阅读全文

posted @ 2008-06-03 16:04 赵俊其 阅读(64) | 评论 (0)编辑

Patterns & Practices 培训资源

1.Patterns & Practices Overview:下载
2.Enterprise Library 3.1:下载
1.Software Factory Overview:下载
2.Smart Client Software Factory:下载
3.Web Client Software Factory:下载
3.Mobile Client Software Factory:下载
3.Web Service Software Factory:下载
3.Web Service Software Factory:下载

posted @ 2008-06-03 11:12 赵俊其 阅读(49) | 评论 (0)编辑

Programming WCF Services 学习笔记三、Data Contract

     摘要: 1. Serialization

i. DataContract保护在Service Metadata中,客户端需要把DataContract转换为可使用的本地类型

ii..Net使用序列化来传递DataContract

iii.一次客户端调用的序列化过程:
...  阅读全文

posted @ 2008-06-03 10:29 赵俊其 阅读(95) | 评论 (0)编辑