对充血模型的疑问

小弟我在坛子上看到关于贫血模型和充血模型的讨论后一直再想这对于我这种代码小工意味着啥?
是否可以更快更迅速更敏捷的完成代码完成开发。
一直做的项目都是SSH 然后分层 action,service,dao,entity;有时候我觉得dao层没什么必要啊,既然
hibernate是面向对象为何还要 dao.save(entity)   dao.update(entity) 这样操作?
感觉如果去掉dao层 直接entity.update(),entity.delete();  这样是否会更形象?
然后小弟开始尝试,首先搭了个struts2+spring3+Hibernate3.6的ssh工程用的是全注解搭法这样开发就不用写配置文件了还是相当的爽啊

首先我觉得应该有一个公共的类充当所有entity的父类


import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;

import javax.annotation.Resource;

import org.hibernate.SessionFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.stereotype.Repository;

@Repository("MagicH")
public class MagicH extends HibernateTemplate {

@Resource(name = "sessionFactory")
public void setSuperSessionFactory(SessionFactory sessionFactory) {
    super.setSessionFactory(sessionFactory);
}

private Serializable id; // 标示
private Class clazz; // 实体
private Object entity; // 数据库实体
private PropertyDescriptor targetPds[];  //初始化后属性
private HashMap setMethodMap = new HashMap();   //set方法映射
private HashMap getMethodMap = new HashMap();  //get方法映射

/**
 * 模型初始化
 * 
 * @param clazz
 * @param id
 */
public boolean init(Class clazz, Serializable id) {
    this.id = id;
    this.clazz = clazz;
    PropertyDescriptor targetPds[] = BeanUtils.getPropertyDescriptors(clazz); 
      for (PropertyDescriptor pdObj : targetPds)
        {
           setMethodMap.put(pdObj.getName().toLowerCase(), pdObj.getWriteMethod());
           getMethodMap.put(pdObj.getName().toLowerCase(), pdObj.getReadMethod());
        }
      this.entity = get(clazz, id);
    return this.entity==null?false:true;  
}

/**
 * 获取对象实体
 * 
 * @return
 */
public Object getObj() {
    if (entity == null) {
        this.entity = get(clazz, id);
    }
    return entity;
}

/**
 * 删除对象实体
 */
public void delObj() {
    if (entity != null)
        delete(entity);
    else
        delete(get(clazz, id));
}

/**
 * 设置数据库实体属性
 * @param propertyName
 * @param propertyValue
 */
public void setProperty(String propertyName, Object propertyValue) {

      Method writeMethod = (Method) setMethodMap.get(propertyName.toLowerCase());
      try {
        writeMethod.invoke(entity, new Object[] {propertyValue});
    } catch (IllegalArgumentException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

/**
 * 获取属性的值
 * @param propertyName
 * @return
 */
public Object getPropertyValue(String propertyName)
{
    Method readMethod = (Method)getMethodMap.get(propertyName.toLowerCase());
    Object returnValue = null;
    try {
        returnValue = readMethod.invoke(entity,null);
    } catch (IllegalArgumentException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    return returnValue;
}
/**
 * 更新对象
 */
public void updateObj()
{
    update(entity);
}


/**
 * 保存对象
 * @param obj
 * @return
 */
public Serializable addObj(Object obj)
{
    Serializable id =  save(obj);
    init(obj.getClass(),id);
    return  id;
}

}



建立一张用户表测试,对应实体如下

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.annotations.GenericGenerator;
import org.springframework.stereotype.Repository;

import com.usoft.core.dao.impl.MagicH;

/**

  • MagicUser entity. @author MyEclipse Persistence Tools
    */
    @Repository("MagicUser")
    @Entity
    @Table(name = "MAGIC_USER")
    public class MagicUser extends MagicH implements java.io.Serializable {

    // Fields

    private String userId;
    private String userNam;
    private String userSex;
    private String userPhone;

    // Constructors

    /** default constructor */
    public MagicUser() {
    }

    /** full constructor */
    public MagicUser(String userNam, String userSex, String userPhone) {
    this.userNam = userNam;
    this.userSex = userSex;
    this.userPhone = userPhone;
    }

    // Property accessors
    @GenericGenerator(name = "generator", strategy = "uuid.hex")
    @Id
    @GeneratedValue(generator = "generator")
    @Column(name = "USER_ID", unique = true, nullable = false, length = 32)
    public String getUserId() {
    return this.userId;
    }

    public void setUserId(String userId) {
    this.userId = userId;
    }

    @Column(name = "USER_NAM", length = 500)
    public String getUserNam() {
    return this.userNam;
    }

    public void setUserNam(String userNam) {
    this.userNam = userNam;
    }

    @Column(name = "USER_SEX", length = 1)
    public String getUserSex() {
    return this.userSex;
    }

    public void setUserSex(String userSex) {
    this.userSex = userSex;
    }

    @Column(name = "USER_PHONE", length = 11)
    public String getUserPhone() {
    return this.userPhone;
    }

    public void setUserPhone(String userPhone) {
    this.userPhone = userPhone;
    }

}


这是本人测试用的service


import javax.annotation.Resource;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.usoft.magich.bs.ImagicServiceTest;
import com.usoft.magich.vo.MagicUser;

@Service("MagicService")
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public class MagicServiceTestImpl implements ImagicServiceTest {

@Resource
private MagicUser magicUser;

public void testMagicH() {
    //保存
    magicUser.setUserNam("用户123");
    magicUser.setUserPhone("12345");
    magicUser.setUserSex("1");
    magicUser.addObj(magicUser);

   //更新
    magicUser.setProperty("UserNam", "1234567");
    magicUser.updateObj();

    System.out.print(magicUser.getPropertyValue("UserNam"));

    boolean b1 = magicUser.init(magicUser.getClass(), "aaa");  //id 不存在初始化失败
    System.out.println(b1);

    boolean b2 = magicUser.init(magicUser.getClass(),"402881862da6ec3b012da6fb4f640004"); //id存在为 true
    System.out.println(b2);
    magicUser.delObj(); //删除 对象
}

}



只是中午吃饭时脑子那么想了一下,然后回来就尝试写了个单表的增删改查,我个人举得无论是单表还是多表最后数据库查出来的无非是个二叉表,而其中的一条记录应该对应一个java类 多条记录则是该java类的List集合,然后所有的数据库操作不再是dao.doSomething 而是entity.doSomething
我这样做了后写的代码就只有actiong,service,entity层了,不知道这样是否可行,谁能解释一下我的疑问。


问题补充
看了大家的回复深受启发,感觉自己这个想法似乎不可取就不再钻牛角尖了。
可能一直处于开发人员的角色,所以关系的核心问题无非就是 写最少的代码做最多的事

一个问题的作用域将决定一个问题的解决方案是否合适。其实这也是大多数人对于领域模型各种变种的误解。

当一个简单类具备了field和method之后,大师们告诉你,这个类的实例叫做对象,基于这种方式写出来的程序叫OO。大师们还告诉你,只有field和它们对应的setter/getter方法的类,不怎么OO,因为这叫贫血。在类上加上method,赋予类逻辑执行能力,这叫领域模型,比较OO。

恩,我们不妨考虑一个拥有3000w行代码的大系统,假设它需要1000个领域模型类。这相当于把一个最简单的问题放大了1000倍,那么我们等于把我们的逻辑重复1000遍。当然,这个系统的逻辑不再是增删改查那么简单,而是有复杂的业务流程。这个时候,你会发现,一个领域模型类可能将达到20K,甚至30K那么大。里面可能有超过40个method,对应30种不同的业务流程。

某天,项目经理说,hey,小伙子,客户说这业务改了,它们要在所有涉及到数据库保存的地方加条日志,并通知另外一个系统。想象一下你的工作?在一个超过2000行的类里面,找到那些方法,一个个加过来。恩,这就是专家们说的,这很OO。

哈哈,你这个Entity充血的解释比较有意思。你这种做法,我们几年前的时候就这样做了,你那个Entity类只不过是个Dao的基类,提供了通用的增删改查改功。这并不是什么充血模型,至多算伪充血,还容易误导人概念。我觉得Entity名字换成BaseDao名比较好。充血模型更多应该从领域对象出发。

你这个MagicUser类无法作为模型,因为他无法通过new的方式创建。

你的做法,说白了就是把model层和dao层合并了,没有带来任何别的变化,这种做法不会让你的开发变得更快。

此外还引入了以下问题:
1.你的领域对象和Hibernate框架完全耦合
2.领域对象承担了不属于它的职责,即持久化。好吧,或许你会说“持久化”也可以算它的职责,那么至少“写入数据库”不是它的职责,为了剥离这个职责,演化之后你还是需要一个DAO层。
3.从第2个问题就衍生出第3个问题,你知道有些领域对象是不需要持久化的,只在内存中,但按照你这个框架来的话,随随便便就要抛exception

我不如用一个基于反射的基础数据库操作的BASEDAO

在SERVICE层里 我自动注入这个基操作DAO,如:

@Resource
BASEdao userDao;

个人偏向于贫血,但是特殊业务下,如文章的图片在创建时建创UUID文件名的文件夹,删除时自动删除,== 我会在POJO中注血的

ObjectWeb的DODS就如你想象的“充血模型”,以前改shark源码的时候接触过这个持久层框架一段时间,但不得不说,比起实体与DAO分离的“贫血模型”来说,实在是很不好用。

充血模型,领域驱动个人觉得最好的解决之道在于DOMAIN EVENT 的应用

为什么Rod不把HibernateTemplate定义为final类型的!

用SSH做一个应用是多么麻烦啊,要写这么多代码,长此下去,必成代码工人

[quote="downpour"]一个问题的作用域将决定一个问题的解决方案是否合适。其实这也是大多数人对于领域模型各种变种的误解。

当一个简单类具备了field和method之后,大师们告诉你,这个类的实例叫做对象,基于这种方式写出来的程序叫OO。大师们还告诉你,只有field和它们对应的setter/getter方法的类,不怎么OO,因为这叫贫血。在类上加上method,赋予类逻辑执行能力,这叫领域模型,比较OO。

恩,我们不妨考虑一个拥有3000w行代码的大系统,假设它需要1000个领域模型类。这相当于把一个最简单的问题放大了1000倍,那么我们等于把我们的逻辑重复1000遍。当然,这个系统的逻辑不再是增删改查那么简单,而是有复杂的业务流程。这个时候,你会发现,一个领域模型类可能将达到20K,甚至30K那么大。里面可能有超过40个method,对应30种不同的业务流程。

某天,项目经理说,hey,小伙子,客户说这业务改了,它们要在所有涉及到数据库保存的地方加条日志,并通知另外一个系统。想象一下你的工作?在一个超过2000行的类里面,找到那些方法,一个个加过来。恩,这就是专家们说的,这很OO。[/quote]
哈哈,这算是我目前不支持所谓充血的原因之一。

[quote="downpour"]一个问题的作用域将决定一个问题的解决方案是否合适。其实这也是大多数人对于领域模型各种变种的误解。

当一个简单类具备了field和method之后,大师们告诉你,这个类的实例叫做对象,基于这种方式写出来的程序叫OO。大师们还告诉你,只有field和它们对应的setter/getter方法的类,不怎么OO,因为这叫贫血。在类上加上method,赋予类逻辑执行能力,这叫领域模型,比较OO。

恩,我们不妨考虑一个拥有3000w行代码的大系统,假设它需要1000个领域模型类。这相当于把一个最简单的问题放大了1000倍,那么我们等于把我们的逻辑重复1000遍。当然,这个系统的逻辑不再是增删改查那么简单,而是有复杂的业务流程。这个时候,你会发现,[b]一个领域模型类可能将达到20K,甚至30K那么大。里面可能有超过40个method,对应30种不同的业务流程。[/b]

某天,项目经理说,hey,小伙子,客户说这业务改了,它们要在所有涉及到数据库保存的地方加条日志,并通知另外一个系统。想象一下你的工作?在一个超过2000行的类里面,找到那些方法,一个个加过来。恩,这就是专家们说的,这很OO。[/quote]

为什么"一个领域模型类可能将达到20K,甚至30K那么大。里面可能有超过40个method,对应30种不同的业务流程。"?为什么不能通过继承等方式对公共功能进行抽象和重用?或者,如果这些逻辑根本就不该重用的话,充血模型和贫血模型的唯一区别就成了:是领域模型类达到20K,还是DAO类达到20K。

关于充血贫血模型的讨论,该说的话04年前后大佬们都已说透了,然而对于一些善于思考的新人,领导说编程规范就是这样,被强制灌输思想的时候,或者对于那些还在这个领域奋斗的理想主义者来说,困扰并没有消除。这种困扰从表面上看是不确定业务逻辑放到哪儿,如何定义类的职责边界,深层问题则是能不能最大可能的进行封装和重用,而这一点除了受限于设计能力、方法论等因素之外,还强烈受制于所处的环境和工具集。

从我近几年使用ruby on rails的体会看,之所以java论坛还在纠结充血贫血,正因为大家用的是java。从实现角度来看,只靠java所谓的OO,“继承”、“多态”那么几板斧,并不能把所有概念都能很好的抽象、封装起来。既然做不好,那就干脆点,把那些持久层相关的功能完全隔离出来,定个概念叫DAO,放在一个地儿管,一样看着清爽。我承认这是某种程度的“务实”。

然而这种状况就像徐^贲再议知识分子里描述的那样:"是现实使然的“不得不如此”(necessity),而不是取决于自由意志的“我选择如此”(choice)"。"长期的思想禁锢使得知识分子习惯于把“不得不如此”当成是“我选择如此”。"(这个回贴要是让R同学看到,肯定又睡不着觉了)

我最近正好用scala试写了一个orm库,在这里拿出来给大家秀一下,并提起一个新的话题:“如果换一个理想工具的话,忘掉充血贫血的纷争,重新聚焦在抽象和封装上,你想做到怎样的效果才比较理想”,-- 然后再回过头反思,如果你用java,能不能做出同等效果的类库。甚至可以考虑有没有折衷的办法,成本收益,务实的做法,等等。

我这个orm库模仿了rails3 ActiveRecord的API,重点参考了squeryl的源码,还远到不了可用的程度,也没有长远的计划,只是一时兴趣。

先上使用范例吧。

[url]https://github.com/liusong1111/soupy/blob/master/app/models/User.scala[/url]

定义model和DAO。在DAO里写几个自定义的方法。
[code="java"]
package models

import soupy.persistence._
import reflect.BeanInfo
import util.Random

//定义users表里的三个字段。
//trait相当于java的interface。后面定义的model和dao都混入了它。
trait UserDef extends TableDef {
var id = field(IntType, "id")
var name = field(StringType, "name")
var age = field(IntType, "age")
}

//定义Model类,编译出的User.class里会生成String name,int age等属性和对应的get/set方法
@BeanInfo
class User extends Model with UserDef {

}

//定义Dao类(误命名成schema了),编译时会生成StringProperty name, IntProperty age等属性(你可以当成常量),另外父类中有insert,update,count,where等大量方法可以直接用。
//对应表名"users"
class UserSchema extends SchemaUser, UserSchema with UserDef {
//主键是id(现在做的还不完善)
val idProperty = IdProperty(id)

// 按名字查询,返回一个query对象。后面会演示query对象的具体用法,跟rails3相近,有where、order、limit、group、offset、limit等方法,另外还有两个重要的方法all和first,分别返回List和User,调用这两个方法时会执行相应的sql语句。
// query可以是链式(chainable)的,甚到是DAO级别的chainable,后面范例中可以看到。
def byName(name: String) = where(User.name == name)

// 找出年轻人
def youngs = {
where(age < 18)
}

// 找出姓刘的
def liu = {
where(name like "%liu%")
}
}

//User是DAO的单例对象,直接使用这个对象。
object User extends UserSchema
[/code]

Model类和DAO可以这样使用:
[code="java"]object Main {
def main(args: Array[String]) {
//设定数据库连接
Repository.setup("default", Map("adapter" -> "mysql", "host" -> "localhost", "database" -> "soupy", "user" -> "root", "password" -> ""))

//为了好玩,user.age每次赋给一个随机数
val rand = new Random

// 打印出总记录数,后台会执行 select count(1) from users,你懂得
// count是DAO父类的方法
println("-- count before create:" + User.count)

//创建,同样insert是DAO父类封装的方法。同时提醒一下,它是强类型的,即,如果你往User.insert里传一个new Animal()进去编译会通不过的。
var user = new User()
user.name = "sliu"
user.age = rand.nextInt(70)
User.insert(user)

// 再打印一下看看
println("-- count after create:" + User.count)

//更新:(这块功能还没做完)
user.age = rand.nextInt(70)
User.update(user)

//删除
//User.delete(user)

//查询,重头戏了!
println("-- normal query --")
//where方法可以串着写(chainable)
//最后到all方法时会执行select users.id,users.name,users.age from users where name like 'liu%' AND age > 18
//all会返回List<User>
var users = User.where(User.name like "liu%").where(User.age > 18).all
//遍历一下看看
users.foreach {
  user =>
    println("name:" + user.name + " age:" + user.age)
}

//更酷的来了!
//找到姓刘的年轻人,而且年纪在10岁以上的前两个人。
//youngs和liu是我们在UserDAO里自定义的方法,也是chainable的~
println("-- [COOL] use DAO's selector chain --")
users = User.youngs.liu.where(User.age > 10).limit(2).all
users.foreach {
  user =>
    println("name:" + user.name + " age:" + user.age)
}

//当然,你可以直接遍历所有年轻人: User.youngs.all,它会执行什么样的SQL你了解

//找到id为1的User
//你可以认为它返回的是一个User对象。实际上它返回的是Option[User]类型,这是scala为了防止NullPointerException引入的一个概念,大家可以忽略。
println("-- first --")
var user1 = User.where(User.id == 1).first
if (user1.isEmpty) {
  println("not found")
} else {
  println(user1.get)
}


//在Query上可以怎么玩?
//More Details on Query -------

// where clause
//User.age是一个IntProperty对象(注意,你可以把它看成静态变量),User.age > 33的值不是boolean,而是一个Query实例,这个query有个toSQL方法,返回 "age > 33"这个串。为了省事,我把它的toString方法也覆写成toSQL了。
println(User.age > 33)
// age = 33
println(User.age == 33)

// name > 'liusong'
println(User.name > "liusong")
// name = 'liusong'
println(User.name == "liusong")
// name like 'liu%'
// 注意:你如果写这样的代码User.age like "liu%"会编译不过,因为User.age是IntProperty对象,它没有like方法。同时,如果like后面跟的不是String也会编译不通过。
println(User.name like "liu%")

//AND条件
//name = 'liusong' AND age > 28
println((User.name == "liusong").where(User.age > 28))
// 跟前面效果一样
println(User.name == "liusong" && User.age > 28)

// 既有AND又有OR
//(name = 'liusong' AND age > 28) OR age < 10 OR name like 'liu%'
println(User.name == "liusong" && User.age > 28 || User.age < 10 || (User.name like "liu%"))

//用括号调整AND、OR的分组
//(name = 'liusong' AND (age > 28 OR age < 10)) OR name like 'liu%'
println(User.name == "liusong" && (User.age > 28 || User.age < 10) || (User.name like "liu%"))
// 指定order by,group等部分
println(User.where(User.age > 28).where(User.name like "liu%").order(User.name.desc).group("group by name"))


//内部实现
// look into properties
println("-- look into properties --")
User.properties.foreach {
  prop =>
    println(prop.name)
}

// use singleton object's build
// same as `new User`
println("-- singleton's `build` --")
val u = User.build
println(u.name + u.age)

//you can use reflect style but type safe like this: User.name.set(m, "sliu")
println("-- set/get property using type safe reflection --")
User.name.set(user, "another name")
println(User.name.get(user))

println("-- use Query object directly --")
//select *
//from users
//where name = 'sliu' AND age > 30
//group by age
println(new Query("users").where(User.name == "sliu").where(User.age > 30).group("group by age").order(User.age.desc))

}
}[/code]

这个类库的实现代码:
[url]https://github.com/liusong1111/soupy/blob/master/src/soupy/persistence.scala[/url]

特色有三个:
一:在一个地方声明字段列表,就做到了给model和dao分别生成两组属性,分别用来接收数据和生成sql。(类比java类的属性和一些静态变量)
举例:(new User()).name是String类型,User.name是StringProperty类型,所以可以写成User.name like "liu%",但不能写成(new User()).name like "liu%",编译通不过,因为like方法是StringProperty的,String里没有。
这个效果我觉得是我这个小类库的特色,我个人认为这是使用scala来跨越java限制的一个例证。在java里,即使考虑用javaassist、aspectj、Annotation Process Tool、dynamic proxy等工具,也很难达到同样的效果。
我是使用scala的abstract type辅以Manifest做到的,其实它的内部实现还有简化的空间。
二:chainable:这是借助scala的case class和一些花活做到的。
三:类型安全:这是借助scala强大的类型系统(包括泛型等)做到的,比如,你不需要进行强制类型转化,User.where(...).all返回给你的就是List,不是List。

做这个小东西主要是学习scala。我个人最近比较看好scala语言。scala是静态语言,所以有编译期检查的优势,它和java集成极佳,它编译成java字节码(.class文件)在jvm上跑。可以和java代码互相调用(无论是jdk的库、开源的库还是自己写的某个java文件或整个java工程)。它编译出的字节码性能上接近java(考虑到尾递归优化一类的因素有些时候还可以优于java),理论上IDE的支持可以达到java的水平(目前我用intellij IDEA的scala插件感觉进步很快)。它强大的语法特性更是一时半会说不完的。当然,它还没走入主流,它同样面临着当年ruby面临的一些质疑,比如学习曲线一类的,把它引入到生产环境需要很大的勇气和控制能力。作为java程序员,我觉得有精力的话可以关注一下,它完全可以引领java语言这几年的发展方向。

第一,我的观点一向是使用Java语言就不要谈什么充血模型。因为Java语言从语法角度对于逻辑的高度抽象性就不够,很容易就造成一个类的急速膨胀。

第二,我们总是生活在一个充满着最佳实践的世界。如果说把逻辑封装在实体模型中是一条最佳实践,那么让一个类不超过8k就是另外一条最佳实践。当2条最佳实践发生矛盾的时候,必须根据最佳实践的优先级和重要性调整编程思路。这也就是每个程序员在哲学选择上不同的分歧所在。

lss的orm写的不错哈。

最近也在搞scala


补充lz的问题,正如ls所说的,在java里面就别讨论充血。

不要MagicUser extends MagicH
把MagicUser作为MagicH的内部类,这样MagicUser可以自由new

这种模式应该是"活动记录(active record)"。他比事务脚本模式要更能体现领域模型的一些特点,但是随着业务的复杂度提升,不可避免的要和持久层耦合的更强,最好的是在ORM的帮助下实现领域模型和仓库的分离,这对团队的要求比较高,领域驱动在某些特别的情况下也可以在局部使用事务脚本,来弥补一下天然阻抗。
最后借用书里的一句话,使用何种模式驱动,要看项目和团队实力的实际情况,使用事务脚本总比项目失败要好吧。

楼主你确定你了解所谓的领域模型?

[quote="JasonChow"]这种模式应该是"活动记录(active record)"。他比事务脚本模式要更能体现领域模型的一些特点,但是随着业务的复杂度提升,不可避免的要和持久层耦合的更强,最好的是在ORM的帮助下实现领域模型和仓库的分离,这对团队的要求比较高,领域驱动在某些特别的情况下也可以在局部使用事务脚本,来弥补一下天然阻抗。
最后借用书里的一句话,使用何种模式驱动,要看项目和团队实力的实际情况,使用事务脚本总比项目失败要好吧。[/quote]

[quote]使用何种模式驱动,要看项目和团队实力的实际情况,使用事务脚本总比项目失败要好吧。[/quote]
为什么使用事务脚本就会有失败的导向?项目失不失败和这个有什么关系?

[quote="downpour"]一个问题的作用域将决定一个问题的解决方案是否合适。其实这也是大多数人对于领域模型各种变种的误解。

当一个简单类具备了field和method之后,大师们告诉你,这个类的实例叫做对象,基于这种方式写出来的程序叫OO。大师们还告诉你,只有field和它们对应的setter/getter方法的类,不怎么OO,因为这叫贫血。在类上加上method,赋予类逻辑执行能力,这叫领域模型,比较OO。

[color=red][b]恩,我们不妨考虑一个拥有3000w行代码的大系统,假设它需要1000个领域模型类。这相当于把一个最简单的问题放大了1000倍,那么我们等于把我们的逻辑重复1000遍。当然,这个系统的逻辑不再是增删改查那么简单,而是有复杂的业务流程。这个时候,你会发现,一个领域模型类可能将达到20K,甚至30K那么大。里面可能有超过40个method,对应30种不同的业务流程。[/b][/color]

某天,项目经理说,hey,小伙子,客户说这业务改了,它们要在所有涉及到数据库保存的地方加条日志,并通知另外一个系统。想象一下你的工作?在一个超过2000行的类里面,找到那些方法,一个个加过来。恩,这就是专家们说的,这很OO。[/quote]

没看懂。你的意思是多一个领域对象就把问题放大一倍?为什么会有这样的结论?

充血与贫血 的差别我认为主要是:
一个是按功能分解业务逻缉
一个是按对象分解业务逻缉

后者带来的是细粒度的逻缉划分,从而会避免重复代码,并使得业务逻缉可以被重用。

[quote]第一,我的观点一向是使用Java语言就不要谈什么充血模型。因为Java语言从语法角度对于逻辑的高度抽象性就不够,很容易就造成一个类的急速膨胀。 [/quote]

java不能充血不能认同。那只是说对于现在涉及到数据库的应用吧。致少仅在内存中完成的逻缉,用java做到OO是没问题的。比如:ArrayList这样的类就是充血的。

java的语法少了什么呢?

另外lz的做法目标是好的,可是做出来的代码却让MagicUser变得难以理解。

MagicUser本身是一个持久化对象。
MagicH却给它加了一些和子类重叠的属性以及Dao的逻缉。MagicH和MagicUser是硬给拼到一块的。
当面对一个MagicUser,是该用父类的方法,还是子类的方法呢?

MagicH起到的作用就是把MagicUser搞乱,而不是让MagicUser充血!

这样的贴子被管理员从论坛拿到这,真是悲哀!

lz的代码是个反例,正因为如此,才显得有意义!