• domain model的延伸讨论


    domain model,又称为领域模型,是Java企业应用讨论的一个热门话题,JavaEye也曾经多次围绕这个话题讨论,我们来看个简单的例子:

    引用

    一个简单的公司工时管理系统,记录员工的个人信息,每个员工的工作任务分配,以及工作所属类别(例如开发,还是测试,还是培训等等),其中每个员工有n个任务,员工和任务是一对多关系,每个员工也分别隶属于多个不同的工作类别,员工和类型是多对多关联关系,而每个任务也分别隶属于唯一的工作类别,任务和类别是多对一关系。另外系统不要求对部门信息进行维护,不需要department表。因此,在这个系统中使用四张数据库表:

    users表保存员工信息,有name, password, gender, department, salary
    tasks表保存工作任务信息,有name,start_time, end_time
    kinds表保存工作所属类别,有name
    kinds_users表是一张关联表,保存users表和kinds表的多对多关联外键的

    系统的功能需求如下:
    1、某部门录用一名新员工
    2、某部门员工总薪水总和
    3、某员工已经开始但尚未结束的任务
    4、给某员工分配一项任务
    5、所有用户当前已经开始但尚未结束的任务
    6、对某一类别,给所有和此一类别相关的员工,批量新增一批任务
    7、针对任务的统计功能,给定某类别,统计当月总的任务数,已完成任务数,未完成任务数


    我们先看看用ruby如何实现系统的领域模型:

    Ruby代码 复制代码
    1. class User < ActiveRecord::Base   
    2.   has_and_belongs_to_many :kinds  
    3.      
    4.   has_many :tasks:dependent => :destroy do  
    5.     def processing_tasks   
    6.       find :all:conditions => ["start_time <= ? AND end_time is null"Time.now]   
    7.     end  
    8.   end  
    9.      
    10.   def apply_task(task_name)   
    11.     self.tasks << Task.new(:name => task_name, :start_time => Date.today)      
    12.   end      
    13.        
    14.   def self.all_processing_tasks   
    15.     Task.find :all:conditions => ["start_time <= ? AND end_time is null AND user_id is not null",Time.now]   
    16.   end  
    17. end  
    18.   
    19. class Task < ActiveRecord::Base   
    20.   belongs_to : owner, :class_name => 'User':foreign_key => 'user_id'  
    21.   belongs_to :kind  
    22.      
    23.   def self.current_month_tasks(kind)   
    24.     kind.tasks.current_month_tasks    
    25.   end  
    26. end  
    27.   
    28. class Kind < ActiveRecord::Base   
    29.   has_and_belongs_to_many :users  
    30.      
    31.   has_many :tasks do  
    32.     def current_month_tasks   
    33.       month_begin = Date.today - Date.today.mday + 1   
    34.       month_end = Date.today - Date.today.mday + 30   
    35.       processing_tasks = find :all:conditions => ["start_time <= ? AND end_time is null ", month_begin]   
    36.       processed_tasks = find :all:conditions => ["end_time >= ? AND end_time <= ? ", month_begin, month_end]   
    37.       all_tasks = processing_tasks.clone   
    38.       all_tasks << processed_tasks unless processed_tasks.size == 0   
    39.       return all_tasks, processed_tasks, processing_tasks   
    40.     end  
    41.   end  
    42.      
    43.   def add_batch_task_to_users(task_name)   
    44.     self.users.each do |user|   
    45.       task = Task.new(:name => task_name, :start_time => Date.today)    
    46.       user.tasks << task   
    47.       self.tasks << task   
    48.     end     
    49.   end  
    50. end  
    51.   
    52. class Department   
    53.   def self.employee(username, department)      
    54.     User.create(:name => username, :department => department)      
    55.   end     
    56.      
    57.   def self.total_salary(department)   
    58.     User.sum :salary:conditions => ["department = ?", department]   
    59.   end  
    60. end  


    1、某部门录用一名新员工
    Ruby代码 复制代码
    1. Department.employee("robbin","开发部")  

    2、某部门员工总薪水总和
    Ruby代码 复制代码
    1. Department.total_salary("开发部")  

    3、某员工已经开始但尚未结束的任务
    Ruby代码 复制代码
    1. user.tasks.processing_tasks  

    4、给某员工分配一项任务
    Ruby代码 复制代码
    1. user.apply_task("学习Java")  

    5、所有用户当前已经开始但尚未结束的任务
    Ruby代码 复制代码
    1. User.all_processing_tasks  

    6、对某一类别,给所有和此一类别相关的员工,批量新增一批任务
    Ruby代码 复制代码
    1. kind.add_batch_task_to_users("学习单元测试")  

    7、针对任务的统计功能,给定某类别,统计当月总的任务数,已完成任务数,未完成任务数
    Ruby代码 复制代码
    1. Task.current_month_tasks(kind)  


    这里值得注意的是,RoR可以很方便的采用充血的领域模型,所有的业务逻辑都可以放在相关的domain model里面。这里的user,task和kind都是对应于数据库表的领域模型,而department是不对应数据库的纯业务逻辑的domain model。总共4个ruby文件,4个domain model,55行代码,所有要写的代码都在这里了,代码量确实非常少,每个domain model的颗粒度都比较大。

    然后我们再看看如何用Java:
    Java代码 复制代码
    1. public class User {   
    2.     private Long id;   
    3.     private String name;   
    4.     private String password;   
    5.     private String gender;   
    6.     private String department;   
    7.     private int salary = 0;   
    8.     private List<Task> tasks = new ArrayList<Task>();   
    9.     # omit getter/setter methods ......   
    10. }   
    11.   
    12. # omit User's ORM Mapping file   
    13.   
    14. public class Task {   
    15.     private Long id;   
    16.     private String name;   
    17.     private int duration = 0;   
    18.     private User owner;   
    19.     # omit getter/setter methods ......   
    20. }   
    21.   
    22. # omit Task's ORM Mapping file   
    23.   
    24. public class Kind {    
    25.     ......   
    26. }   
    27.   
    28. # omit Kind's ORM Mapping file   
    29.   
    30. public interface UserDao {   
    31.     public void addUser(User user);   
    32.     public loadUserById(Long id);   
    33.     # omit CRUD and other persistent methods ......   
    34.     public List<User> findByDeparment(String department);   
    35. }   
    36.   
    37. public interface TaskDao {   
    38.     # omit CRUD and other persistent methods ......   
    39. }   
    40.   
    41. public class UserDaoImpl {   
    42.     # omit implementations ......   
    43. }   
    44.   
    45. public class TaskDaoImpl {   
    46.     # omit implementations ......   
    47. }   
    48.   
    49.   
    50. public class UserService {   
    51.     private UserDao userDao;   
    52.     public setUserDao(UserDao userDao) { this.userDao = userDao; }   
    53.     public int workload(User user) {   
    54.         int totalDuration = 0;   
    55.         for (Task task : user.getTasks()) {   
    56.             totalDuration += task.duration;   
    57.         }   
    58.         return totalDuration;   
    59.     }   
    60.     public employee(String username, String department) {   
    61.         User user = new User();   
    62.         user.setName(username);   
    63.         user.setDepartment(department);   
    64.         userDao.addUser(user);   
    65.     }   
    66. }   
    67.   
    68. public class TaskService {   
    69.     private TaskDao taskDao;   
    70.     public void setTaskDao(TaskDao taskDao) { this.taskDao = taskDao }   
    71.     public applyTask(String taskName, User user) {   
    72.         Task task = new Task();   
    73.         task.setName(taskName);   
    74.         task.setUser(user);   
    75.         taskDao.addTask(task);   
    76.     }   
    77. }   
    78.   
    79. public class DepartmentService {   
    80.     private UserDao userDao;   
    81.     public void setUserDao(UserDao userDao) { this.userDao = userDao; }   
    82.     private UserService userService;   
    83.     public void setUserService(UserService userService) { this.userService = userService; }   
    84.     public int totalSalary(String department) {   
    85.         ......   
    86.     }   
    87.     ......     
    88. }    
    89.   
    90. # omit IoC Container weaving configuration's file  


    Java版本的实现代码大家都比较熟悉,因此绝大部分代码都省略了。Java版本需要3个持久对象,3个映射XML文件,3个DAO接口和实现类,4个Service和实现类,和一个IoC的bean组装文件,总共21个文件,全部逻辑写完整,代码行数至少上千行。

    通过对比,我们可以看到Java比较流行的实现是贫血的模型,按照面向对象的基本原则,对象的状态应该和它的行为封装在一起,因此Java多出来的这些XXXService是一些从纯理论角度而言应该放入其相应的持久对象中去。但是Java实现充血模型从技术上有一定的难度,如何Service方法挪入到持久对象中呢?如何解决Dao的注入问题?如何解决domain logic方法的事务封装问题?前者可以通过AspectJ的静态织入来解决,后者也许可以通过织入或者annotation声明来解决。但不管怎么说,Java从技术上很难实现充血模型,而且即使实现充血模型,也会导致一个Java类好几百行代码的状况,其代码的可阅读性,模块解藕能力都会变得很差,因此我们认为Java不适合充血模型,在表达复杂的业务逻辑的能力上,Java要比ruby差很多:

    结论:
    对于Java来说,更加适合采用贫血的模型,Java比较适合于把一个复杂的业务逻辑分离到n个小对象中去,每个小对象描述单一的职责,n个对象互相协作来表达一个复杂的业务逻辑,这n个对象之间的依赖和协作需要通过外部的容器例如IoC来显式的管理。但对于每个具体的对象来说,他们毫无疑问是贫血的。

    这种贫血的模型好处是:
    1、每个贫血对象职责单一,所以模块解藕程度很高,有利于错误的隔离。
    2、非常重要的是,这种模型非常适合于软件外包和大规模软件团队的协作。每个编程个体只需要负责单一职责的小对象模块编写,不会互相影响。

    贫血模型的坏处是:
    1、由于对象状态和行为分离,所以一个完整的业务逻辑的描述不能够在一个类当中完成,而是一组互相协作的类共同完成的。因此可复用的颗粒度比较小,代码量膨胀的很厉害,最重要的是业务逻辑的描述能力比较差,一个稍微复杂的业务逻辑,就需要太多类和太多代码去表达(针对我们假定的这个简单的工时管理系统的业务逻辑实现,ruby使用了50行代码,但Java至少要上千行代码)。
    2、对象协作依赖于外部容器的组装,因此裸写代码是不可能的了,必须借助于外部的IoC容器。

    对于Ruby来说,更加适合充血模型。因为ruby语言的表达能力非常强大,现在用ruby做企业应用的DSL是一个很热门的领域,DSL说白了就是用来描述某个行业业务逻辑的专用语言。

    充血模型的好处是:
    1、对象自洽程度很高,表达能力很强,因此非常适合于复杂的企业业务逻辑的实现,以及可复用程度比较高。
    2、不必依赖外部容器的组装,所以RoR没有IoC的概念。

    充血模型的坏处是:
    1、对象高度自洽的结果是不利于大规模团队分工协作。一个编程个体至少要完成一个完整业务逻辑的功能。对于单个完整业务逻辑,无法再细分下去了。
    2、随着业务逻辑的变动,领域模型可能会处于比较频繁的变动状态中,领域模型不够稳定也会带来web层代码频繁变动。

  • 相关阅读:
    java Object类是可以接收集合类型的
    java.lang.String中[ "张飞"+1+1 ] 和 [ "张飞"+(1+1) ]
    AFL Fuzz入门
    [转载]linux与grep
    linux下安装clamav
    [转载]Linux连续执行多条命令
    [转载]linux下各文件夹的结构说明及用途介绍
    [转载]linux常用命令
    [转载]Ubuntu 16.04 蓝屏解决方案
    pycharm修改python版本
  • 原文地址:https://www.cnblogs.com/sunwei2012/p/1636766.html
Copyright © 2020-2023  润新知