再论abstract class# Java - 爪哇娇娃
z*3
1 楼
之前说过我对abstract class的看法,倒是引来不少非议
尤其是有些人居然举出了例子,好,我们就从这个例子开始
有人说在这种情况下要使用abstract class
比如一个animal,有walk和sing方法
那么代码就是
public abstract class Animal{
public void walk(){System.out.println("walk")};
public abstract void sing();
}
然后对于具体的实现类,比如Cat,有如下实现
public class Cat extends Animal{
public void sing(){
System.out.println("cat sing");
}
}
这样一个类,好处就是“便于扩展”等等
OK,那么我们就从这个例子出发,说说为什么在j2ee环境中,我们不这么做
然后说说会怎么做
首先,在j2ee的环境中,关于animal这种实体
我们会在各个层面建立entity
比如在db层面建立一个表,叫做animal,然后有一个cat记录
然后通过orm,建立起一个dto之类的玩意
而这个dto会是这个样子的
public class Animal{
private String id;
private String name;
public void setId(String id){ this.id = id;}
public void setName(String name){this.name = name;}
public String getId(){return id;}
public String getName(){return name;}
}
或者是一个xml,如果你是在web service的环境中,你需要这样的一个xml
***
***
等等
注意到没有?这个animal没有任何的动作和行为
有的只是attributes,只是属性,而没有任何的动作
这就是mvc里面的model,也就是m
那么动作怎么办?
动作交给controller去办
在哪里写动作代码呢?
在business tier
然后动作会被封装在一些类似controller之类的class里面
那至于这个class是什么名字,要看情况
如果是tomcat,那么这个动作类就叫做AnimalServlet
如果是struts,那么动作类就叫做AnimalAction & AnimalService
如果是spring,那么有可能就叫做AnimalHandler
如果是ejb容器,那么有可能就叫做AnimalSessionBean
如果是用了jms,那么多半是叫做AnimalMessageBean
etc.,总之不会只叫做Animal,这是极其业余的起名方式
而对于animal的walk和sing
会这样做
public class AnimalHandler{
public void walk(Animal animal){
//这里实现方法体
System.out.println(animal.getName + "is walking");
}
public void sing(Animal animal){
System.out.println(animal.getName + "is singing");
}
}
而不是
public class AnimalHandler{
public void walk(Animal animal){
animal.walk();
}
public void sing(Animal animal){
animal.sing();
}
}
这样做
有什么好处呢?好处就是当涉及到不同实体之间关系的时候
比如bite(Human human)这个方法
如果用最开始的方式
那么将会建立一个从Animal到Human两个实体之间的依赖
因为要在Animal这个class中加上
animal.bite(Human human){...}这个方法
而如果用常见的j2ee的方式,就可以剥离这种实体之间的依赖
只需要在AnimalHandler里面加上
public void bite(Animal animal, Human human){
System.out.println(animal.getName() +" bites "+human.getName());
}
使得Animal和Human这两个class相互独立
而仅仅用Business tier的类来管理他们之间的逻辑联系
实体的管理交给persistent tier的工具去做
什么是business tier的工具呢?
各种app server, spring framework等
什么是persistent tier的工具呢?
db啊,还有hibernate等orm的framework
通过这种方式,可以集中管理实体的各种行为,也就是业务逻辑
而实体仅剩下毫无动作的各种属性,以及简单读取设定属性的setter/getter方法
而所有复杂的行为全部被剥离,这其实就是很早以前说的pojo
因为一般来说,行为的代码会有各种约束,常见的就是要继承各种interface
所以不满足pojo的定义,尤其是ejb, servlet这些,到今天servlet还需要继承接口
后来spring出现,使得行为代码也不需要有太多的约束,所以也变成pojo了
最后处理完这些实体之间的关系,再交给viewer去处理
一般这个时候会上一堆的模板,比如jsp, pdf之类的
那现在回到最初的问题,为什么abstract class不用?
那如果要用,在哪一层用呢?
如果是在persistent tier用的话
那么肯定要映射到数据库表中去
那么数据库中如何建立这种继承呢?
事实上数据库中并不建立继承关系
他们用外键来建立联系,比如有一个Object表
里面的记录有Real Object和Virtual Object
那如果他们用的是外键,映射到object中去
也是外键,也就是类似这种
public class Animal{
private ObjectId;
}
这种方式映射,而不是通过继承类的方式
比如public class Animal extends Object...
予以映射,这样做好处就是,当Object这个类发生改变了之后
我只需要重新编译Object这个class,而不用连同Animal一起重编译
所以在这一层,在剥离了动作行为之后的仅存的这种无行为能力的实体
我们不用abstract class,也用不到
除非你认为setter/getter方法需要用到abstract关键字
另外这里也会用上interface,用来定义setter/getter
但是毕竟不是abstract class
那么business tier呢?
我觉得这一层只是说有可能使用
但是也非必要,你要用可以用
但是我没有看出来有什么非用不可的必要
因为实体与实体之间的关联,你写到哪里不是一回事?
所谓的handler之类的方法,不过是给这些方法找一个地方放而已
大多数时候我都只有一层的继承,就是interface到具体的实现类
然后那一层的interface可以扩展成rmi之类的
最后是viewer
那么对于viewer来说
重要的是模板的建立,比如jsp,就是一个常见的模板
然后再套用上现有的api,直接生成输出
而大多数时候模板本身并不是java代码,而是各种什么格式
也就是文本的编辑工具而已
然后这一层需要跟美工人员配合完成,多数时候
作为开发人员,你只需要知道怎么把前面拿到的animal model给塞到模板中去就可以了
而且viewer工具多数是现成的,比如浏览器啊,pdf reader啊之类的
那么什么时候才需要用?
你写api的时候要用,因为你这个时候不能使用数据库等其它实体管理工具帮忙
所以你这个时候倒是有可能要用到这种实体的继承关系
但是这年头,谁还自己写api呢?
所以当我说到这一步的时候,有人一看就知道了,就猜到后面了,就走了
而很多人还是不知道,还在反复论证abstract class存在的必要性
我说的不是它不需要存在,而是我自己不会去写,仅此而已
另外,其实api中也在逐步剥离这种强关联
你看最早我们要启动一个Thread对象该怎么做?
需要thread.start();对不对?
这个就是典型的实体行为和实体本身绑定,是落后的生产关系,需要淘汰
所以后来有了一个东西叫做Executor,用来管理thread对象
所以只要你不去碰底层代码,其实很多时候abstract class是非必需的
至少你完全可以选择不用它,当然如果你写的是framework,server这种
我这些话都当我没说
看代码怎么看?
如果完全按照j2ee架构搭建的平台
看代码从核心的controller类开始看
也就是你需要找的,不是各个entity
而是service, servlet, handler之类的
同时每一层管理这些类得会有一个总的controller,比如dispatchservlet
不管是spring还是ejb container,都有类似的总controller
当然有时候这个总的controller不可见
比如在源代码里面,那上网搜索一下,看看是根据哪个配置文件做的配置
然后通过阅读配置文件,可以很轻松地定位到具体的实现类
最常见的就是servlet的那个web.xml了
或者是struts里面的strutsConfig.xml
或者是spring的application.xml
或者是ejb-jar.xml,大部分都是xml
比较新一点的j2ee代码多数可以通过xml直接定位
如果是最新用annotation的话,看xml定位到哪一个包里面去
然后通过阅读annotation来找,这个自由度很大
不过也不难,因为每一层的annotation是不一样的
比如spring的@controller, @service, @repository
哪怕这三个实际上都是@component,但是建议你不要用这个annotation
而每一层的代码也会集中存放,比如action就会放在一起
handler也会放在一起,service也会放在一起,ejb也会放在一起
dao也会放在一起,etc.
然后同一层内部也可以继续划分模块,按层次找也很容易定位
所以如何组织代码存放结构也是很重要的活
遇到一个狗屁不通的架构师,结构不清,可以把下面的人搞死
所以我从来都认为结构是第一优先,其它的都是次要
没有结构,其它无从谈起,大多数时候项目失败都是因为结构混乱
写到后面,你中有我我中有你,然后天天吵架扯皮
最后一拍两散,项目失败,所有人都隔屁
说来说去最后还是回到结构上去,最根本的东西
而这个结构一定是从大而小,先说整体结构再说部分,最后才是具体的某一个类
而不是脱离实际应用大谈简单例子,简单的东西多了,但是日常不用的也很多,比如
transient
所以哪怕是最基本的类的命名,包的结构,等都有诸多讲究,还要配套文档
好的代码,你读了第一行就知道作者要干什么,定位在哪一个tier以及layer上
然后再快速定位方法
所以当你看到public class MyApplet extends JApplet implements MouseListener,
Runnable
的时候,你不需要看代码都应该能够感觉出来,写出这种代码的作者是头猪
脑子里完全没有层次感,写的东西一团浆糊
类似的,对于spring,看到@Service, @Controller同时出现在一个类里面
差不多就可以骂人了,不过j2ee还好,因为j2ee这么多年,不停的努力
所以结构大部分都比较规范,了解了层次结构之后
很容易定位层次对应的包,再通过包定位具体的实现类,再定位最后的方法
都很成熟了,但是在swing等组件里面,那垃圾就多了
第二个例子
template method
这个例子也是有问题的
因为对于一个interface来说
如果你要写一个template class
那么建议你把所有的method全部实现
不要只实现一半半
当然你会告诉我,剩下的各个类不一样
那我们来做简单分析
assume你有100个情况
除非100个情况里面,所有的方法的实现
任意两个类都不一样,你才不需要全部实现
否则我建议你做template methods的时候
把所有最有可能发生的方法给实现了
然后再各个情况单独继承
但是留着这个最有可能的方法为concrete
也就是不要abstract掉
这样在绝大多数时候,你会发现,你压根不需要去继承任何东西
这样做岂不是更简单?
这也是为什么api里面JApplet, Applet, Object, Thread等
可以abstract的class全部都concrete掉的原因
最后说说我怎么看abstract class
其实没有abstract class
有的只是abstract的class
java所有的结构都只有一个东西,就是class
interface是什么?是完全abstract的class
enum是什么?是有限个的class
其实所有的东西都是class
而abstract class是被abstract关键字所修饰的class
abstract的意思就是不可实例化的意思
就是不让你new的意思,就这么简单
就跟final class是一个意思
好,如果有人问我什么是final class,我可以解释
但是估计我无法举例
又回到那个经典的回答
你翻开api,到处都是final
嗯,但是我不用
这一次你还打算告诉我final class存在的必要性吗?
尤其是有些人居然举出了例子,好,我们就从这个例子开始
有人说在这种情况下要使用abstract class
比如一个animal,有walk和sing方法
那么代码就是
public abstract class Animal{
public void walk(){System.out.println("walk")};
public abstract void sing();
}
然后对于具体的实现类,比如Cat,有如下实现
public class Cat extends Animal{
public void sing(){
System.out.println("cat sing");
}
}
这样一个类,好处就是“便于扩展”等等
OK,那么我们就从这个例子出发,说说为什么在j2ee环境中,我们不这么做
然后说说会怎么做
首先,在j2ee的环境中,关于animal这种实体
我们会在各个层面建立entity
比如在db层面建立一个表,叫做animal,然后有一个cat记录
然后通过orm,建立起一个dto之类的玩意
而这个dto会是这个样子的
public class Animal{
private String id;
private String name;
public void setId(String id){ this.id = id;}
public void setName(String name){this.name = name;}
public String getId(){return id;}
public String getName(){return name;}
}
或者是一个xml,如果你是在web service的环境中,你需要这样的一个xml
等等
注意到没有?这个animal没有任何的动作和行为
有的只是attributes,只是属性,而没有任何的动作
这就是mvc里面的model,也就是m
那么动作怎么办?
动作交给controller去办
在哪里写动作代码呢?
在business tier
然后动作会被封装在一些类似controller之类的class里面
那至于这个class是什么名字,要看情况
如果是tomcat,那么这个动作类就叫做AnimalServlet
如果是struts,那么动作类就叫做AnimalAction & AnimalService
如果是spring,那么有可能就叫做AnimalHandler
如果是ejb容器,那么有可能就叫做AnimalSessionBean
如果是用了jms,那么多半是叫做AnimalMessageBean
etc.,总之不会只叫做Animal,这是极其业余的起名方式
而对于animal的walk和sing
会这样做
public class AnimalHandler{
public void walk(Animal animal){
//这里实现方法体
System.out.println(animal.getName + "is walking");
}
public void sing(Animal animal){
System.out.println(animal.getName + "is singing");
}
}
而不是
public class AnimalHandler{
public void walk(Animal animal){
animal.walk();
}
public void sing(Animal animal){
animal.sing();
}
}
这样做
有什么好处呢?好处就是当涉及到不同实体之间关系的时候
比如bite(Human human)这个方法
如果用最开始的方式
那么将会建立一个从Animal到Human两个实体之间的依赖
因为要在Animal这个class中加上
animal.bite(Human human){...}这个方法
而如果用常见的j2ee的方式,就可以剥离这种实体之间的依赖
只需要在AnimalHandler里面加上
public void bite(Animal animal, Human human){
System.out.println(animal.getName() +" bites "+human.getName());
}
使得Animal和Human这两个class相互独立
而仅仅用Business tier的类来管理他们之间的逻辑联系
实体的管理交给persistent tier的工具去做
什么是business tier的工具呢?
各种app server, spring framework等
什么是persistent tier的工具呢?
db啊,还有hibernate等orm的framework
通过这种方式,可以集中管理实体的各种行为,也就是业务逻辑
而实体仅剩下毫无动作的各种属性,以及简单读取设定属性的setter/getter方法
而所有复杂的行为全部被剥离,这其实就是很早以前说的pojo
因为一般来说,行为的代码会有各种约束,常见的就是要继承各种interface
所以不满足pojo的定义,尤其是ejb, servlet这些,到今天servlet还需要继承接口
后来spring出现,使得行为代码也不需要有太多的约束,所以也变成pojo了
最后处理完这些实体之间的关系,再交给viewer去处理
一般这个时候会上一堆的模板,比如jsp, pdf之类的
那现在回到最初的问题,为什么abstract class不用?
那如果要用,在哪一层用呢?
如果是在persistent tier用的话
那么肯定要映射到数据库表中去
那么数据库中如何建立这种继承呢?
事实上数据库中并不建立继承关系
他们用外键来建立联系,比如有一个Object表
里面的记录有Real Object和Virtual Object
那如果他们用的是外键,映射到object中去
也是外键,也就是类似这种
public class Animal{
private ObjectId;
}
这种方式映射,而不是通过继承类的方式
比如public class Animal extends Object...
予以映射,这样做好处就是,当Object这个类发生改变了之后
我只需要重新编译Object这个class,而不用连同Animal一起重编译
所以在这一层,在剥离了动作行为之后的仅存的这种无行为能力的实体
我们不用abstract class,也用不到
除非你认为setter/getter方法需要用到abstract关键字
另外这里也会用上interface,用来定义setter/getter
但是毕竟不是abstract class
那么business tier呢?
我觉得这一层只是说有可能使用
但是也非必要,你要用可以用
但是我没有看出来有什么非用不可的必要
因为实体与实体之间的关联,你写到哪里不是一回事?
所谓的handler之类的方法,不过是给这些方法找一个地方放而已
大多数时候我都只有一层的继承,就是interface到具体的实现类
然后那一层的interface可以扩展成rmi之类的
最后是viewer
那么对于viewer来说
重要的是模板的建立,比如jsp,就是一个常见的模板
然后再套用上现有的api,直接生成输出
而大多数时候模板本身并不是java代码,而是各种什么格式
也就是文本的编辑工具而已
然后这一层需要跟美工人员配合完成,多数时候
作为开发人员,你只需要知道怎么把前面拿到的animal model给塞到模板中去就可以了
而且viewer工具多数是现成的,比如浏览器啊,pdf reader啊之类的
那么什么时候才需要用?
你写api的时候要用,因为你这个时候不能使用数据库等其它实体管理工具帮忙
所以你这个时候倒是有可能要用到这种实体的继承关系
但是这年头,谁还自己写api呢?
所以当我说到这一步的时候,有人一看就知道了,就猜到后面了,就走了
而很多人还是不知道,还在反复论证abstract class存在的必要性
我说的不是它不需要存在,而是我自己不会去写,仅此而已
另外,其实api中也在逐步剥离这种强关联
你看最早我们要启动一个Thread对象该怎么做?
需要thread.start();对不对?
这个就是典型的实体行为和实体本身绑定,是落后的生产关系,需要淘汰
所以后来有了一个东西叫做Executor,用来管理thread对象
所以只要你不去碰底层代码,其实很多时候abstract class是非必需的
至少你完全可以选择不用它,当然如果你写的是framework,server这种
我这些话都当我没说
看代码怎么看?
如果完全按照j2ee架构搭建的平台
看代码从核心的controller类开始看
也就是你需要找的,不是各个entity
而是service, servlet, handler之类的
同时每一层管理这些类得会有一个总的controller,比如dispatchservlet
不管是spring还是ejb container,都有类似的总controller
当然有时候这个总的controller不可见
比如在源代码里面,那上网搜索一下,看看是根据哪个配置文件做的配置
然后通过阅读配置文件,可以很轻松地定位到具体的实现类
最常见的就是servlet的那个web.xml了
或者是struts里面的strutsConfig.xml
或者是spring的application.xml
或者是ejb-jar.xml,大部分都是xml
比较新一点的j2ee代码多数可以通过xml直接定位
如果是最新用annotation的话,看xml定位到哪一个包里面去
然后通过阅读annotation来找,这个自由度很大
不过也不难,因为每一层的annotation是不一样的
比如spring的@controller, @service, @repository
哪怕这三个实际上都是@component,但是建议你不要用这个annotation
而每一层的代码也会集中存放,比如action就会放在一起
handler也会放在一起,service也会放在一起,ejb也会放在一起
dao也会放在一起,etc.
然后同一层内部也可以继续划分模块,按层次找也很容易定位
所以如何组织代码存放结构也是很重要的活
遇到一个狗屁不通的架构师,结构不清,可以把下面的人搞死
所以我从来都认为结构是第一优先,其它的都是次要
没有结构,其它无从谈起,大多数时候项目失败都是因为结构混乱
写到后面,你中有我我中有你,然后天天吵架扯皮
最后一拍两散,项目失败,所有人都隔屁
说来说去最后还是回到结构上去,最根本的东西
而这个结构一定是从大而小,先说整体结构再说部分,最后才是具体的某一个类
而不是脱离实际应用大谈简单例子,简单的东西多了,但是日常不用的也很多,比如
transient
所以哪怕是最基本的类的命名,包的结构,等都有诸多讲究,还要配套文档
好的代码,你读了第一行就知道作者要干什么,定位在哪一个tier以及layer上
然后再快速定位方法
所以当你看到public class MyApplet extends JApplet implements MouseListener,
Runnable
的时候,你不需要看代码都应该能够感觉出来,写出这种代码的作者是头猪
脑子里完全没有层次感,写的东西一团浆糊
类似的,对于spring,看到@Service, @Controller同时出现在一个类里面
差不多就可以骂人了,不过j2ee还好,因为j2ee这么多年,不停的努力
所以结构大部分都比较规范,了解了层次结构之后
很容易定位层次对应的包,再通过包定位具体的实现类,再定位最后的方法
都很成熟了,但是在swing等组件里面,那垃圾就多了
第二个例子
template method
这个例子也是有问题的
因为对于一个interface来说
如果你要写一个template class
那么建议你把所有的method全部实现
不要只实现一半半
当然你会告诉我,剩下的各个类不一样
那我们来做简单分析
assume你有100个情况
除非100个情况里面,所有的方法的实现
任意两个类都不一样,你才不需要全部实现
否则我建议你做template methods的时候
把所有最有可能发生的方法给实现了
然后再各个情况单独继承
但是留着这个最有可能的方法为concrete
也就是不要abstract掉
这样在绝大多数时候,你会发现,你压根不需要去继承任何东西
这样做岂不是更简单?
这也是为什么api里面JApplet, Applet, Object, Thread等
可以abstract的class全部都concrete掉的原因
最后说说我怎么看abstract class
其实没有abstract class
有的只是abstract的class
java所有的结构都只有一个东西,就是class
interface是什么?是完全abstract的class
enum是什么?是有限个的class
其实所有的东西都是class
而abstract class是被abstract关键字所修饰的class
abstract的意思就是不可实例化的意思
就是不让你new的意思,就这么简单
就跟final class是一个意思
好,如果有人问我什么是final class,我可以解释
但是估计我无法举例
又回到那个经典的回答
你翻开api,到处都是final
嗯,但是我不用
这一次你还打算告诉我final class存在的必要性吗?