专注于互联网--专注于架构

最新标签
网站地图
文章索引
Rss订阅

首页 »项目管理 » 敏捷开发:敏捷开发中要慎用继承 »正文

敏捷开发:敏捷开发中要慎用继承

来源: 发布时间:星期五, 2009年1月9日 浏览:27次 评论:0
  举例

  这是个会议管理系统用来管理各种各样会议参和者信息数据库里面有个表Participants里面每条记录表示个参会者经常会发生用户误删掉某个参会者信息所以现在用户删除时并不会真删除那参会者信息而只是将该记录删除标记设为true24小时以后系统会自动将这条记录删除但是在这24小时以内如果用户改变主意了系统还可以将这条记录还原将删除标记设置为false

  请认真读下面代码: 

public DBTable {
protected Connection conn;
protected tableName;
public DBTable(String tableName) {
this.tableName = tableName;
this.conn = ...;
}
public void clear {
PreparedStatement st = conn.prepareStatement
("DELETE FROM "+tableName);
try {
st.executeUpdate;
}finally{
st.close;
}
}
public getCount {
PreparedStatement st = conn.prepareStatement
("SELECT COUNT(*) FROM"+tableName);
try {
ResultSet rs = st.executeQuery;
rs.next;
rs.getInt(1);
}finally{
st.close;
}
}
}
public ParticipantsInDB extends DBTable {
public ParticipantsInDB {
super("participants");
}
public void addParticipant(Participant part) {
...
}
public void deleteParticipant(String participantId) {
DeleteFlag(participantId, true);
}
public void restoreParticipant(String participantId) {
DeleteFlag(participantId, false);
}
private void DeleteFlag(String participantId, boolean b) {
...
}
public void reallyDelete {
PreparedStatement st = conn.prepareStatement(
"DELETE FROM "+tableName+" WHERE deleteFlag=true");
try {
st.executeUpdate;
}finally{
st.close;
}
}
public countParticipants {
PreparedStatement st = conn.prepareStatement(
"SELECT COUNT(*) FROM "+tableName+" WHERE deleteFlag=false");
try {
ResultSet rs = st.executeQuery;
rs.next;
rs.getInt(1);
}finally{
st.close;
}
}
}
  注意到countParticipants这个思路方法只计算那些deleteFlags为false记录也就是被删除那些参会者不被计算在内

  上面代码看起来还不错但却有个很严重问题什么问题?先看看下面代码:

ParticipantsInDB partsInDB = ...;
Participant kent = Participant(...);
Participant paul = Participant(...);
partsInDB.clear;
partsInDB.addParticipant(kent);
partsInDB.addParticipant(paul);
partsInDB.deleteParticipant(kent.getId);
.out.prln("There are "+partsInDB.getCount+ "participants");
  最后行代码会打印出"There are 1 participants"这样信息对不?错!它打印是"There are 2 participants"!最后是DBTable里面这个思路方法getCount,而不是ParticipantsInDBcountParticipantsgetCount点都不知道删除标记这回事它只是简单计算记录数量并不知道要计算那些真正有效参会者(就是删除标记为false)

  继承了些不合适(或者没用)功能

  ParticipantsInDB继承了来自DBTable思路方法比如clear和getCount对于ParticipantsInDB来讲clear这个思路方法确是有用:清空所有参会者但getCount就造成了点点小意外了:通过ParticipantsInDBgetCount这个思路方法时是取得participants这个表里面所有记录不管删除标记是true还是false而实际上没人想知道这个数据即使有人想知道这个思路方法也不应该叫做getCount这名字很容易就会跟“计算所有(有效)参会者数量”联系在

  因此ParticipantsInDB是不是真应该继承这个思路方法getCount呢?或者我们应该如何做比较恰当呢?

  它们的间是否真有继承关系?

  当我们继承了些我们不想要东西我们应该再 3想想:它们的间是不是真有继承关系?ParticipantsInDB必须是个DBTable吗?ParticipantsInDB希不希望别人知道它是个DBTable?

  实际上ParticipantsInDB描述是系统中所有参会者集合该系统可以是个单数据库也可以是多数据库也就是说这个类可以代表个数据库里个Participants表也可以代表两个数据库各自两个Participants表总和

  如果还不清楚我们就这样举例吧比如现在我们已经有了2000个参会者在两个数据库中存放其中数据库Aparticipants表里面存放了1000个参会者数据库Bparticipants这个表存放了1000个参会者DBTable顶多只能描述个数据库里面张表也就是1000个参会者而participants则可以完全描述这2000年参会者信息前面可以当作数据库数据表在系统中代表而后者表示应该包含更多业务逻辑个域对象(原谅这边我只能用域对象这样词来断开这样混淆)

  因此我们可以判断ParticipantsInDB跟DBTable的间不应该有什么继承关系ParticipantsInDB不能继承DBTable这个类了于是现在ParticipantsInDB也没有getCount这个思路方法了可是ParticipantsInDB还需要DBTable类里面其他思路方法啊那如何办?所以现在我们让ParticipantsInDB里面引用了个DBTable:    

