Lookup在Netbeans IDE中可以说是整个软件架构系统的灵魂。从本质上讲就是以apdatable模式实现的一个Map,其主要功能体现以下几个方面:
- 提供一个Context 可以想象Lookup是一个magic bag可以在运行时(runtime)方便的在bag中添加、删除对象 而这些对象恰好可以组织成系统当前的context get it?
- 由于Adaptable的实现方式(核心是泛型),准确的用Class作为参数查找Map中的实例避免了类型转换的问题,把问题留在了编译时。
- Map内部的监听机制 可以实现热插拔 动态更新的功能
/**
*
* @author Vijay
* of course you can say Callback itself is one of subscribers
*/
public interface Subscriber {
/**
* the smaller priority will get done earlier
*
* @return
*/
Integer priority(Message msg);
}
/**
* All callbacks have to extends this ifc thanks for your cooperation
*
* @author Vijay
*/
public interface Callback extends Subscriber {
final static Integer DEFAULT_PRIORITY = 0;
final static Integer HIGH_PRIORITY = 1;
final static Integer MEDIUM_PRIORITY = 10;
final static Integer LOW_PRIORITY = 100;
/**
* The <code>call</code> method is called when required, and is given a
* single argument of type P, with a requirement that an object of type R is
* returned.
*
* @param param The single argument upon which the returned value should be
* determined.
* @return An object of type R that may be determined based on the provided
* parameter value.
*/
public void call(Message msg);
/*
we only broadcast abstract message
*/
public abstract class Message {
final private Param param;
protected Message(Param param) {
this.param = param;
}
public Param getParam() {
return param;
}
public abstract Class<? extends Callback> getRelatedCallback();
public static Message createLocate(Param param) {
Message message = new Locate(param);
return message;
}
/**
* concrete built-in message here and always come with Operation(Callback)
*/
//locate
private static class Locate extends Message {
private Locate(Param param) {
super(param);
}
@Override
public Class getRelatedCallback() {
return Moveable.class;
}
}
public interface Moveable extends Callback {
}
//add
private static class Add extends Message {
private Add(Param param) {
super(param);
}
@Override
public Class getRelatedCallback() {
return Addable.class;
}
}
public interface Addable extends Callback {
}
//delete .... continue
}
}
/**
*
* @author Vijay
*/
public interface Context {
Lookup getBusContext();
}
/**
*
* @author Vijay
*/
public class DefaultContext implements Context{
@Override
public Lookup getBusContext() {
return Utilities.actionsGlobalContext();
}
}
import java.util.Map;
/**
* Sometimes we needs to define data structure to get parameter
*
*
*/
public final class Param {
final Map<String, Object> attachments;
final Integer priority;
private Param(Integer priority, Map<String, Object> attachments) {
this.priority = priority;
this.attachments = attachments;
}
public static Param empty() {
return new Param(Callback.DEFAULT_PRIORITY, null);
}
public final Param priority(Integer priority) {
return new Param(priority, this.attachments);
}
public final Param attachments(Map<String, Object> attachments) {
return new Param(this.priority, attachments);
}
public Integer getPriority() {
return priority;
}
public Map<String, Object> getAttachments() {
return attachments;
}
}
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.openide.util.Lookup;
/**
* Treat Context as a System Bus
*
* @author Vijay
*/
public final class BusEngine {
private static final BusEngine instance = new BusEngine();
private final ExecutorService executor;
static {
}
private BusEngine() {
executor = Executors.newSingleThreadExecutor();
}
public void broadcast(final Callback.Message msg) {
executor.submit(new Runnable() {
@Override
public void run() {
doSyncCallback(msg,new DefaultContext().getBusContext());
}
});
}
void broadcast(final Callback.Message msg,final Lookup lkp) {
executor.submit(new Runnable() {
@Override
public void run() {
doSyncCallback(msg,lkp);
}
});
}
public static BusEngine getDefault() {
return instance;
}
@Deprecated
static void refreshAllInContext(Callback.Message msg,Context cxt) {
doSyncCallback(msg,cxt.getBusContext());
}
static void doSyncCallback(final Callback.Message msg,Lookup lkp) {
Lookup.Template template = new Lookup.Template(msg.getRelatedCallback());
Lookup.Result result = lkp.lookup(template);
Collection c = result.allInstances();
List<Callback> callbacks = new ArrayList();
callbacks.addAll(c);
Collections.sort(callbacks, new Comparator<Callback>() {
@Override
public int compare(Callback o1, Callback o2) {
return o1.priority(msg)-o2.priority(msg);
}
});
for (Callback cb : callbacks) {
processMsg(cb, msg);
}
}
static void processMsg(Callback cb, final Callback.Message msg) {
cb.call(msg);
}
}
以上代码需要依赖org.openide.util.jar 和org.openide.util.lookup.jar 由于测试需要有些方法开放为包可见
为了说明其用例 请参考以下测试代码
import java.util.ArrayList;
import java.util.List;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
/**
*
* @author Vijay
*/
public class BusEngineNGTest {
/**
* use list to test priority
*/
static List<Callback> GLOBAL_TEST_LIST;
public BusEngineNGTest() {
}
@BeforeClass
public static void setUpClass() throws Exception {
}
@AfterClass
public static void tearDownClass() throws Exception {
}
@BeforeMethod
public void setUpMethod() throws Exception {
GLOBAL_TEST_LIST = new ArrayList();
}
@AfterMethod
public void tearDownMethod() throws Exception {
GLOBAL_TEST_LIST = null;
}
@Test
public void testBasicFunctionMsg1() throws InterruptedException {
BusEngine.getDefault().broadcast(new CBMsg1(Param.empty()), new TestContext().getBusContext());
Thread.sleep(1000);
Assert.assertEquals(GLOBAL_TEST_LIST.size(), 1);
Assert.assertEquals(GLOBAL_TEST_LIST.get(0).priority(null),new Integer(1));
}
@Test
public void testBasicFunctionMsg2() throws InterruptedException {
BusEngine.getDefault().broadcast(new CBMsg2(Param.empty()), new TestContext().getBusContext());
Thread.sleep(1000);
Assert.assertEquals(GLOBAL_TEST_LIST.size(), 2);
Assert.assertEquals(GLOBAL_TEST_LIST.get(0).priority(null),new Integer(1));
Assert.assertEquals(GLOBAL_TEST_LIST.get(1).priority(null),new Integer(10));
}
}
/**
*
* @author Vijay
*/
public interface CB1 extends Callback{
}
/**
*
* @author Vijay
*/
public interface CB2 extends Callback{
}
/**
*
* @author Vijay
*/
public class CBMsg1 extends Message{
public CBMsg1(Param param) {
super(param);
}
@Override
public Class<? extends Callback> getRelatedCallback() {
return CB1.class;
}
}
/**
*
* @author Vijay
*/
public class CBMsg2 extends Message {
public CBMsg2(Param param) {
super(param);
}
@Override
public Class getRelatedCallback() {
return CB2.class;
}
}
/**
*
* @author Vijay
*/
public class Subscriber1 implements CB1, CB2 {
@Override
public void call(Message msg) {
final String CBName = msg.getRelatedCallback().getSimpleName();
if ("CB1".equals(CBName)) {
Assert.assertEquals(CBName, "CB1");
} else {
Assert.assertEquals(CBName, "CB2");
}
BusEngineNGTest.GLOBAL_TEST_LIST.add(this);
}
@Override
public Integer priority(Message msg) {
return 1;
}
}
/**
*
* @author Vijay
*/
public class Subscriber2 implements CB2{
@Override
public void call(Message msg) {
final String simpleName = msg.getRelatedCallback().getSimpleName();
Assert.assertEquals("CB2",simpleName);
BusEngineNGTest.GLOBAL_TEST_LIST.add(this);
}
@Override
public Integer priority(Message msg) {
return 10;
}
}
/**
*
* @author Vijay
*/
public class TestContext implements Context{
@Override
public Lookup getBusContext() {
return Lookups.fixed(new Subscriber1(),new Subscriber2());
}
}
我所说的有悖“开闭”原则 是指我们必须实现Callback的call方法 这样如果Subscriber实现多个Callback 不得不以Message来区分。目前我还没有想到解决的办法... 望各位看官不吝指教