`

Double Dispatch(双分派)

阅读更多

   参考资料:

   http://en.wikipedia.org/wiki/Double_dispatch

   http://en.wikipedia.org/wiki/Multiple_dispatch

   http://hi.baidu.com/blue_never_died/blog/item/2d19403474fd3b4e251f149a.html

 

   几个源代码搜索引擎

 

   http://www.koders.com

   http://www.codase.com

   http://www.krugle.com

 

   Koder有firefox搜索条扩展。

 

   分派过程就是确定一个方法调用的过程,双分派就是根据运行时多个对象的类型确定方法调用的过程。

 

   想象这样一个客户服务的场景,一般客户支持有一级支持和二级支持。一级支持一般解决比较简单的问题,如果问题解决不了,就会由二级支持来解决。

 

 

    定义一般问题:

 

package com.baskode.test.doubledispatch;

public class Problem {

}

 

   定义特殊问题:

 

package com.baskode.test.doubledispatch;

public class SpecialProblem extends Problem {

}

 

    定义一级支持:

 

 

package com.baskode.test.doubledispatch;

public class Supporter {
	void solve(Problem problem){
		System.out.println("一级支持解决一般问题!");
	}
	
	void solve(SpecialProblem problem){
		System.out.println("一级支持解决特殊问题");
	}
}
 

   定义资深支持:

 

 

package com.baskode.test.doubledispatch;

public class SeniorSupporter extends Supporter{

	void solve(Problem problem){
		System.out.println("资深支持解决一般问题!");
	}
	
	void solve(SpecialProblem specProblem){
		System.out.println("资深支持解决特殊问题!");
	}
}

 

   下面是测试类:

 

package com.baskode.test.doubledispatch;


import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class DoubleDispatchTest {

	@Before
	public void setUp() throws Exception {
	}

	@After
	public void tearDown() throws Exception {
	}

	/**
	 * 函数重载
	 */
	@Test
	public void functionOverload(){
		Problem problem = new Problem();
		SpecialProblem specProblem = new SpecialProblem();
		
		Supporter supporter = new Supporter();
		supporter.solve(problem);
		supporter.solve(specProblem);
	}
	
	/**
	 * 单次动态分派
	 */
	@Test
	public void singleDynamicDispatch(){
		Problem problem = new Problem();
		SpecialProblem specProblem = new SpecialProblem();

		Supporter supporter = new SeniorSupporter();
		supporter.solve(problem);
		supporter.solve(specProblem);
		
		//资深支持解决一般问题!
		//资深支持解决特殊问题!
		
		//能够正确路由
	}
	
	@Test
	public void someError(){
		Problem problem = new Problem();
		Problem specProblem = new SpecialProblem();

		Supporter supporter = new SeniorSupporter();
		
		supporter.solve(problem);
		supporter.solve(specProblem);
				
		//资深支持解决一般问题!
		//资深支持解决一般问题!
		
		//出错了,与我们意图不一样
	}

}
 

 

    先看singleDispatch和someError方法,区别就在下面一句:

 

 

    singleDynamicDispatch

SpecialProblem specProblem = new SpecialProblem();

 

 

    someError

Problem specProblem = new SpecialProblem();

 

    对于singleDynamicDispatch其实进行了两次分派,首先编译时分别绑定了solve(Problem)方法和solve(SpecialProblem),然后solve方法的多态路由到SeniorSupporter.solve(Problem)和SeniorSupporter.solve(SpecialProblem)。这时执行结果是正确的。

 

    对与someError方法,因为两个Problem编译时的类型都是Problem,所以静态绑定都是solve(Problem)方法。

所以运行结果不是我们所期望的。

 

    解决这个问题,就是想办法在运行时根据Problem和Supporter的具体类型进行分派。

 

    在Problem中增加如下方法,在方法调用时将自身传入。

 

package com.baskode.test.doubledispatch;

public class Problem {
	void solve(Supporter supporter){
		supporter.solve(this);
	}
}

 

   在SpecialProblem,增加如下方法,在方法调用时,将自身传入:

 

package com.baskode.test.doubledispatch;

public class SpecialProblem extends Problem {
	void solve(Supporter supporter){
		supporter.solve(this);
	}
}
 

 

    看看现在的测试代码:

 

 

package com.baskode.test.doubledispatch;


import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class DoubleDispatchTest {

	@Before
	public void setUp() throws Exception {
	}

	@After
	public void tearDown() throws Exception {
	}

	/**
	 * 函数重载,静态分派,编译时
	 */
	@Test
	public void functionOverload(){
		Problem problem = new Problem();
		SpecialProblem specProblem = new SpecialProblem();
		
		Supporter supporter = new Supporter();
		supporter.solve(problem);
		supporter.solve(specProblem);
	}
	
	/**
	 * 单次分派,运行时确定Supporter类型为S
	 */
	@Test
	public void singleDynamicDispatch(){
		Problem problem = new Problem();
		SpecialProblem specProblem = new SpecialProblem();

		Supporter supporter = new SeniorSupporter();
		supporter.solve(problem);
		supporter.solve(specProblem);
		
		//资深支持解决一般问题!
		//资深支持解决特殊问题!
		
		//能够正确路由
	}
	
	@Test
	public void someError(){
		Problem problem = new Problem();
		Problem specProblem = new SpecialProblem();

		Supporter supporter = new SeniorSupporter();
		
		supporter.solve(problem);
		supporter.solve(specProblem);
				
		//资深支持解决一般问题!
		//资深支持解决一般问题!
		
		//出错了,与我们意图不一样
	}
	
	@Test
	public void doubleDispatch(){
		Problem problem = new Problem();
		Problem specProblem = new SpecialProblem();
		
		Supporter supporter = new SeniorSupporter();

		problem.solve(supporter);
		specProblem.solve(supporter);
		
		//资深支持解决一般问题!
		//资深支持解决特殊问题!
		
		//Now,It's right!
	}
}

 

   现在,通过调用:

 

 

		problem.solve(supporter);
		specProblem.solve(supporter);

 

   来实现两次动态分派,第一次是problem中solve方法的多态,第二次是supporter中solve方法的多态。

 

 

    参观者模式 (Vistor)也使用了类似的方式。

 

    我们经常用的JUnit,也是采用这种方式实现TestCase和TestResult的灵活扩展。

 

 

package junit.framework;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/**
 * A test case defines the fixture to run multiple tests. To define a test case<br>
 * 1) implement a subclass of TestCase<br>
 * 2) define instance variables that store the state of the fixture<br>
 * 3) initialize the fixture state by overriding <code>setUp</code><br>
 * 4) clean-up after a test by overriding <code>tearDown</code>.<br>
 * Each test runs in its own fixture so there
 * can be no side effects among test runs.
 * Here is an example:
 * <pre>
 * public class MathTest extends TestCase {
 *     protected double fValue1;
 *     protected double fValue2;
 *
 *    protected void setUp() {
 *         fValue1= 2.0;
 *         fValue2= 3.0;
 *     }
 * }
 * </pre>
 *
 * For each test implement a method which interacts
 * with the fixture. Verify the expected results with assertions specified
 * by calling <code>assertTrue</code> with a boolean.
 * <pre>
 *    public void testAdd() {
 *        double result= fValue1 + fValue2;
 *        assertTrue(result == 5.0);
 *    }
 * </pre>
 * Once the methods are defined you can run them. The framework supports
 * both a static type safe and more dynamic way to run a test.
 * In the static way you override the runTest method and define the method to
 * be invoked. A convenient way to do so is with an anonymous inner class.
 * <pre>
 * TestCase test= new MathTest("add") {
 *        public void runTest() {
 *            testAdd();
 *        }
 * };
 * test.run();
 * </pre>
 * The dynamic way uses reflection to implement <code>runTest</code>. It dynamically finds
 * and invokes a method.
 * In this case the name of the test case has to correspond to the test method
 * to be run.
 * <pre>
 * TestCase test= new MathTest("testAdd");
 * test.run();
 * </pre>
 * The tests to be run can be collected into a TestSuite. JUnit provides
 * different <i>test runners</i> which can run a test suite and collect the results.
 * A test runner either expects a static method <code>suite</code> as the entry
 * point to get a test to run or it will extract the suite automatically.
 * <pre>
 * public static Test suite() {
 *      suite.addTest(new MathTest("testAdd"));
 *      suite.addTest(new MathTest("testDivideByZero"));
 *      return suite;
 *  }
 * </pre>
 * @see TestResult
 * @see TestSuite
 */

public abstract class TestCase extends Assert implements Test {
	/**
	 * the name of the test case
	 */
	private String fName;

	/**
	 * No-arg constructor to enable serialization. This method
	 * is not intended to be used by mere mortals without calling setName().
	 */
	public TestCase() {
		fName= null;
	}

	/**
	 * Runs the test case and collects the results in TestResult.
	 */
	public void run(TestResult result) {
		result.run(this);
	}

        。。。。。。

}
 

 

package junit.framework;

import java.util.Enumeration;
import java.util.Vector;

/**
 * A <code>TestResult</code> collects the results of executing
 * a test case. It is an instance of the Collecting Parameter pattern.
 * The test framework distinguishes between <i>failures</i> and <i>errors</i>.
 * A failure is anticipated and checked for with assertions. Errors are
 * unanticipated problems like an <code>ArrayIndexOutOfBoundsException</code>.
 *
 * @see Test
 */
public class TestResult extends Object {
	protected Vector fFailures;
	protected Vector fErrors;
	protected Vector fListeners;
	protected int fRunTests;
	private boolean fStop;
	
	public TestResult() {
		fFailures= new Vector();
		fErrors= new Vector();
		fListeners= new Vector();
		fRunTests= 0;
		fStop= false;
	}
	/**
	 * Adds an error to the list of errors. The passed in exception
	 * caused the error.
	 */
	public synchronized void addError(Test test, Throwable t) {
		fErrors.addElement(new TestFailure(test, t));
		for (Enumeration e= cloneListeners().elements(); e.hasMoreElements(); ) {
			((TestListener)e.nextElement()).addError(test, t);
		}
	}
	/**
	 * Adds a failure to the list of failures. The passed in exception
	 * caused the failure.
	 */
	public synchronized void addFailure(Test test, AssertionFailedError t) {
		fFailures.addElement(new TestFailure(test, t));
		for (Enumeration e= cloneListeners().elements(); e.hasMoreElements(); ) {
			((TestListener)e.nextElement()).addFailure(test, t);
		}
	}
	/**
	 * Registers a TestListener
	 */
	public synchronized void addListener(TestListener listener) {
		fListeners.addElement(listener);
	}
	/**
	 * Unregisters a TestListener
	 */
	public synchronized void removeListener(TestListener listener) {
		fListeners.removeElement(listener);
	}
	/**
	 * Returns a copy of the listeners.
	 */
	private synchronized Vector cloneListeners() {
		return (Vector)fListeners.clone();
	}
	/**
	 * Informs the result that a test was completed.
	 */
	public void endTest(Test test) {
		for (Enumeration e= cloneListeners().elements(); e.hasMoreElements(); ) {
			((TestListener)e.nextElement()).endTest(test);
		}
	}
	/**
	 * Gets the number of detected errors.
	 */
	public synchronized int errorCount() {
		return fErrors.size();
	}
	/**
	 * Returns an Enumeration for the errors
	 */
	public synchronized Enumeration errors() {
		return fErrors.elements();
	}
	/**
	 * Gets the number of detected failures.
	 */
	public synchronized int failureCount() {
		return fFailures.size();
	}
	/**
	 * Returns an Enumeration for the failures
	 */
	public synchronized Enumeration failures() {
		return fFailures.elements();
	}
	/**
	 * Runs a TestCase.
	 */
	protected void run(final TestCase test) {
		startTest(test);
		Protectable p= new Protectable() {
			public void protect() throws Throwable {
				test.runBare();
			}
		};
		runProtected(test, p);

		endTest(test);
	}
	/**
	 * Gets the number of run tests.
	 */
	public synchronized int runCount() {
		return fRunTests;
	}
	/**
	 * Runs a TestCase.
	 */
	public void runProtected(final Test test, Protectable p) {
		try {
			p.protect();
		} 
		catch (AssertionFailedError e) {
			addFailure(test, e);
		}
		catch (ThreadDeath e) { // don't catch ThreadDeath by accident
			throw e;
		}
		catch (Throwable e) {
			addError(test, e);
		}
	}
	/**
	 * Checks whether the test run should stop
	 */
	public synchronized boolean shouldStop() {
		return fStop;
	}
	/**
	 * Informs the result that a test will be started.
	 */
	public void startTest(Test test) {
		final int count= test.countTestCases();
		synchronized(this) {
			fRunTests+= count;
		}
		for (Enumeration e= cloneListeners().elements(); e.hasMoreElements(); ) {
			((TestListener)e.nextElement()).startTest(test);
		}
	}
	/**
	 * Marks that the test run should stop.
	 */
	public synchronized void stop() {
		fStop= true;
	}
	/**
	 * Returns whether the entire test was successful or not.
	 */
	public synchronized boolean wasSuccessful() {
		return failureCount() == 0 && errorCount() == 0;
	}
}
 

 

 

   以下是Spring中的代码,XmlWebApplicationContext:

 

   实现BeanDefinitionReader和ApplicationContext的灵活扩展。

 

	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException {
		// Create a new XmlBeanDefinitionReader for the given BeanFactory.
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

		// Configure the bean definition reader with this context's
		// resource loading environment.
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

		// Allow a subclass to provide custom initialization of the reader,
		// then proceed with actually loading the bean definitions.
		initBeanDefinitionReader(beanDefinitionReader);
		loadBeanDefinitions(beanDefinitionReader);
	}
2
1
分享到:
评论
1 楼 hax 2009-02-04  
语言支持动态多分派,就不用那么麻烦了。

相关推荐

    Java模拟双分派DoubleDispatch

    Java、C#等仅仅支持单分派(singledispatch)而不支持双分派(double dispatch)。【相关概念,参考《设计模式.5.11访问者模式》p223】对于消息a.foo(b),假设有父类X及其两个子类X1、X2,a声明为X类型变量;有父类Y...

    69丨访问者模式(下):为什么支持双分派的语言不需要访问者模式?1

    然而,为了深入理解这一模式,我们需要探讨一个相关概念——【双分派(Double Dispatch)】。 双分派是指在运行时,不仅根据对象的类型决定调用哪个方法,还根据方法参数的类型决定调用哪个具体实现。这与单分派...

    C++版本设计模式

    而Visitor模式,也谈doubledispatch(双分派),这种模式可以在不改变已有对象结构的前提下增加新的操作。 总之,设计模式作为软件开发中的一种重要工具,不仅可以提高开发效率,还能使得代码结构更加清晰,易于扩展...

    源码:阎宏设计模式光盘

    com.javapatterns.doubledispatch 专题:单分派与多分派 com.javapatterns.facade 门面模式 com.javapatterns.factorymethod 工厂方法模式 com.javapatterns.flyweight 享元模式 com.javapatterns.immutable ...

    组件模型与双事件处理线程

    接下来,我们转向双事件处理线程(Double-Edged Event Dispatch Thread,简称EDT)。在Java GUI中,所有的UI更新和事件处理都必须在EDT上进行,以确保线程安全和界面的正确显示。当用户与组件交互时,事件被放入事件...

    C++设计模式精解

    9. 文档的结构:文档包含了引言、总序、设计模式的解析、在开发中体验设计模式、深入理解State模式、对doubledispatch的探讨、为什么使用设计模式的思考以及附录等部分。 通过这些知识点,我们可以理解到C++设计...

    Smalltalk Best Practice Patterns(Kent Beck).pdf

    - **Double Dispatch**:双分派是一种设计模式,允许方法选择基于两个接收者的类型进行。 #### 状态(State) - **Common State**:共享状态是在多个对象之间共享的信息,需要谨慎管理以避免副作用。 - **Variable ...

    GoF 23种设计模式解析附C++实现源码(2nd Edition).pdf

    - **4.3 也谈double dispatch(双分派) :: Visitor模式**:探讨双分派在Visitor模式中的实现细节。 - **4.4 为什么使用设计模式——从Singleton模式谈起**:通过Singleton模式探讨设计模式的重要性。 #### 5. 附录 ...

    设计模式精解-GoF 23 种设计模式解析附 C++实现源码

    - **4.3 也谈double dispatch(双分派):: Visitor模式**:通过解释双分派的概念,进一步阐述了Visitor模式的工作原理及其优势。 - **4.4 为什么使用设计模式——从Singleton模式谈起**:通过分析Singleton模式的...

    23种设计模式(C++)

    例如,通过具体的案例来理解State模式的应用,或者探讨Double Dispatch(双分派)在Visitor模式中的实现。此外,还可以从Singleton模式出发,探讨为什么我们需要设计模式,以及它们如何帮助我们解决实际问题。 ####...

    C#23种设计模式【完整】_(0610).rar

    C#中,可以使用双分派(double dispatch)来实现访问者。 以上23种设计模式在C#编程中具有广泛的实践价值,熟练掌握这些模式将极大地提升开发效率和代码质量。通过学习压缩包中的资料,开发者可以更深入地理解这些...

    设计模式新解23招,招招新鲜

    - **也谈Double Dispatch(双分派):Visitor模式** - **概念介绍**:讨论双分派机制及其在Visitor模式中的实现。 - **为什么使用设计模式——从Singleton模式谈起** - **概念介绍**:解释为什么设计模式在软件开发...

    Domain-Driven-Design-StepbyStep

    为了减少类间的依赖,可以通过多种方式实现规格模式,例如使用组合模式来组合多个规格,或者通过双分派(Double Dispatch)机制来避免硬编码的if-else语句。 #### 结论 综上所述,《领域驱动设计:逐步指南》全面地...

    设计模式精解(GoF 23 种设计模式解析)

    在探讨double dispatch(双分派)机制时,可以通过Visitor模式来深入理解其原理;在讨论为什么使用设计模式时,可以从Singleton模式出发,探讨其在不同场景下的优劣。 #### 5. 附录:关于设计模式的思考 设计模式...

    设计模式精解-GoF 23 种设计模式解析附 C++实现源码.rar

    设计模式精解-GoF 23 种设计模式解析附 C++实现源码 目 录 0 引言 ............................................................................................................................................

Global site tag (gtag.js) - Google Analytics