public DBTable {
private Connection conn;
private String tableName;
public DBTable(String tableName) {
this.tableName = tableName;
this.conn = ...;
}
public void clear {
PreparedStatement st = conn.prepareStatement("DELETE FROM "+tableName);
{
st.executeUpdate;
}finally{
st.close;
}
}
public getCount {
PreparedStatement st = conn.prepareStatement("SELECT COUNT(*) FROM "+tableName);
try {
ResultSet rs = st.executeQuery;
rs.next;
rs.getInt(1);
}finally{
st.close;
}
}
public String getTableName {
tableName;
}
public Connection getConn {
conn;
}           
}             
public ParticipantsInDB {
private DBTable table;
public ParticipantsInDB {
table = DBTable("participants");
}
public void addParticipant(Participant part) {
...
}
public void deleteParticipant(String participantId) {
DeleteFlag(participantId, true);
}
public void restoreParticipant(String participantId) {
DeleteFlag(participantId, false);
}
private void DeleteFlag(String participantId, boolean b) {
...
}
public void reallyDelete {
PreparedStatement st = table.getConn.prepareStatement(
"DELETE FROM "+table.getTableName+"WHERE deleteFlag=true");
try {
st.executeUpdate;
}finally{
st.close;
}
}
public void clear {
table.clear;
}
public countParticipants {
PreparedStatement st = table.getConn.prepareStatement( "SELECT COUNT(*) FROM
  "+table.getTableName+" WHERE deleteFlag=false");
try {
ResultSet rs = st.executeQuery;
rs.next;
rs.getInt(1);
}finally{
st.close;
}
}
}
  ParticipantsInDB不再继承DBTable代替它里面有个属性引用了个DBTable对象然后这个DBTableclear, getConn, getTableName 等等思路方法

  代理(delegation)  

  其实我们这边可以看下ParticipantsInDBclear思路方法这个思路方法除了直接DBTableclear思路方法以外什么也没做或者说ParticipantsInDB只是做为个中间介让外界DBTable思路方法我们管这样传递中间介叫“代理(delegation)”      

  现在的前有bug那部分代码就编译不过了:

ParticipantsInDB partsInDB = ...;
Participant kent = Participant(...);
Participant paul = Participant(...);
partsInDB.clear;
partsInDB.addParticipant(kent);
partsInDB.addParticipant(paul);
partsInDB.deleteParticipant(kent.getId);
//编译出错:在ParticipantsInDB里面已经没有getCount这个思路方法了!
.out.prln("There are "+partsInDB.getCount+ "participants");
  整理总结下:首先我们发现ParticipantsInDB 和 DBTableIn的间没有继承关系然后我们就将“代理”来取代它们继承“代理”优点就是我们可以控制DBTable哪些思路方法可以“公布(就是设为public)”(比如clear思路方法)如果我们用了继承我们就没得选择DBTable里面所有public思路方法都要对外公布!

  抽取出父类中没必要功能

  现在我们来看下另个例子假定个Component代表个GUI对象比如按钮或者文本框的类请认真阅读下面代码:

abstract Component {
boolean isVisible;
posXInContainer;
posYInContainer;
width;
height;
...
abstract void pa(Graphics graphics);
void Width( Width) {
...
}
void Height( Height) {
...
}
}
Button extends Component {
ActionListener listeners;
...
void pa(Graphics graphics) {
...
}
}
Container {
Component components;
void add(Component component) {
...
}
}
  假定你现在要写个时钟clock组件它是个有时分针在转动圆形每次更新时针跟分针位置来显示当前时间这也是个GUI组件所以我们同样让它继承自Component类: ClockComponent extends Component {
...
void pa(Graphics graphics) {
//根据时间绘制当前钟表图形
}
}


  现在我们有个问题了:这个组件应该是个圆形但是它现在却继承了Componentwidth跟height属性也继承了Width 和 Height这些思路方法而这些东西对个圆形东西是没有意义

  当我们让个类继承另个类时我们需要再 3想想:它们的间是否有继承关系?ClockComponent是个Component吗?它跟其他Compoent(比如Button)是吗?

  跟ParticipantsInDB那个案例相反我们不得不承认ClockComponent确实也是个Component否则它就不能像其他组件那样放在个Container中因此我们只能让它继承Component类(而不是用“代理”)

  它既要继承Component又不要width height Width 和 Height这些我们只好将这 4样东西从Component里面拿走而事实上它也应该拿走已经证明了并不是所有组件都需要这 4样东西(至少ClockComponent不需要)

  如果个父类描述东西不是所有子类共有那这个父类设计肯定不是个好设计

  我们有充分理由将这些移走

  只是如果我们从Component移走了这 4样东西那原来那些类比如Button就没了这 4样东西而它确实又需要这些(我们假定按钮是方形)

  个可行方案是创建个RectangularComponent类里面有widthheightWidth和Height这 4样然后让Button继承自这个类:

abstract Component {
boolean isVisible;
posXInContainer;
posYInContainer;
...
abstract void pa(Graphics graphics);
}
abstract RectangularComponent extends Component {
width;
height;
void Width( Width) {
...
}
void Height( Height) {
...
}
}
Button extends RectangularComponent {
ActionListener listeners;
...
void pa(Graphics graphics) {
...           }
}
ClockComponent extends Component {
...
void pa(Graphics graphics) {
//根据时间绘制当前钟表图形
}         
}
  这并不是唯可行思路方法个可行思路方法是创建个RectangularDimension这个类持有这 4个功能然后让Button去代理这个类:

abstract Component {
boolean isVisible;
posXInContainer;
posYInContainer;
...
abstract void pa(Graphics graphics);
}
RectangularDimension {
width;
height;
void Width( Width) {
...
}
void Height( Height) {
...
}
}
Button extends Component {
ActionListener listeners;
RectangularDimension dim;
...
void pa(Graphics graphics) {
...
}
void Width( Width) {
dim.Width(Width);
}
void Height( Height) {
dim.Height(Height);
}
}
ClockComponent extends Component {
...
void pa(Graphics graphics) {
//根据时间绘制当前钟表图形
}
}


  整理总结

  当我们想要让个类继承自另个类时我们定要再 3检查:子类会不会继承了些它不需要功能(属性或者思路方法)?如果是我们就得认真再想想:它们的间有没有真正继承关系?如果没有就用代理如果有将这些不用功能从基类转移到另外个合适地方去

  引述

  里斯科夫替换原则(LSP)表述:Subtype must be substitutable for their base types. 子类应该能够代替父类功能或者直接点说我们应该做到将所有使用父类地方改成使用子类后对结果点影响都没有或者更直白点吧请尽量不要用重载重载是个很坏很坏主意!更多信息可以去:

  http://www.objectmentor.com/resources/articles/lsp.pdf.



  http://c2.com/cgi/wiki?LiskovSubstitutionPrinciple.

  Design By Contract是个跟LSP有关东西它表述说我们应该测试我们所有假设更多信息:http://c2.com/cgi/wiki?DesignByContract.

  系列PDF下载:

  敏捷开发必要窍门技巧:http://www.blogjava.net/Files/Wingel/%E6%95%8F%E6%8D%B7%E5%BC%80%E5%8F%91%E7%9A%84%E5%BF%85%E8%A6%81%E6%8A%80%E5%B7%A7%E5%AE%8C%E6%95%B4%E7%89%88.rar



0

相关文章

读者评论

发表评论

  • 昵称:
  • 内容: