如何设计业务层?

liujiboy 2004-09-29 07:32:36
如果表示层是一个是显示一个查询的页面,如何设计对应的业务层呢?
为了显示的效率,不能把属性都取出来,只能是显示多少取多少,那么表示层要显示多少,业务层怎么可以知道?如果表示层的显示发生改变,必然导致业务层改变,那么表示层和业务层不就是耦合在一起了吗?

例如一个显示学生列表的页面,要显示姓名、学号。
服务层提供一个服务getAllStudents(),这个服务只返回包括姓名、学号的列表。

后来又要显示班级,那么就要改写这个getAllStudents()获取班级。

这不是表示层和服务的耦合吗?
如果直接传sql,情况更糟糕,这就意味着表示层需要知道持久层的情况!

如何解决这个问题?如何设计?
...全文
1165 94 打赏 收藏 转发到动态 举报
写回复
用AI写文章
94 条回复
切换为时间正序
请发表友善的回复…
发表回复
jiganghao 2004-10-22
  • 打赏
  • 举报
回复
If extensibility and flexibility are required, forget quick and dirty way. code to interface is one of the principles to stick to.
jiganghao 2004-10-22
  • 打赏
  • 举报
回复
service should be the facade between presentation and business tier. you action classes or other clients dont need anything besides it.

dao should be the interface between business and persistence tier. you business logic knows things below except it. when requirement changes, modify dao as required. coding of dao is simple, and not tedious if you can generate automatically (doable and preferrable cuz your dao must be a java bean).

remember in a system, Db shema is one of the least likily changed. if you change it often, go back to your data and domain object model for less perfect design.
liujiboy 2004-10-21
  • 打赏
  • 举报
回复
没有发言的人了?
lsqlister001 2004-10-21
  • 打赏
  • 举报
回复
学习
liujiboy 2004-10-17
  • 打赏
  • 举报
回复
如果写一个类
public class QueryStringConstants
{
public final STUDENTNAMES_QUERY="select name from student";
public final STUDENTIDS_QUERY="select id from student";
}
如果真的出现了模式改变的情况,修改这个文件就ok了。
如果改成了hibernate或者jdo,只要这个产品支持queryString那么就不会有太大的问题。
而如果是EJB,估计就会出较大的问题,但是EJB作为存储实在是个非常坏的主意,如果需要很多查询EJB显然不合适的,其查询能力太低,代价太高。我到是见过EJB系统中为了性能使用sql的情况。

另一种方式就是把query封装成为一个类。
liujiboy 2004-10-17
  • 打赏
  • 举报
回复
如果写一个类
public class QueryStringConstants
{
public final
}
fantasyCoder 2004-10-17
  • 打赏
  • 举报
回复
目前我的做法是Business层做为事务的边界,数据源由业务层的传到DAO层,DAO层的方法尽量原子化,
这样业务层的方法只要做组合使用DAO层的方法就可以了,我想无论如何,要做事务,Business层和DAO
层肯定要有偶合,Spring声明式事务应该能很好的解决,甚至是不同的数据源之间的事务处理,
DAO模式的缺点就是业务复杂的时候会造成业务层积聚膨胀,好象很少有方法去解决这个问题..
liujiboy 2004-10-17
  • 打赏
  • 举报
回复
QueryStringConstants类当然是不然客户知道的好了。可以将其作为Dao的一部分,不同的Dao实现可能有不同的QueryStringContstants。
实际上如果采用第二种方式,在很多情况下,只是用一个方法封装了一个queryString而已。
当然如同我在另一个论坛上说道的,如果数据存储用queryString,例如EJB,采用第一方式就要复杂很多了。


总结一句,我虽然提出了第一种方式,并且用queryString进行封装。但是我现在仍然倾向在项目中使用第二种方式,但是第二种方式显然有很多冗余。

各位的处理方式很可能和我的不相同,既不是第一种也不是第二种。我想知道各位是如何处理同类问题的。
fantasyCoder 2004-10-17
  • 打赏
  • 举报
回复
呵呵,看了你在另外的一个论坛上同样的帖子.

我想即使你构照了一个QueryStringConstants类,也最好注射到DAO层,
而不是做为Business方法的参数传递到DAO里,你想看到你的client关心
这个QueryStringConstants吗?
ChenBo_Etu 2004-10-16
  • 打赏
  • 举报
