最近忙的利害,也懒了,好久没有更新,写个小文补补课.
应对需求的变化才是OO方法最好的用武之地,如果没有需求的变化那么OO方法的优势就很难体现了,对于
很多程序员使用C++,java,C#等等 很多支持OO方法的语言环境编程时都不能(貌似也不必)熟练地使用OO
方法.所以我觉得有必要把一些更具体的场景写出来,说明典型的OO设计.
例如:使用Decorator而不是参数.
具体场景如下:
已有一个接口,包含一个方法 execute(),由于需求变化,该接口的实现有些需要加入一种能力
--需要异步执行,有两种典型的处理方法:
1.将execute()加入一个参数:签名将被改为 execute(boolean asynchronous)
2.新写一个 AsynDecorator 的新接口实现,实现execute()方法的异步调用.显然,第二中方法是典型的OO方法.
名称 Decorator
意图 动态地给一个对象添加一些额外的职责。就增加功能来说,D e c o r a t o r 模式相比生成子
类更为灵活。
适用性 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
处理那些可以撤消的职责。
当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产
生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于
生成子类。
在使用Spring1.*的时候,最不爽的一个问题就是不能被注入的类,就是指那些只能由应用逻辑实例化的类,如果这种类的实例需要进行一些属性注入我们往往使用以下的方法:
将实例化业务类所需的依赖注入到实现该类实例化的类中。
而该方法会造成了一种"依赖泛化",就是说本来该某业务类依赖的东西,现在扩展到实例化业务类的类中。
此时应该使用简单工厂来解决该问题,建立一个Spring-Bean的Factory,将依赖注入到该工厂中,然后将工厂注入到相应的实例中,在该实例中使用该工厂创建相关业务类的实例。
问题:
在Web应用中,一个界面处理完成后对于如何流转界面非常棘手.
不好的解决方案:
1.返回主页
2.每个页面单独编码,返回固定的页
3.记录session信息,写大量代码,返回相关页
一种比较好的方案
在使用WebMVC方案时,将Controller分成两类:一类是入口类(比如*.door),一类是过程处理类(*.proc),入口类的Controller都是通过简单的GET方式相应请求的.然后在session建立请求列表(数据结构一个缓冲区),由Filter对入口类的请求进行记录,当(一个或)几个普通的处理类Controller完成整个pageflow之后直接跳转(redirect)到最后一个入口类请求中.当然我们可以更详细的提供相应的API,比如:最后一个(代参数),最后一个(不带参数),第n个.....
关于"贫血模型"的讨论几乎没有停止过,在openfans.org的开发过程中,我们也讨论了很久,我觉的有很多东西应该记下来:
明确一下意思先:
DAO:数据操作对象,会操作数据库
持久层:能提供对象持久化服务的一系列组件或服务
领域对象:描述领域模型的对象,是通过业务分析进行系统建模的产物
贫血模型:就是domain object只有属性的getter/setter方法的纯数据类,所有的业务逻辑完全由一个所谓的Manager来完成(又称TransactionScript),这种模型下的domain object被Martin Fowler称之为“贫血的domain object”
常见的类基本结构如下:
一个业务数据类叫做Item,
一个DAO接口类叫做ItemDao
一个DAO接口实现类叫做ItemDaoHibernateImpl
一个业务逻辑类叫做ItemManager(或者叫做ItemService).
观察上面的几个类很容易发现问题:
1:Item和ItemManager实际是操作与数据的关系,实际完成的就是经典OO中的一个对象的能力;
2:当有许多Item时 类组变得很庞大,产生很多 xxxDao xxxImpl xxxManager 其中包含大量重复代码;
按<<重构>>的观点,上述代码存在以下臭味:
1:重复的代码 xxxDao xxxImpl xxxManager(通常)
2:霰弹式修改,一个变化影响多个类,类之间不够高内聚 item变化-->Dao,Impl,Manager均要变动
3:依恋情结,两个类之间互相作用过多 item<->Manager
4:平行继承体系,当增加一个新类时总是要增加另一个类
5:夸夸其谈未来性,在没有任何暗示的情况下考虑扩展 Dao,实际HibernateImpl可能n年内是唯一的Dao实现
6:纯稚的数据类,只有数据的类 item
我觉的 贫血模型 是系统分析设计方向性错误的产物:
1:没有进行领域建模---以数据表结构为中心,而不是业务模型为中心的思考方式,使设计人员选择Item为考虑问题的出发点
2:将DAO与持久层混淆---我们需要的一种持久化服务,DAO紧紧是提供数据操作能力而已,Hibernate是一种高级的服务(他已经包含了DAO,而不是相反),已经完成了所有的持久层服务.
3:过于强调低偶合---将一些本来一些提供单一职责的内容分散在多个单元中使 客户端 依赖更多的接口,而忘记了高内聚原则.
4:Spring的能力限制---由于Spring现阶段不支持对于领域模型的服务注入,使设计人员将操作和数据分开,并将领域变为DataOnly的.
(Spring2.0将在很大程度上解决这个问题)
我认为良好的解决方案:
首先领域建模,建立领域模型(以满足用例)-->合并前面所说的Item和ItemManager成为 domainItem;对于数据库服务,
1:如果考虑领域层包含数据库操作能力,则建立DAO并选择其它好的DAO方案比如IBATIS或Hibernate之类的组件;此时领域对象直接调用DAO代码,而客户程序不必了解DAO的情况,只是使用领域模型.
例子: 人员 新员工=new 人员();新员工.填表(姓名,年龄,...),新员工.持久化('这里会调用DAO').客户程序不必了解DAO的情况,只有领域对象才是客户程序需要了解的.
2:如果考虑将数据库(或其他存储界质)存储考虑在领域之外成为持久层,持久层持久的是业务对象而不仅是数据.
a:则或者对持久层框架同时建模,同时选择合适的组件为持久层服务提供存储服务(包括DAO--亦可选择IBATIS/Hibernate组件),
此时如果由客户程序决定持久化---即持久化是该应用的典型用例,客户端程序有义务了解持久层接口,根据这些接口,客户程序完成自己的用例.
例子: 人员 新员工=new 人员();新员工.填表(姓名,年龄,...); 持久化服务.持久化(新员工);
此时如果不希望客户程序了解持久层接口,则需要提供一系列fasade,隐藏持久化接口的细节.
b:或者直接使用Hibernate/JDO等框架实现持久化服务,领域层直接使用持久层服务,对领域对象进行持久化操作.
此时Hibernate/JDO会侵入到领域模型之内,这样也没什么不好,这样领域对象能够充分利用Hibernate/JDO的强大能力,提供丰富的功能.
此时如何使用Spring一类的IOC呢,这可能就需要我曾经说过的"神力第一次推动"了,这时可能需要一个ServiceLocator,来获取IOC容器的实例.
其他:
实际上,作为一种解决方案,所谓"贫血模型"的具体使用,并不会有太大的问题,尤其是使用一些代码生成工具或已经做好相应的基本框架时,很多软件的核心价值都在于对客户提供的服务,而其内部则成为黑盒,我们只要合理的解决业务问题,就是"王道"了,对于代码的臭味,可以慢慢重构--因为这也需要成本呀.
在使用代码生成工具的时候,我有个小建议,就是不要修改代码生成工具生成的代码,要想扩展其功能,你最好继承之,然后在其他代码使用扩展后的类.
再其他:
有人说,我们的业务就是CRUD,领域模型只有数据类就足够了.我觉的这是搞错了方向------只有CRUD时,只有处理CRUD的那些类才有必要进行建模(他们才是领域模型),而所谓的User\Item等数据类则完全没有必要进行建模,更不要谈领域了.
贫血之外:
实际上,软件\OO方法的外延大的很,更多问题与数据库存储无关(但也有贫血问题),所以建模才是根本,OO方法的原则才是我们必须掌握的.
Template和Command模式从客户实现者来看非常接近,都是继承(或实现)一个固定的类(接口),然后将实现交给已有框架的上下文进行处理.在框架编程中非常常用,但是他们之间的区别很多时候使用者都不是很清晰:
1、Template倾向于算法构架的重用,Command不倾向于算法
2、Template往往要求实现者了解算法的细节,Command则往往用于框架对客户端的回调
3、Command强调对实现的管理,即对一系列Command实例的管理,而Template强调单一实现对基类的扩展
4、Command实现的方法被框架直接使用,而Template实现的方法被框架隐含调用(即框架并不直接调用扩展方法而由Template基类按算法要求调用)
5、Command往往是接口继承,而Template是实现继承,所以Command更易于重用
贫血对象:一个不包含Save方法的对象
http://jroller.com/page/habuma?entry=spring_2_0_vs_the // business functions
public void save() {
dao.save(this);
}
// injected DAO
private CustomerDao dao;
public void setDao(CustomerDao dao) {
this.dao = dao;
}
偶然听到有人讨论OO设计中,领域对象的贫血模型和非贫血模型的讨论.
有人支持贫血模型,有人支持非贫血模型.
我认为这不是一个值得上升到系统模型设计的问题,理由如下
1.OOA,OOD关心的主要是领域建模,是否包含Save只是对领域的理解范围不同.
2.实际"贫血"的说法很不确切:除了Save,领域对象还有很多其他的丰富多彩的功能,这时对象并不贫血
3.如果领域对象只是值对象,根本没有必要过多的分析,即只有DTO职责的ValueObject才是领域中值得存在的,但不用过度分析
4.Save仅仅是一个方面,将来AOP的实现可以到达这个层次,即将来可以通过AOP来实现Save,那时就不存在贫血不贫血的问题
另外:
A:持久层不是DAO层,持久层的职责是对领域对象的持久化,所以持久层包含DAO,职责却远大于DAO.
B:当我们考虑的仅仅是通过数据库存储业务影响时,即理解持久层是数据存储层时,DAO的实现则完全由数据存储层完成了.
我认为数据存储层不是完整的持久层.
结论:当我们将DAO作为领域内问题是,显然非贫血模型很合理:90%的以上企业应用还行典型的数据库应用.
当我们将持久化剔除出领域时(使用一些持久层框架:比如hibernate)我们就可以使用贫血模型咯.
What is it
Active Record包装了数据表或视图中的一行数据,封装了它的数据库访问行为,并加入了该数据的业务逻辑。也可以这样看,Active Record是加入了数据库访问行为的Domain Object ^_^
How it works
Active Record即可以包含数据记录的所有业务逻辑,也可以只包含普通的面向数据的逻辑,将其余的业务逻辑通过Transaction Script来实现
Active Record常常实现下面的方法:
1。通过Sql查询结果构造一个Active Record实例
2。为插入数据库操作预先生成一个Active Record实例
3。静态的Finder方法,返回Active Record对象
4。通过Active Record对象Update数据库和Insert数据库
5。Get/Set数据域
6。实现一些业务逻辑
由于Active Record和数据库的紧密耦合,我们经常在此模式中看到静态的Finder方法,当然也可以把这些Finder方法放到一个类中。
When to use it
Active Record的一个优点是比较简单,在基于单条数据记录的CRUD操作中都能很好的工作
在Domain Model中主要的选择就是Active Record和Data Mapping,前者比较简单,而且比较适合解决对象模型恰好对应数据库中的表结构的业务领域。但是问题是如果义务逻辑比较复杂,你希望你的对象模型能够处理关系,集合,继承等等时,Active Record就力不从心了,这是就是使用Data Mapper的时候了
Active Record的另一个问题是它把对象模型的设计紧密地和数据库结构耦合在一起,修改和重构都很困难
如果你使用Transaction Script,那么Active Object也是一个很好的模式,它帮你减少代码重复等不好的现象。