这个故事最初是来自和发哥的一次聊天,他说了一些面向对象设计方面挺有意思的事情,包括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编程中,"左手画圆,右手画方"是一个形象的说法,用来描述如何通过多线程技术在同一程序中实现两个或多个独立的任务同时进行。在这个案例中,我们看到的是一段利用Java Swing库创建图形用户界面(GUI)并利用...
标题提到的"左键画圆,右键画方"是一个典型的图形用户界面(GUI)编程示例,它利用C++库来实现鼠标事件监听,并根据用户的左键或右键点击绘制不同的图形。 首先,让我们深入了解一下C++的基础知识: 1. **基础语法**...
在这个“基于MFC框架的并行画圆画方代码包”中,我们可以探讨以下几个关键知识点: 1. **MFC框架**:MFC是一套面向对象的类库,它封装了Windows API,使得开发者能够更方便地创建Windows程序。MFC提供了诸如窗口、...
c++编写的画圆方形小程序,简单实用 大家可以共享一下
在这个"DrawCircleAndSquare"的项目中,学生需要创建一个窗口应用,能够响应用户的鼠标点击事件,根据不同的鼠标操作在窗口上绘制绿色的圆和红色的正方形,以及清除所有已画图形。 首先,我们需要导入相关的Java库...
本系列教程将探讨如何利用西门子定位控制功能实现图形绘制,包括画圆、画五角星以及画方。 首先,让我们详细了解一下“画圆”。画圆操作在许多制造过程中是必不可少的,例如在机械加工和3D打印中。西门子定位控制...
《简易图形编辑软件的设计与实现》 图形编辑软件在日常工作中有着广泛的应用,从简单的图纸绘制到复杂的3D建模,都是其施展才华的舞台。本文将以“简易图形编辑软件”为主题,探讨其核心功能、设计思路和技术实现,...
本代码用VC++6.0软件编写,实现自定义端点画线、自定义圆心半径画圆,其中画线算法用到了DDA、逐点逼近、Bresenham、中点画线法;画圆算法使用了Bresenham、中点画圆法。各算法可以分别选择使用哪种画图,比较哪种...
在本文中,我们将深入探讨如何使用GUI(图形用户界面)与OLED显示器进行交互,特别是关注于OLED的画点、画线、画圆以及更多高级功能。OLED(有机发光二极管)显示屏因其高对比度、低功耗和小巧尺寸而广泛应用于...
在本教程中,我们将深入探讨如何利用VML技术实现动态地画圆、画矩形以及画多边形。 ### 1. VML基本概念 VML是微软在SVG(Scalable Vector Graphics)标准之前提出的一种矢量图形格式,它允许开发者使用XML语法来...
在本案例中,1200PLC将通过编程生成合适的脉冲序列,控制两轴伺服电机按照预设路径进行画方、画圆等运动。 接着,1200PLC的轴组态是其运动控制的核心部分。在博图(TIA Portal)软件中,用户可以设置伺服电机的轴...
在八分之一画圆的基础上完成四分之一的中点画圆
【源代码】中秋画圆,画的越圆得分越高,基于fabric和vue.js实现
项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松copy复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全栈开发),有任何使用问题欢迎随时与我联系,我会及时为您解惑,...
例如,使用`drawOval(int x, int y, int width, int height)`方法可以画出一个椭圆,如果width和height相等,则画出的是一个圆形;而`drawRect(int x, int y, int width, int height)`方法则用于绘制矩形。在Applet...
### DDA画线算法及画圆算法 #### 数值微分法(DDA) 数值微分法(Digital Differential Analyzer,简称DDA)是一种常见的用于在计算机图形学中绘制直线的算法。该方法通过逐步增加x坐标值并相应地计算出y坐标值的...
西门子博图1214c组态的运动控制学习案列,画圆,画方,相对运动,绝对运动,点动回原点,注释全面,博图v15.1版本
The title “java 一手画方一手画圆 多线程” translates to "Java: Drawing Squares and Circles with Multi-Threading." This indicates that the program is designed to draw shapes using two separate threads...