ActiveMQ 源码学习 2:从 CommandTypes 谈常量接口反模式

在上一篇文章里,我写了在阅读 ActiveMQ 的一小段源码时碰到的两种设计模式:抽象工厂和策略模式。实际上 ActiveMQ 源码量很大,只要认真分析,你会找到很多设计模式的应用场景。其中,有一个模式非常典型,并且它在整个 ActiveMQ 的源码架构中扮演者非常重要的角色,它就是 GoF 设计模式中的:命令模式(Command Pattern)。Command 是 ActiveMQ broker 与 client 的主要通信方式,每个 Command 对象代表了一个待执行的动作。关于命令模式在 ActiveMQ 源码中的应用,我会在后面找个时间专门写一下。

 

这篇文章讨论另一个问题。它是我在开始分析 ActiveMQ 中 Command 实现时碰到的。起因是这样的,我发现了一种不常见的接口定义:

 

/**
 * Holds the command id constants used by the command objects.
 * 
 * Mark by wxl24life: 2013-08-15
 * Constant Interface Antipattern
 */
public interface CommandTypes {

    // /
    //
    // Info objects sent back and forth client/server when
    // setting up a client connection.
    //
    // /
    byte WIREFORMAT_INFO = 1;
    byte BROKER_INFO = 2;
    byte CONNECTION_INFO = 3;
    byte SESSION_INFO = 4;
    byte CONSUMER_INFO = 5;
    byte PRODUCER_INFO = 6;
    byte TRANSACTION_INFO = 7;
    byte DESTINATION_INFO = 8;
    byte REMOVE_SUBSCRIPTION_INFO = 9;

    // ...Other constants definement ignored
}

 

这种写法我是第一次碰到:接口中没有包含任何的方法,只是定义了一些常量属性。抱着这种疑惑,按照惯例自然首先要求助的便是 google,简单的两个关键字可以锁定到 SO 上的这个问答:What is the use of interface constants -- 小提示:在 google 搜索结果里,StackOverFlow 上的问答链接应该始终作为不二的选择

 

得票数最高的回答里获取到的最有价值信息是:这种用法称为常量接口,它是一种典型的 Java 反模式,并且在 Joshua Bloch 的 Effective Java 里有单独的条目详细描述了这个问题。所谓的反模式是指,不建议使用的方法。结合前面碰到的问题,CommandTypes 不应该或者至少是不建议被定义成那样的常量接口。换句话说,再优秀的开源代码中也会有一些不那么“优秀”的用法。真的是这样么?看下面的分析。

 

首先,接口中是可以定义属性的,在不带任何修饰符的情况下,默认是 static final 的。在接口中定义的属性可以在接口的实现类中直接访问,而不需要使用类名进行引用,也就是说,接口中的属性传递到了它的实现类的名字空间里去了。因此,在接口中已经定义了方法的前提下,偶尔定义少量的属性是可以接受的。但是,Java 同时允许常量接口的存在,所谓常量接口就是在接口中只定义属性而不包含方法,这显然违背了接口存在的初衷:接口为它的实现类提供了一种向外界暴露服务的方式。因而,当你定义常量接口的目的是为它的实现类访问常量属性提供便利的时候,就符合 Joshua Bloch 所说的 “常量接口是对接口的不良使用” 了。

 

在 Effective Java 第二版的条目19中描述了使用常量接口可能会产生的问题,以及它的替代用法。

 

  • 类在内部使用某些常量属于实现细节,实现常量接口会导致这样的实现细节泄漏到该类的导出API。
  • 如果非 final 类实现了常量接口,它的子类的名字空间也会被接口中的常量所污染。

通过一段代码来解释上面这两句话:

 

/**
 * @author wxl24life
 *
 */
public class AntiPatternConstantInterfaceImpl implements CommandTypes {
	public byte getActivemqQueue() {
		return ACTIVEMQ_QUEUE; // 常量接口带来的“便利”
	}
	
	public static void main(String[] args) {
		AntiPatternConstantInterfaceImpl antiPat = new AntiPatternConstantInterfaceImpl();
		byte a = antiPat.WIREFORMAT_INFO; // 产生的问题1:实现细节泄漏
		new AntiPatternConstantInterfaceImpl() {
			public void foo() {
				byte b = WIREFORMAT_INFO; // 产生的问题2:子类名字空间被“污染”
			}
		};
	}
}

 

 

当然,在 ActiveMQ 的源码里,CommandTypes 虽然被定义成常量接口,却没有任何类实现这个接口,常量接口在这里充当的角色是:定义一些公共的常量属性提供给其他类引用,相当于一个常量集合。这种用法严格上说不符合常量接口反模式的用法,但是使用接口定义一个常量集合仍然是不够合适的。

 

为此可以考虑另外的一些定义常量集合的方式,比如枚举类型(enum),或者定义一个不可实力化的工具类。

 

 

/**
 * @author wxl24life
 *
 */
public final class CommandTypesUtility {
	
	private CommandTypesUtility() {}
	
	public static final byte WIREFORMAT_INFO = 1;
	public static final byte BROKER_INFO = 2;
	public static final byte CONNECTION_INFO = 3;
	public static final byte SESSION_INFO = 4;
	public static final byte CONSUMER_INFO = 5;
	public static final byte PRODUCER_INFO = 6;
	public static final byte TRANSACTION_INFO = 7;
	public static final byte DESTINATION_INFO = 8;
	public static final byte REMOVE_SUBSCRIPTION_INFO = 9;
	// other constants ignored
}
使用工具类定义常量后,当需要引用某个常量时只需要使用类似于 CommandTypesUtility.WIREFORMAT_INFO 的方式即可。而如果在同一个类里需要大量使用 CommandTypesUtility 中的常量,可以使用静态导入(static import)机制。
import static org.apache.activemq.command.CommandTypesUtility.*;

/**
 * @author wxl24life
 *
 */
public class StaticImportTest {
	byte getWirformat_info() {
		return WIREFORMAT_INFO;
	}
	
	byte getBroker_info() {
		return BROKER_INFO;
	}
	
	// many more other uses 
}
最后引用书中最后一句话: 接口应该只被用于定义类型,它们不应该被用来导出常量
总结: 由于阅读源码的过程更多的是在学习和积累知识,遇到新鲜用法或者知识上的盲点是非常正常的,比如这里的常量接口,通过迅速的搜索相关知识点,经过一定的分析,无论是最终接受还是推倒它,对自己而言都是一种收获。另外,结合实际用例会帮助理解并加深对所读书中知识的印象,其实 Effective Java 这本书我经常在翻阅,条目19虽然只有简短到不足两页的篇幅,但是没有碰到实际的使用场景导致在碰到常量接口时完全没有印象。
完。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值