这个故事最初是来自和发哥的一次聊天,他说了一些面向对象设计方面挺有意思的事情,包括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)。
文章系本人原创,转载请注明出处和作者
分享到:
相关推荐
java多线程实现画圆画方的小程序 方和圆相对移动
java 利用多线程 实现两个面板上 一个画圆 一个画方 源码的噢
简单的c++入门程序,左键画圆,右键画方,C++程序
西门子定位控制- 画圆-画五角星- 画方
简单编辑软件,可以画圆,画直线等,实现多边形的裁剪等
c++编写的画圆方形小程序,简单实用 大家可以共享一下
Java图形界面作业题 题目要求: 窗体内“单击鼠标左键”,在鼠标处绘制一个“绿圆” 窗体内“单击鼠标右键”,在鼠标处绘制一个“红色方框” 窗体内“双击鼠标左键”,清空所有已画“圆”和“方”
本代码用VC++6.0软件编写,实现自定义端点画线、自定义圆心半径画圆,其中画线算法用到了DDA、逐点逼近、Bresenham、中点画线法;画圆算法使用了Bresenham、中点画圆法。各算法可以分别选择使用哪种画图,比较哪种...
仅供学习参考,如有学习需要可以下载。
(1)1200PLC控制两轴伺服画方、画圆等运动控制程序案例 (2)包含脉冲PTO、1200PLC轴组态等博图控制伺服案例 (3)控制三菱、台达等品牌伺服以及参数设置,USS协议控制变频等运动控制
在八分之一画圆的基础上完成四分之一的中点画圆
【源代码】中秋画圆,画的越圆得分越高,基于fabric和vue.js实现
是一个Applet程序,用户可以调整所画圆和正方形的大小,并且是鼠标点击就实现画图功能,而且可移动所画的图形,并且所画的图形可以根据需要选择不同颜色!!
软件开发设计:PHP、QT、应用软件开发、系统软件开发、移动应用开发、网站开发C++、Java、python、web、C#等语言的项目开发与学习资料 硬件与设备:单片机、EDA、proteus、RTOS、包括计算机硬件、服务器、网络设备、...
计算机图形学基本算法实现,画圆、画线以及平面裁减。算法有DDA算法、中分点算法,CS裁减算法。
简易的画图小工具,支持改变颜色,大小,前进,后退,后期希望加入画方画圆等功能
import java.applet.*; import java.awt.*; import java.awt.event.*; public class fangyuan extends Applet implements Runnable { Thread left,right; Graphics mypen; int x,y,z,a,b,i; ...}
MFC文档(SDI)应用:绘图程序(画圆、画线、鼠标事件) 1、 在客户区输出一条顺时针45度的直线、一个正方形、一个大圆; 2、 在客户区输出一个图标; 3、 当按下鼠标左键时,将以鼠标坐标为圆心画直径为20个单位的...
今天小编就为大家分享一篇Python 用turtle实现用正方形画圆的例子,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
能够画圆,矩形,直线,正方形,有橡皮擦和选择画笔颜色