领域对象模型(domain object model)
在Play程序中,模型(model)占据了核心地位。它是程序操作的信息的特定领域的表现方式。
Martin Fowler这样定义模型:
负责表达业务概念,业务状态信息以及业务规则。尽管保存业务状态的技术细节是有基础设施层实现的,但是反应业务情况的状态是有本层控制并且使用的。领域层是业务软件的核心。
Java中有一个常见的反模式:仅仅把模型当作一个个的简单的Java Bean,里面就只有一些字段和getter/setter,然后把业务逻辑代码放到一个Service层中,在Service层中处理模型对象。
Martin fowler之后把这种反模式称为 贫血模型:
贫血模型一个明显的特征是它仅仅是看上去和领域模型一样,都拥有对象、属性、对象间通过关系关联。但是当你观察模型所持有的业务逻辑时,你会发现,贫血模型中除了一些getter和setter方法,几乎没有其他业务逻辑。这种情况事实上是由一些设计规则中(design rules)规定不要把业务逻辑放到“领域模型”中造成的,取而代之的是把所有的业务逻辑都封装到众多service中。这些service对象在“领域对象”(领域层)之上形成一个service层,而把“领域对象”当做数据使用。
贫血模型从根本上就违背了面向对象设计将属性与操作融合的思想。贫血模型就是我们纯粹的面向对象忠实者(比如我和Eric)从Smalltalk早期就极力反对的面向过程化设计的风格。更糟糕的是,很多人认为贫血模型就是真正的面向对象,进而也就完全领悟不到面向对象设计的真谛。
(注:这里引用的Martin fowler的两段话的翻译,均来自http://www.turingbook.com/article/details/25)
实际上想在Java中实现非贫血的模型,有时候是一件很困难的事情(因为Java的语法限制)。Play是通过对类进行增强而实现的。
属性(Properties)模拟
如果我们去看Play的示例程序,就会发现很多类都声明了public的变量。如果你是一个有经验的Java程序员,看到这一定会觉得不可思议。如果在Java(以及其它面向对象语言)中,把字段声明为private并且提供访问器和修改器才是最佳实践。因为“封装”是面向对象中的一个重要观念。
Java没有真正的内置的属性定义系统。它使用了一个命名规范叫Java Bean: Java对象的一个属性,即是一对getXxx/setXxx方法。如果这个属性是只读的,那就只提供一个getter.
虽然这套系统可以工作,但写起来非常繁琐。每一个属性,你都需要先声明一个private的变量,再写两个方法。而且,大多数的getter和setter都是非常简单而且都一样:
private String name;
public String getName() {
return name;
}
public void setName(String value) {
name = value;
}
Play的Model系统则可以自动生成这种模式,让我们的代码看起来更干净一些。实际上所有的public变量都会变成属性。这里有个约定:一个类所有的 public, non-static, non-final 字段,都将被看作一个属性。
例如,当我们声明了一个类:
public class Product {
public String name; public Integer price; }
实际上被载入的类会变成这样:
public class Product {
public String name;
public Integer price;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getPrice() {
return price;
}
public void setPrice(Integer price) {
this.price = price;
}
}
当你想访问一个属性时,你可以直接写成:
product.name = "My product";
product.price = 58;
在载入时,它将被替换为:
product.setName("My product");
product.setPrice(58);
警告!
如果你依赖于Play的自动生成,你没有办法直接使用getter和setter来访问属性,因为这些方法是在运行时才生成的。所以,当你在代码中调用getter/setter,编译器会提醒你找不到该方法。
当然,你也可以自己定义getter和setter,此时play会使用你自己定义的方法。
所以,如果想来保护Product类的price属性,我们可以写成:
public class Product {
public String name;
public Integer price;
public void setPrice(Integer price) {
if (price < 0) {
throw new IllegalArgumentException("Price can’t be negative!");
}
this.price = price;
}
}
如果我们给它赋一个负值,将会抛出一个异常:
product.price = -10: // Oops! IllegalArgumentException
Play将总是使用已经存在的getter和setter。看以下代码:
@Entity
public class Data extends Model {
@Required
public String value;
public Integer anotherValue;
public Integer getAnotherValue() {
if(anotherValue == null) {
return 0;
}
return anotherValue;
}
public void setAnotherValue(Integer value) {
if(value == null) {
this.anotherValue = null;
} else {
this.anotherValue = value * 2;
}
}
public String toString() {
return value + " - " + anotherValue;
}
}
从另一个类中进行判断:
Data data = new Data();
data.anotherValue = null;
assert data.anotherValue == 0;
data.anotherValue = 4
assert data.anotherValue == 8;
一切运行正常。因为增强的类遵守了Java Bean的约定,所以你在一个使用JavaBean规范的其它库里使用它,也没问题。
设置一个数据库来持久化你的模型对象
通常你都需要把模型对象中的数据持久化,最常用的方法就是使用数据库。
在开发阶段,我们可以快速启动一个嵌入式的内存数据库,或者一个文件数据库(在我们项目中的一个子目录中),把数据保存进去。我们可使用 配置数据库 来进行配置。
Play下载包中,已经包含了H2和MySQL数据库的JDBC驱动,位于 $PLAY_HOME/framework/lib/ 目录。如果你使用PostgreSQL或者Oracle,你必须把相应的JDBC驱动程序放进去,或者放到你的程序的 lib/ 目录中。
我们需要在conf/application.conf文件中,配置与数据库相关的连接信息,比如 db.url, db.driver, db.user和 db.pass:
db.url=jdbc:mysql://localhost/test
db.driver=com.mysql.jdbc.Driver
db.user=root
db.pass=
我们还可以使用 jpa.dialect 来配置一个JPA方言(dialect).
在代码中,我们可以从 play.db.DB 中取得 java.sql.Connection 对象,然后按标准方式使用它。
Connection conn = DB.getConnection();
conn.createStatement().execute("select * from products");
使用Hibernate来持久化模型对象
我们可以使用Hibernate(通过JPA)来自动将Java对象持久化到数据库中。
当我们把@javax.persistence.Entity注解加到某个Java对象上来定义JPA实体时,Play会自动开启JPA实体管理器。
@Entity
public class Product {
public String name;
public Integer price;
}
警告!
一个常见的错误是使用了Hibernate的@Entity注解。我们应该使用JPA中的@Entity才对,因为Play是通过JPA的API来使用Hibernate的。
然后我们可以通过 play.db.jpa.JPA 来取得一个EntityManager:
EntityManager em = JPA.em();
em.persist(product);
em.createQuery("from Product where price > 50").getResultList();
Play提供了一个非常好用的基类来处理JPA相关的操作。我们只需要继承 play.db.jpa.Model 即可。
@Entity
public class Product extends Model {
public String name;
public Integer price;
}
然后,我们就可以直接使用 Product 上的一些简单方法,来对它进行操作:
Product.find("price > ?", 50).fetch();
Product product = Product.findById(2L);
product.save();
product.delete();
支持多数据库
我们可以配置Play来使用多个(独立的)数据库。
在 conf/application.conf 中,以‘db.’开头的key是用来配置默认数据库的(比如 db.url )。如果想再配置一个其它的数据库,需要在‘db’后面增加一个下划线,和一个名称。如下:
db_other.url=jdbc:mysql://localhost/test
db_other.driver=com.mysql.jdbc.Driver
db_other.user=root
db_other.pass=
我们定义了一个名为‘other’的数据库。要对它进行其它的配置,则这样做:
db_other.jpa.dialect=<dialect>
我们可以在程序中,这样取得该数据库的连接:
Connection conn = DB.getDBConfig("other").getConnection()
DB.getDBConfig(configName) 返回一个含有与 play.db.DB 类中静态方法相同方法的对象。
保持模型为“无状态”(stateless)
Play设计成“完全无共享”的架构,以保证整个程序完全没有状态。这样做,可以让我们同时跑任意多个相同的程序,同时向外提供服务(集群)。
为了达到模型无状态,应该注意什么? 不要在Java heap中保存多个请求共享的模型对象
如果想在多个请求中共享数据,我们有多种选择:
- 如果数据很小很简单,直接把它保存在session或flash中。但要注意,每一个不能超过4KB,并且只能是String
- 把数据持久化(例如保存在数据库中)。例如,我们需要创建一个跨越多个请求的“wizard”对象:
- 当第一个请求到达时,初始化该对象,并把它保存在数据库中
- 把新创建的对象的ID值放到flash域
- 当新请求到来时,从flash域中取出该ID,再到数据库中取出对应的数据,更新并重新保存
- 把数据保存在非持久化设备(如Cache)中。例如,还是创建那个跨越多个请求的“wizard”对象:
- 当第一个请求到达时,初始化该对象,并把它保存在Cache中
- 把新创建的对象的ID值放到flash域
- 当新请求到来时,从flash域中取出该ID,再从Cache中取出对应的数据,更新并重新保存在cache中
- 当所有相关请求结束时,将该对象持久化(比如保存在数据库中)
虽然Cache是一个不可靠的保存处,但你把一个对象保存进去后,通常能够再取出来。在某些情况下,Cache是一个可以用来替代Java Servlet session的好选择。
继续讨论
现在我们将讲解如果使用 JPA persistence 来持久化模型。
领域对象模型(domain object model)的更多相关文章
- Qt 中的对象模型(Object Model)
原标题:Qt 中的对象模型(Object Model)90不太后,余生皆折腾 本节内容主要讲了 Qt 对象模型比标准 C++ 对象模型多了什么内容,并介绍了组成 Qt 对象模型基础的相关的类.最后说明 ...
- javascript快速入门之BOM模型—浏览器对象模型(Browser Object Model)
什么是BOM? BOM是Browser Object Model的缩写,简称浏览器对象模型 BOM提供了独立于内容而与浏览器窗口进行交互的对象 由于BOM主要用于管理窗口与窗口之间的通讯,因此其核心对 ...
- javascript快速入门13--BOM——浏览器对象模型(Browser Object Model)
什么是BOM? BOM是Browser Object Model的缩写,简称浏览器对象模型 BOM提供了独立于内容而与浏览器窗口进行交互的对象 由于BOM主要用于管理窗口与窗口之间的通讯,因此其核心对 ...
- BOM——浏览器对象模型(Browser Object Model)
什么是BOM? BOM是Browser Object Model的缩写,简称浏览器对象模型 BOM提供了独立于内容而与浏览器窗口进行交互的对象 由于BOM主要用于管理窗口与窗口之间的通讯,因此其核心对 ...
- DOM---文档对象模型(Document Object Model)的基本使用
一.DOM简介 文档对象模型(Document Object Model,简称DOM),是W3C组织推荐的处理可扩展置标语言的标准编程接口.它是一种与平台和语言无关的应用程序接口(API),它可以动态 ...
- selenium 的页面对象模型Page Object
页面对象模型page object model是selenium中的一种脚本设计模式,它能将页面元素封装起来,与业务操作分隔开, 在页面变化改变时,无需去修改业务逻辑代码,提高脚本维护的效率. 1.p ...
- BOM (Browser Object Model) 浏览器对象模型
l对象的角色,因此所有在全局作用域中声明的变量/函数都会变成window对象的属性和方法; // PS:尝试访问未声明的变量会抛出错误,但是通过查询window对象,可以知道某个可能未声明的对象是否存 ...
- 文本对象模型(Document Object Model)
本文内容: 1. 概述 2. DOM中的节点类型 3. DOM节点的选取 4. 存取元素属性 5.DOM元素的增删 6.小结 ★ 概述 文本对象模型(DOM)是一个能够让程序和脚本动态访问和更新文档内 ...
- 浏览器对象模型(BOM,Browser Object Model)
本文内容 1.概述 2.windows与document 3.对话框 4.定时调用 5.URL解析与访问历史 6.浏览器和屏幕信息 ★概述 &q ...
随机推荐
- Zynq学习笔记(1)
做硬件的第一个实例,一般当然是LED点灯啦~ 硬件:ZedBoard 软件:ISE 14.7 1.新建工程 2.选择平台 3.新建完成后,输入如下代码: `timescale 1ns / 1ps // ...
- 碰到一个在app内部浏览器锚点异常的问题
最近在做一个文章评论的功能,其中一个需求是:在提交完评论后,需要跳转到位于页面底部的评论区域,正常情况下location.href=http://m.hostname.cn/article#comme ...
- 重新发现梯度下降法--backtracking line search
一直以为梯度下降很简单的,结果最近发现我写的一个梯度下降特别慢,后来终于找到原因:step size的选择很关键,有一种叫backtracking line search的梯度下降法就非常高效,该算法 ...
- JDBC数据库编程基本流程
1.加载驱动类 Class.forName(""); 2.创建数据库连接 Connection con = DriverManager.getConnection(url, u ...
- 3D全景!这么牛!!
如果你用过网页版的百度地图,你大概3D全景图浏览是一种怎样的酷炫体验:在一个点可以360度环顾周围的建筑.景色,当然也可以四周移动,就像身临其境. 全景图共分为三种: ①球面全景图 利用一张全景图围成 ...
- 使用tomcat作为web应用容器时,启用新线程找不到Session的问题
今天做一个功能,为了快速响应前端,业务完成后,另起了一个线程做一些不影响业务的统计工作,然后立即将业务操作结果返回给前台. 结果在新线程里报空指针找不到request对象.检查了下,我们用的是stru ...
- Django项目--web聊天室
需求 做一个web聊天室,主要练习前端ajax与后台的交互: 一对一聊天和群组聊天 添加用户为好友 搜索并添加群组 管理员可以审批用户加群请求,群管理员可以有多个,群管理员可以删除,添加禁言群友 与聊 ...
- ExtendHelper
public static class ExtendHelper { /// <summary> /// 检查当前字符串是否符合某种格式 /// </summary> /// ...
- C#函数式程序设计之局部套用与部分应用
函数式设计的核心与函数的应用以及函数如何作为算法的基本模块有关.利用局部套用技术可以把所有函数看成是函数类的成员,这些函数只有一个形参,有了局部套用,才有部分应用.部分应用是使函数模块化成为可能的两个 ...
- Node.js面试题:侧重后端应用与对Node核心的理解
Node是搞后端的,不应该被被归为前端,更不应该用前端的观点去理解,去面试node开发人员.所以这份面试题大全,更侧重后端应用与对Node核心的理解. node开发技能图解 node 事件循环机制 起 ...