Factory configured by a. object's class - how to do it nicely?
In my current project we have a couple of data classes that deal with core concepts of our application domain. Now at some places in our project we have to have different behavior depending on the concrete object in hand. E.g. a JList has the task to render a list of objects but we want the rendering to be slightly different, depending on the object's class. E.g. an object of class A should be rendered different than one of class B and class C is a totally different animal, too.
We encapsulate the behavior in strategy classes and then have a factory that returns a class suitable for the object that is to be rendered. From the client perspective, that is okay, I guess.
Now from the perspective of the factory this gets pretty ugly, because all we could come up with so far is stuff like
if (obj instanceof class开发者_如何学编程A) return strategyA;
else if (obj instanceof classB) return strategyB;
...
Now, for a pool of already instantiated objects, a map would also work. But if the factory has to actually create a new object, we'd have to put another layer of factory/strategy objects into that map that then return a suitable strategies for displaying.
Is there any design pattern that deals nicely with this kind of problem?
One way to do this is to delegate the implementation to the object itself. For instance, if classes A
, B
, and C
are all rendered differently, you might have them each implement an interface such as:
interface IRenderable {
public void render();
}
Then, each one would provide its own implementation of render()
. To render a List
of IRenderable
, you would only need to iterate over its members and call the render()
method of each.
Using this approach, you never have to explicitly check the type of an object. This is particularly useful if any of your classes are ever subclassed. Suppose you had class classD
which extends classA
, and was to be rendered differently from A
. Now code like:
if (obj instanceof classA) return strategyA;
...
else if (obj instanceof classD) return strategyD;
will fail - you would always need to check in order of most to least specific. Better not to have to think about such things.
Edit: in response to your comment - if your goal is to keep the front end code out of the model objects, but you still want to avoid explicit checks, you can use the visitor pattern.
Something like this:
class Renderer {
public void visit(classA obj);
public void visit(classB obj);
// etc
}
and
class classA {
public void accept(Renderer r) {
r.visit(this);
}
}
Now, all the rendering code goes into the Renderer
, and the model objects choose which method to call.
Instead of the if/else block you can have a Factory interface, like this
interface RendererFactory {
supports(Object obj);
createRenderer(Object obj);
}
Then you can have an implementation which asks a list of other implementations if one of them support a given type. The other implementations may do an instanceof
check in the supports
method.
The consumer of the renderer only needs to call createRenderer
.
Advantage: Configuration of your RendererFactories possible
Disadvantage: You have to take care about the order of the RendererFactories (but you have to do that with if/else too)
I like the factory-serving-strategy objects a lot. But I wonder if you could treat it like IoC and register strategies for specifci types? You don't have a bunch of if-else's but you would have to 'register' them. But it might also be nice for testing - rather an implementing a 'mock factory' you'd register 'mock strategies'?
You can have your model classes implement an interface like:
public interface RenderingStrategyProvider {
public RenderingStrategy getRenderingStrategy();
}
and return an instance of the appropriate strategy. Like:
public ClassA implements RenderingStrategyProvider {
public RenderingStrategy getRenderingStrategy() {
return new ClassARenderingStrategy(this);
// or without this, depending on your other code
}
}
In that case you wouldn't even need a factory. Or if you want such, it will contain just a one method call. That way you don't have the presentation logic inside your model classes.
Alternatively, you can use convention + reflection, but this is a weird. The strategy for a model class would be ModelClassStrategy
, and you can have:
public RenderingStrategy createRenderingStrategy(Object modelObject) {
return (RenderingStrategy) Class.forName(
modelObject.getClass().getName() + "Strategy").newInstance();
}
精彩评论