`

画圆画方的故事

阅读更多

这个故事最初是来自和发哥的一次聊天,他说了一些面向对象设计方面挺有意思的事情,包括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)。

 

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

 

8
0
分享到:
评论
4 楼 chairmanMao 2012-12-07  
使用范型就可以了,
3 楼 RayChase 2012-04-15  
xiaodatao 写道
故事很精彩!
小P可以考滤一下,用设计模式中的“策略模式(Strategy)”。

public class Drawer {

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

不,你没有说到点子上去,Event始终只是一个委托,真正draw的是谁(Double Dispatch的“double”一个是你提到的这个event,还有一个呢)?
你调用了event.draw()没有错,但是这个event会把这个draw的行为委托给真正的drawer来完成,那么你这里是通过怎样的机制来解决这个变化点、把这个委托完成的呢?
2 楼 xiaodatao 2012-04-15  
故事很精彩!
小P可以考滤一下,用设计模式中的“策略模式(Strategy)”。

public class Drawer {

    DrawEvent event;
   
    public Drawer(DrawEvent event) {
        this.event = event;
    }
   
    public void draw() {
        event.draw();
    }
}
1 楼 bloodwolf_china 2012-03-12  
嗯,很巧妙,类似于回调函数一样,把抽象类型变为具体类型。
有时候设计模式就像时逢山开路遇水填桥一样,再多想一步。

相关推荐

    java多线程 画圆画方

    java多线程实现画圆画方的小程序 方和圆相对移动

    左手画圆,右手画方

    在Java编程中,"左手画圆,右手画方"是一个形象的说法,用来描述如何通过多线程技术在同一程序中实现两个或多个独立的任务同时进行。在这个案例中,我们看到的是一段利用Java Swing库创建图形用户界面(GUI)并利用...

    左键画圆,右键画方,C++程序

    标题提到的"左键画圆,右键画方"是一个典型的图形用户界面(GUI)编程示例,它利用C++库来实现鼠标事件监听,并根据用户的左键或右键点击绘制不同的图形。 首先,让我们深入了解一下C++的基础知识: 1. **基础语法**...

    基于MFC框架的并行画圆画方代码包

    在这个“基于MFC框架的并行画圆画方代码包”中,我们可以探讨以下几个关键知识点: 1. **MFC框架**:MFC是一套面向对象的类库,它封装了Windows API,使得开发者能够更方便地创建Windows程序。MFC提供了诸如窗口、...

    c++编写的画圆方形小程序

    c++编写的画圆方形小程序,简单实用 大家可以共享一下

    DrawCircleAndSquare.zip_DrawCircleAndSquare_Java作业 画圆画方

    在这个"DrawCircleAndSquare"的项目中,学生需要创建一个窗口应用,能够响应用户的鼠标点击事件,根据不同的鼠标操作在窗口上绘制绿色的圆和红色的正方形,以及清除所有已画图形。 首先,我们需要导入相关的Java库...

    西门子定位控制- 画圆-画五角星- 画方

    本系列教程将探讨如何利用西门子定位控制功能实现图形绘制,包括画圆、画五角星以及画方。 首先,让我们详细了解一下“画圆”。画圆操作在许多制造过程中是必不可少的,例如在机械加工和3D打印中。西门子定位控制...

    简单编辑软件,可以画圆,画直线等

    《简易图形编辑软件的设计与实现》 图形编辑软件在日常工作中有着广泛的应用,从简单的图纸绘制到复杂的3D建模,都是其施展才华的舞台。本文将以“简易图形编辑软件”为主题,探讨其核心功能、设计思路和技术实现,...

    VC6.0中的MFC实现画线、画圆

    本代码用VC++6.0软件编写,实现自定义端点画线、自定义圆心半径画圆,其中画线算法用到了DDA、逐点逼近、Bresenham、中点画线法;画圆算法使用了Bresenham、中点画圆法。各算法可以分别选择使用哪种画图,比较哪种...

    GUI.rar_OLED 画点_OLED画圆函数_OLED画线_oled使用的gui_适合oled GUI

    在本文中,我们将深入探讨如何使用GUI(图形用户界面)与OLED显示器进行交互,特别是关注于OLED的画点、画线、画圆以及更多高级功能。OLED(有机发光二极管)显示屏因其高对比度、低功耗和小巧尺寸而广泛应用于...

    VML经典教程 VML动态画圆、画矩形、画多边形

    在本教程中,我们将深入探讨如何利用VML技术实现动态地画圆、画矩形以及画多边形。 ### 1. VML基本概念 VML是微软在SVG(Scalable Vector Graphics)标准之前提出的一种矢量图形格式,它允许开发者使用XML语法来...

    1200PLC两轴伺服画圆等运动控制案例

    在本案例中,1200PLC将通过编程生成合适的脉冲序列,控制两轴伺服电机按照预设路径进行画方、画圆等运动。 接着,1200PLC的轴组态是其运动控制的核心部分。在博图(TIA Portal)软件中,用户可以设置伺服电机的轴...

    中点画圆画四分之一圆

    在八分之一画圆的基础上完成四分之一的中点画圆

    【源代码】中秋画圆,画的越圆得分越高,基于fabric和vue.js实现

    【源代码】中秋画圆,画的越圆得分越高,基于fabric和vue.js实现

    华中科技大学操作系统实验,一个用qt实现的简单的多线程画圆画方的示例.zip

    项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松copy复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全栈开发),有任何使用问题欢迎随时与我联系,我会及时为您解惑,...

    可设置大小的鼠标点击画圆和正方形的Applet程序

    例如,使用`drawOval(int x, int y, int width, int height)`方法可以画出一个椭圆,如果width和height相等,则画出的是一个圆形;而`drawRect(int x, int y, int width, int height)`方法则用于绘制矩形。在Applet...

    dda画线,画圆算法

    ### DDA画线算法及画圆算法 #### 数值微分法(DDA) 数值微分法(Digital Differential Analyzer,简称DDA)是一种常见的用于在计算机图形学中绘制直线的算法。该方法通过逐步增加x坐标值并相应地计算出y坐标值的...

    西门子博图1214c组态的运动控制学习案列,画圆,画方,相对运动,绝对运动,点动回原点,注释全面,博图v15.1版本

    西门子博图1214c组态的运动控制学习案列,画圆,画方,相对运动,绝对运动,点动回原点,注释全面,博图v15.1版本

    java 一手画方一手画圆 多线程

    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...

Global site tag (gtag.js) - Google Analytics