Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
482 views
in Technique[技术] by (71.8m points)

design patterns - Custom Guice Scope, or a better approach?

Here's my problem:

It's first important to know that I'm writing a simulation. This is a standalone application, and is single-threaded. I have essentially two classes of objects that have different scoping requirements.

  1. Classes that should be used as singletons throughout the entire simulation. An instance of Random, as an example.

  2. Groups of classes that are created together, and within the group, each instance should be treated like a Singleton. For example, say RootObject is the top level class, and has a dependency to ClassA and ClassB, both of which have a dependency to ClassD. For any given RootObject, both of its dependencies (ClassA and ClassB) should depend on the same instance of ClassD. However, instances of ClassD should not be shared across different instances of RootObject.

Hopefully that makes sense. I can think of two approaches to this. One is to mark all of the injected objects as Singletons, create the root injector, and spin off a child injector each time I need to create a new RootObject instance. Then, the instances of RootObject and all of its dependencies are created as Singletons, but that scoping information is thrown away the next time I go to create another RootObject.

The second approach is to implement some type of custom scope.

The Guice documentation gives conflicting advice... On one hand, it says that you should have a single injector, and that ideally it is called once to create some top level class. On the other hand, it says to stay away from custom scopes.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

It seems to me like you need a scope for each instance of RootObject and all its dependencies.

In Guice you can create a custom scope, say @ObjectScoped, like this:

@Target({ TYPE, METHOD })
@Retention(RUNTIME)
@ScopeAnnotation
public @interface ObjectScoped {}

Now just place RootObject, A, B and D into this scope:

@ObjectScoped
public class RootObject {

    private A a;
    private B b;

    @Inject
    public RootObject(A a, B b) {
        this.a = a;
        this.b = b;
    }

    public A getA() {
        return a;
    }

    public B getB() {
        return b;
    }

}

@ObjectScoped
public class A {

    private D d;

    @Inject
    public A(D d) {
        this.d = d;
    }

    public D getD() {
        return d;
    }
}

// The same for B and D

Now each RootObject has its own scope. You can implement this as a simple HashMap:

public class ObjectScope {

    private Map<Key<?>,Object> store = new HashMap<Key<?>,Object>();

    @SuppressWarnings("unchecked")
    public <T> T get(Key<T> key) {
        return (T)store.get(key);
    }

    public <T> void set(Key<T> key, T instance) {
        store.put(key, instance);
    }

}

To integrate these scopes with Guice you will need a com.google.inject.Scope-implementation which lets you switch the scopes and the corresponding wiring in your Module.

public class GuiceObjectScope implements Scope {

    // Make this a ThreadLocal for multithreading.
    private ObjectScope current = null;

    @Override
    public <T> Provider<T> scope(final Key<T> key, final Provider<T> unscoped) {
        return new Provider<T>() {

            @Override
            public T get() {

                // Lookup instance
                T instance = current.get(key);
                if (instance==null) {

                    // Create instance
                    instance = unscoped.get();
                    current.set(key, instance);
                }
                return instance;

            }
        };
    }

    public void enter(ObjectScope scope) {
        current = scope;
    }

    public void leave() {
        current = null;
    }

}

public class ExampleModule extends AbstractModule {

    private GuiceObjectScope objectScope = new GuiceObjectScope();

    @Override
    protected void configure() {
        bindScope(ObjectScoped.class, objectScope);
        // your bindings
    }

    public GuiceObjectScope getObjectScope() {
        return objectScope;
    }

}

Initialize your program like this:

ExampleModule module = new ExampleModule();
Injector injector = Guice.createInjector(module);
GuiceObjectScope objectScope = module.getObjectScope();

Create the first instance of RootObject and its corresponding scope:

ObjectScope obj1 = new ObjectScope();
objectScope.enter(obj1);
RootObject rootObject1 = injector.getInstance(RootObject.class);
objectScope.leave();

Just switch the scope for a second group of objects:

ObjectScope obj2 = new ObjectScope();
objectScope.enter(obj2);
RootObject rootObject2 = injector.getInstance(RootObject.class);
objectScope.leave();

Test if your requirements are met:

assert rootObject1 != rootObject2;
assert rootObject1.getA() != rootObject2.getA();
assert rootObject1.getA().getD() == rootObject1.getB().getD();
assert rootObject1.getA().getD() != rootObject2.getB().getD();

To work with a group of objects just enter its scope and use the injector:

objectScope.enter(obj1);
B b1 = injector.getInstance(B.class);
objectScope.leave();
assert rootObject1.getB() == b1;

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...