回复
pstmt = con.prepareStatement(sql);
pstmt.setString(1, student_class_id);
rs = pstmt.executeQuery();
以上是不是只对数据库做了预处理,还没有对库做实际操作,如果是我觉得上面的方法可行,如果不是我上面的方法就有点傻了
ChenBo_Etu 2004-10-16
  • 打赏
  • 举报
回复
student 表字段
student_id
student_name
student_age
student_sex
student_address
student_class_id

public class HashtableUtils
{
private Hashtable hashtable = new Hashtable();
public HashtableUtils() {
//hashtable = new Hashtable();
}

public synchronized final void putValue(String key,String value) {
if(key == null || key.equals("")){
return ;
}
if(value == null || value.equals("")) {
value = "";
}
try {
hashtable.put(key,value);
}
catch(NullPointerException e) {
System.out.println("key or value is null ");
e.printStackTrace();
}
}
public synchronized final String getValue(String key ) {
if(key == null || key.equals(""))
{
return "";
}
try {
return String.valueOf(hashtable.get(key));
}
catch(NullPointerException e)
{
e.printStackTrace();
return "";
}
}

public synchronized final void clearAll() {
try {
hashtable.clear();
}
catch(UnsupportedOperationException e) {
System.out.println("clear is not supported by this map");
e.printStackTrace();
}
}

public synchronized final int getSize() {
return hashtable.size();
}

public synchronized final void put(Object key,Object value) {
try {
hashtable.put(key,value);
}
catch(NullPointerException e) {
System.out.println("key or value is null ");
e.printStackTrace();
}
}
public synchronized final Object get(Object key ) {
Object keyword = hashtable.get(key);
return keyword;
}
public final ArrayList getElements() {
ArrayList elementsList = new ArrayList();
for (Enumeration e = hashtable.elements() ; e.hasMoreElements() ;) {
elementsList.add(e.nextElement());
}
return elementsList;
}

public final ArrayList getKeys() {
ArrayList keysList = new ArrayList();
for (Enumeration e = hashtable.keys() ; e.hasMoreElements() ;) {
keysList.add(e.nextElement());
}
return keysList;
}

public void putKey(String Key){
put(Key,Key);
}


public boolean exists(String key){
String value = this.getValue(key);
if(value==null||value.equals(""))
return false;
else return true;
}
}

public class student{
private ArrayList getAllStudents((HashtableUtils table) {
String student_class_id = table.getValue("student_class_id");
if(student_class_id==null||student_class_id.equals.(""))
return null;
String sql = "select student_id,student_name,student_age,student_sex,student_address where student_class_id = ?";
ArrayList arrayList;
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
HashtableUtils tab;
try {
con = ConnectionManager.getConnection();
pstmt = con.prepareStatement(sql);
pstmt.setString(1, student_class_id);
rs = pstmt.executeQuery();
while(rs.next()){
tab = new HashtableUtils();
if(table.exists("student_id"))
tab.put("student_id",rs.getString("student_id"));
if(table.exists("student_name"))
tab.put("student_name",rs.getString("student_name"));
if(table.exists("student_age"))
tab.put("student_age",rs.getString("student_age"));
if(table.exists("student_sex"))
tab.put("student_sex",rs.getString("student_sex"));
if(table.exists("student_address"))
tab.put("student_address",rs.getString("student_address"));
arrayList.add(tab);
}
pstmt.close();

}
catch( SQLException sqle ) {
System.err.println( "SQLException in saveToDb(): " + sqle );
}
finally {
try { con.close(); }
catch (Exception e) { e.printStackTrace(); }
}
return arrayList;
}
}
在表现层如果想显示所以人student_name,student_age,student_sex
HashtableUtils table= new HashtableUtils();
table.put("student_name");
table.put("student_age");
table.put("student_sex");
student stu=new student();
ArrayList list = stu.getAllStudents(table);
这样就得到了一个封装了student_name,student_age,student_sex的
HashtableUtils 列表
liujiboy 2004-10-16
  • 打赏
  • 举报
回复
补充一句,如果getStudentNamesAndIds要在Dao中复用,那么就可以成为一个Dao的接口。
liujiboy 2004-10-16
  • 打赏
  • 举报
回复
考虑一下两种设计DAO和Service的方法。
方法一:
public class StudentDao
{
public List getStudents(String queryString){
//find是已经构造好的查找方法
return find(queryString);
}
}
public class StudentService
{
public List getAllStudentNames()
{
return studentDao.getStudents("select name age from student");
}
public List getAllStudentIds()
{
return studentDao.getStudents("select id age from student");
}
}

方法二:
public class StudentDao
{
public List getStudentNames(){

return find("select name from student");
}
public List getStudentIds(){
//find是已经构造好的查找方法
return find("select id from student");
}

}
public class StudentService
{
public List getAllStudentNames()
{
return studentDao.getStudentNames();
}
public List getAllStudentIds()
{
return studentDao.getStudentIds();
}
}

两种方法各有优势。其中第一种方法简单,但是暴露了底层的实现细节。Service层的编写人员需要构造queryString,当然queryString不一定就是sql,也可能是项目组实现约定的queryString,Dao的编写者负责把这个queryString,变成操作数据的操作。
第二种编写方法很复杂,并且很多时候显得多余了。但是Service层不需要知道Dao是如何编写的,只要Dao提供了足够的接口。最容易出现的问题是,一旦Service层需要一个getStudentAges,而Dao没有编写,那么就惨了,这个时候Service层的编写人员只能等待Dao的编写者编写。

那种方式更好?我在网上看到的demo中两种方式都有采用,但是demo都是很小的东西,完全体现不出到底哪个有优势。以第一种方式来说,虽然Service层的编写人员需要构造一个让底层能够理解的queryString,但是造成的耦合并不是想象的如此严重。因为一旦数据访问发生改变,例如改变了数据库,Dao的编写者只要重新解释这个queryString就可以了。严格来说sql作为通用的queryString进行传递也不是不可以的,因为如何解释queryString完全是Dao编写者的问题,只是queryString设计得不好,可能Dao的解析工作很麻烦。另一种方法是将queryString变成一个通用的api,这样Dao的解析过程也比较容易,毕竟string解析不用到编译原理是不可能的,而设计好的api,其语义是非常明确的。
来看看第二种方式,虽然Service层和Dao层的耦合降低到了0,但是Dao可能没有提供足够的接口,这是严重的问题。另外Dao层虽然不用不用编写复杂的queryString解析,但是却需要编写更多的方法,这样Dao还是非常庞大。更重要的是在国内的环境下,软件分工并不明确,经常一个程序员既要写Service又要写Dao,第二种方式难免在这种情况下让程序员产生厌恶。

目前我的程序采用了第二种方式,但是由于显著的复杂性,正准备转换到第一种方式(我实际上是从view一直写到Dao,严格的分层,好痛苦)。

采用第一种方式,我准备设计一个BaseDao,包括基本的load,add,edit,delete和find操作。然后针对不同的情况实现其他的Dao,比如操作Student,那么就有StudentDao,但是查询操作不再提供各个方法只针对一些必要的操作封装。

例如
public class BaseDao{
public Object load(String id)
{
load obj;
}
public void add(Object obj)
{
add obj;
}
public void edit(Object obj)
{
edit obj;
}
public void delete()
{
delete obj;
}
public List find(String queryString)
{
find obj by queryString;
}
}
然后是我的studentDao
public class StudentDao
{
//用组合而不用继承,这样是有好处的,今后一旦改变数据库也是baseDao改变而已
private BaseDao baseDao;
setter and getter of baseDao
public Student loadStudent(String id)
{
return (Student)load(id);
}
public void addStudent(Student student)
{
baseDao.add(student);
}
public void editStudent(Student student)
{
baseDao.edit(student);
}
public void deleteStudent(Student student)
{
baseDao.deleteStudent(student);
}
//一些可能要重复使用的方法,通常是些查询操作,但是会用到多个地方,这时候就可以将其抽取出来放在Dao中,当然也也可以是直接的放在Service层就可以了。另外一些和Student相关的数据访问操作可能也放在下面。例如如下方法:
public boolean hasSelectedTheCourse(Student student,Course course)
{
检查student是不是已经选择了这个course。
}
}
看看service层的调用
public class Student
{
public void addCourse2Student(String studentId,String courseId)
{
Student student=studentDao.getStudent(studentId);
Course course=courseDao.getCourse(courseId);
if(studentDao.hasSelectedTheCourse(student,course))
throw new Exception();
student.getCourses().add(course);
studentDao.editStudent(student);
}
//下面是查询
public List getStudentNames()
{
return baseDao.find("select name from student");
}
public List getStudentNamesAndIds()
{
List list =baseDao.find("select name,id from student"); //返回一个Object[]的list
List returnList=new ArrayList();
for(int i=0;i<list.size();i++)
{
Object[] objs=(Object[])list.get(i);
String name=(String)objs[0];
String id=(String)objs[1];
NameAndIdVo nameAndId=new NameAndIdVo(name,id);
returnList.add(nameAndId);
}

}
}
大家看看这种使用方法有什么不足?
liujiboy 2004-10-16
  • 打赏
  • 举报
回复
在另一个论坛上就同一问题发出的讨论中,有朋友提出第一种方法,当student发生改变时需要修改service层的大量queryString,这些queryString的修改是费力的而且无法保证每个地方都能够修改到。
这好像是第二种方法存在的理由。但是理由不充分的。首先我不知道如果student发生改变,在第二种方式的情况下是不是要修改Dao层呢?答案是肯定的,我们需要修改Dao层。而这个修改也并不会比修改service层更省力。
而且queryString可以写在一个外部的配置文件中,通过查找配置文件得到我们需要的queryString。一旦结构发生改变,所有queryString都在一个文件中,难到不好修改呢?
最后,student的结构为什么要变呢?除非在设计解决,或者编码中。对于现有系统,除非是重要的问题,我们都不应该去更改数据库模式。这是显然的,因为数据库已经有了数据。修改数据库意味着转换数据,这个问题不是更复杂了,更应该避免的吗?

另一位朋友提到我的studentDao完全没有必要存在,因为studentDao不过是一种重复,我的studentService实际严重依赖baseDao。那么完全可以将baseDao提到studentService。我以前认为存在studentDao是有必要的,因为service层需要控制事务,而如果studentDao提供了原子操作,那么就可以对这些操作进行组合。而如果把studentDao中的方法都提到service,那么这种好处就没有了。因为每个service都是独立控制事务的。那么如果组合这些service方法来构造一个大的service方法就无法控制事务了。
例如
public class StudentService{
public Student getStudent(string id){
return (Student)baseDao.load(id);
}
public void editStudent(Student student){
//有事务处理
baseDao.edit(student);
}
public boolean hasSelectedTheCourse(Student student,Course course)
{
检查student是不是已经选择了这个course。
}
public void setCourse2Students(String ids[],Course course)
{
//有事务处理
for(int i=0;i<ids.length;i++)
{
Student student=getStudent(ids[i]);
if(hasSelectedTheCourse(student,course)
{
throw new Exception("Student has already selected the course");
}
student.setCourse(course);
editStudent(student);
}
}
}
按照事务的概念,当setCourse2Students执行过程中发生异常是,必须回滚所有的操作。但是由于editStudent独立提交了事务,因此在出错之前的都无法回滚!
而如果采用原子的Dao,就可以用下面的方式:
public void setCourse2Students(String ids[],Course course)
{
//有事务处理
for(int i=0;i<ids.length;i++)
{
Student student=studentDao.getStudent(ids[i]);
if(hasSelectedTheCourse(student,course)
{
throw new Exception("Student has already selected the course");
}
student.setCourse(course);
studentDao.editStudent(student);
}
}
由于studentDao没有控制事务,因此事务是可以回滚的。

但是如果使用了spring框架,用spring来控制事务,第一种写法就是可能是完全合适的。我没有做试验,但是spring的事务控制机制似乎就是这样的。
如果这样是成立的,那么studentDao中的crud操作就完全可以取消了。即使不采用spring框架,我们也完全可以采用其他方式来处理。
如此一来,Dao就瘦身为一个BaseDao了,而queryString写配置,并由BaseDao来解析。

Dao是不是应该这么瘦呢?我现在都有点动摇了。
这样看似乎真的没有多大问题,数据访问的更改顶多动到一个配置文件和一个BaseDao,难道会影响很多地方吗?
showerXP 2004-10-16
  • 打赏
  • 举报
回复
getAllStudents()搞到一个接口去。要想返回什么就是不同implements。
工厂模式确实可以实现。
liujiboy 2004-10-16
  • 打赏
  • 举报
回复
fantasyCoder(Attitude is everything)
你有一个误解,第一种方式不使用sql而是用一个通用的queryString,或者一个通用的query api。
Dao能够解析这种string。无论是用hibernate还是jdo,或者是其他的,这个queryString都可以不发生改变,尽量的设计到与平台无关。这样通过Dao来解析,如果是关系数据库,那么就转为sql,如果是hibernate那么转为hql,如果是jdo那么是jdo的。
一个string需要复杂的解析,因此通常可以设计为一个通用的api,然后按照不同的方式来解析。
如果有这样一个统一的能够解析成各种类型的查询语言,那么完全不会有什么问题了。

另外我说道sql可以做这种工作,因为sql是现在查询语言中最好的,语义最丰富的,资料也最全面的。
将sql转换为hibernate的hql也是完全可能的(既然hql可以转换为sql)。

我当然不觉得我在实际项目中会采取第一种方式,我还是会选第二种,而且在性能和模式上优先选择模式。但是如果不是项目而是想设计一个把事情变得更容易的框架能?如果设计一种结构在采用第一种方式的时候,能够方便的将查询转换成平台特有的语义,那么就值得这么做。
要知道hibernate的hql是有很多不同实现的,有db2的,oracle的和sql server的,这些是采取了不同的解析方式。然后当我写session.find(queryString)的时候,hibernate会进行这个转换工作。
那么为什么不能有一个通用的queryString,能够转换为sql,转换为hql或者其他,编写一次就能够在今后的很多项目中反复使用。

我认为这种东西是有价值的。但是要想在实际项目中搞这样一个东西不应该在项目中来搞,而应该提供一个这样的框架。
segl 2004-10-16
  • 打赏
  • 举报
回复
我觉得 liujiboy(liujiboy)的两种例子的出发点还是为了减少开发阶段及维护阶段工作中技术的实现难度。值得肯定的是liujiboy(liujiboy)的态度是值让人学习的。
但过多地考虑这这技术方面的问题,可能会忽略掉对层次清晰度及业务封装方面的更重要的方面。
TinyJimmy 2004-10-16
  • 打赏
  • 举报
回复
还是那句话, 关键在设计. 好设计的本身不会使程序结构和编写方式变得复杂而难懂. 相反优良设计能综合各个方面的因素(特别是用户业务的因素), 得到一个整体最优的结果.

我觉得目前的讨论限于技术实现上的讨论, 如果能把这个因素提升一下, 到业务级别会更有意义. 就如翻页来说, 如果我们有100000条记录, 客户根本不可能去看这100000条数据, 也许他能看200条, 是100000的汇总, 然后会去看某一个汇总结果下的50条明细. 而我们的开发人员通常都没有考虑到这一点, 只是从技术上的可实现性上去描述这个问题, 100000也不是很多. 现实中面对这个问题很多很多.

象hibernate这个工具没有怎么用过, 自己有一个类似的, 主要考虑到对象之间可关联性上自己写的. 仅仅是一个工具. 写得再好, 也不如去引导客户去使用一个好的功能来得直接.

如 segl(天猪行空) 说的, 楼主对程序的追求有些完美. 我认为性能和模式是有冲突的.
ww09124 2004-10-16
  • 打赏
  • 举报
回复
我觉的分离的本质是为了,利于重用和容易维护

其实主要还是在,业务中事物的抽象封装和组合方面
fantasyCoder 2004-10-16
  • 打赏
  • 举报
回复
http://joe.truemesh.com/squiggle/

看看sql API级的拼装,你能玩出更多的花样....

需求的变更想不修改代码是不可能的,只是关注点不同而已..
修改DAO层的代码或是Business层的代码也不会有太大的区别...
DAO层是关注底层处理的,而Business层关注的业务需求,要我选的
话我选第2种设计,Business层当然只希望一个明确的接口,取的想
要的数据,再说了,你将sql语句偶合到了Business层,用hibernate,或是jdo
你又能怎么?还不是要全部重写...

我想一个项目代码的成功取决于一个统一的设计,程序员
遵循一个相对合理的设计编码就没什么问题,还是那句话你所选的crud操作
都不是项目的关键.
加载更多回复(74)

50,549

社区成员

发帖
与我相关
我的任务
社区描述
Java相关技术讨论
javaspring bootspring cloud 技术论坛(原bbs)
社区管理员
  • Java相关社区
  • 小虚竹
  • 谙忆
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