画圆画方的故事

这个故事最初是来自和发哥的一次聊天,他说了一些面向对象设计方面挺有意思的事情,包括Double Dispatch(下面会提到),我根据我自己的体会和思考,把这些零散的片段重新整理成一个小故事,欢迎感兴趣的同学一起讨论。

 

有一个苦逼的程序员,叫做小P。

 

有一天,老板给他传达了这样一个需求,根据用户不同的图像绘制事件,画出一个圆或者是画出一个方块来。

老板传达的图像绘制事件是这样的:

 

 

interface DrawEvent {
}

class RoundDrawEvent implements DrawEvent {
}

class RectangleDrawEvent implements DrawEvent {
}
 

 

小P说,这个问题很简单:

 

public class Drawer {
	public void draw(DrawEvent event) {
		if (event instanceof RoundDrawEvent) {
			// 画圆
		} else if (event instanceof RectangleDrawEvent) {
			// 画方
		} else {
			System.out.print("error");
		}
	}
}

 

这似乎没有任何难度,一下就做出来了,不过,用户心情一好,就要提新的需求,而苦逼的程序员面对用户新的需求,是不能退缩的。用户说,现在我要求系统还要支持三角形。

 

小P想到,如果我再在代码里面增加一个if-else分支,问题是可以解决,可是分支越来越多,代码越来越丑陋,如果我可以用一个Map来代替if-else完成选择的功能,岂不是可以让我原来的实现优雅一点

 

public class Drawer {
	private static Map<Class, IDrawer> DRAWER_MAP = new HashMap<Class, IDrawer>();
	static {
		DRAWER_MAP.put(RoundDrawEvent.class, new RoundDrawer());
		DRAWER_MAP.put(RectangleDrawEvent.class, new RectangleDrawer());
	}

	public void draw(DrawEvent event) {
		DRAWER_MAP.get(event.getClass()).draw();
	}
}

interface IDrawer {
	public void draw();
}

class RoundDrawer implements IDrawer {
	public void draw() {
		// 画圆
	}
}

class RectangleDrawer implements IDrawer {
	public void draw() {
		// 画方
	}
}

 

突然,一瞬间的火花,小P觉得如果用方法重载来代替if-else的工作,把变化的点转移到方法重载上,也可以做到

 

 

public class Drawer {
	public void draw(RoundDrawEvent event) {
		//画圆
	}
	
	public void draw(RectangleDrawEvent event) {
		//画方
	}
	
	public void draw(DrawEvent event) {
		System.out.print("error");
	}
}

 

 

可以,测试这个方法的时候,他傻眼了:

 

 

DrawEvent event = new RoundDrawEvent();
new Drawer().draw(event);

 

 

他发现每次输出的结果都是“error”!

而如果测试代码改成这样,却是正确的:

 

 

new Drawer().draw(new RoundDrawEvent());

 

 

这是怎么回事?

 

看来小P和很数苦逼程序员还是有点不一样,他喜欢尝试、喜欢思考,而且还特别喜欢研究,一查到底。

原来,在Java中,方法重载都是在编译期间确定的,对于编译期间draw方法的实参event,如果使用了DrawEvent这个接口来引用,那么结果就可想而知,去执行draw(DrawEvent event)这个方法了。

 

原因清楚了,接下去就不难想出解决办法:

既然方法的重载无法是动态的,那么我在调用这个重载了的方法之前,我就要给它传入一个在编译期就已经确定了具体类型的入参,把变化的点转移到对象的多态上

可是,DrawEvent接口里并没有提供可供外部因素参与和影响的变化点,如果它能够提供一个供外部注入行为的变化点,不就可以用多态来帮助我们了么:

 

 

interface DrawEvent {
	public void draw(Drawer drawer);
}

class RoundDrawEvent implements DrawEvent {
	public void draw(Drawer drawer) {
		drawer.draw(this);
	}
}

class RectangleDrawEvent implements DrawEvent {
	public void draw(Drawer drawer) {
		drawer.draw(this);
	}
}

这里我说明一下为什么要传入drawer参数,因为真正要画图的家伙,不是这个event,而是drawer,而这个event只不过是利用多态,起到了寻找那个合适的重载方法的作用

 

好,接下去再完成Drawer就可以了:

 

 

public class Drawer {
	public void draw(RoundDrawEvent event) {
		// 画圆
	}

	public void draw(RectangleDrawEvent event) {
		// 画方
	}

	public void draw(DrawEvent event) {
		event.draw(this);
	}
}

可以看到,其实这里的DrawEvent已经不纯粹了,不仅仅代表了事件本身,还作为一个行为的委托者,甄选具体要执行的行为,再把执行的任务交还给Drawer

 

 

这时,我用下面的办法测试这个方法的时候,结果就是正确的了:

 

 

DrawEvent event = new RoundDrawEvent();
new Drawer().draw(event);

 

 

如果我把入参的引用变成具体类型,如:

 

 

new Drawer().draw(new RoundDrawEvent());

 

 

就直接走到Drawer的draw(RoundDrawEvent event)方法上了,于是结果也是正确的。

 

类似的实现方式,被称为Double Dispatch,它要根据两个对象的运行时类型来选择具体的执行方法(dispatches a function call to different concrete functions depending on the runtime types of two objects involved in the call)。

 

文章系本人原创,转载请注明出处和作者

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值