`

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  
语言支持动态多分派,就不用那么麻烦了。

相关推荐

Global site tag (gtag.js) - Google Analytics