I have开发者_C百科 an abstract class A and several implementations of it. I expect this to evolve over time, by adding more implementations.
I also have an interface that does something at instances of the above class hierarchy (eg print them).
I want implementations of the interface to provide some special functionality for some of the subclasses of A and a default functionality for the rest of them.
I hope this example clarifies things:
abstract class A { }
class B extends A { }
class C extends A { }
interface Processor {
public void process(A a);
}
class SimpleProcessor implements Processor {
//I want this to be called when argument is instance of A or C or any
//new class that will be added in the future
public void process(A a) {
//Line 14
System.out.println("Default processing");
}
//I want this to be called when argument is instance of B
public void process(B b) {
System.out.println("Special processing");
}
}
public class Runner {
public static void main(String[] args) {
B b = new B();
Processor p = new SimpleProcessor();
p.process(b);
}
}
The example prints "Default processing". The problem is that the method to be executed is chosen based at the compile-time type of the interface's method. Is there a way (or design pattern) to make this program print "Special processing" without adding at line 14 a list of
if (a instance of B)
process( (B) a );
for every class that needs special processing?
I had a look at the visitor pattern but it doesn't seem like an improvement because I don't want to "pollute" the Processor interface with methods for every subclass of A because more subclasses of A will be added.
To put it another way, I want the implementations of the interface to:
- provide custom implementation of the method for specific subclasses of A
- provide a default implementation for classes that will be added in the future
- avoid listing all the classes in a large if-then-else list
Thanks!!
Move the code into the type which varies and use polymorphism. See Open Closed Principle.
interface Processable {
void process();
}
abstract class A implements Processable {
public void process() {
System.out.println("Default processing");
}
}
class B extends A {
public void process() {
System.out.println("Special processing");
}
}
class C extends A {
// default implementation inherited from A
}
class SimpleProcessor {
public void process(Processable p) {
p.process()
}
}
public class Runner {
public static void main(String[] args) {
B b = new B();
Processor p = new SimpleProcessor();
p.process(b);
}
}
How about if you create an Adapter which take the object you want to process and return the processor for that object?
if A -> return ProcessorA
if B -> return ProcessorB
code example:
class Adapter {
Processor getProcessor(Object o) {
if (o instance of A) {
return new ProcessorA();
} else if ...
}
}
You could let the classes itself return the processor
interface Widget {
Processor getProcessor();
}
interface Processor {
void process(Widget w);
}
abstract class WidgetA implements Widget {
Processor getProcessor() {
return new Processor() {
void process(Widget w) {// do magic default process stuff}
};
}
}
class WidgetB extends WidgetA {
// uses default processor
}
class WidgetC extends WidgetA {
Processor getProcessor() {
return new Processor() {
void process(Widget w) {// do magic widget C process stuff}
};
}
}
However for the different skin story, Perhaps it would then be better to create a processor factory that returns the right processor depending on widget, for the different skins you could then create ProcessorFactory that depends on which skin is used
interface ProcessorFactory {
Processor getWidgetAProcessor();
....
}
abstract class WidgetA implements Widget {
Processor getProcessor() {
return factory.getWidgetAProccesor();
}
void setProcessorFactory(ProcessorFactory pf) {
this.factory = pf; // move method signature also to interface
}
}
note: this is only an idea, certainly not the best solution i think
How about create Processor implementation for each Object, then we register them into CompositeProcessor, e.g.
public class ProcessorB implements Processor
{
public void process(A input) { // do something here for B. }
}
public class ProcessorC implements Processor
{
public void process(A input) { // do something here for C}
}
// add more processors if needed
public class CompositeProcessor implements Processor
{
private Map<Class,Processor> processors;
public CompositeProcessor(Map<Class,Processor> processors)
{
this.processors=processors;
}
public void process(A input)
{
for (Map.Entry<Class<?>,Processor> entry : processors.entries())
{
if (entry.getKey().isAssignableFrom(input.getClass())
{
entry.getValue().process(input);
return;
}
}
// do default processing here
}
}
Now use CompositeProcessor in the Runner class.
Note: I did not compile the code above, just typed in this editor, hence probably there are some errors, but you get idea :).
Some advantages are: - the processor is separated from the class it processes (e.g. A and ProcessorA are separated). - There could be more than 1 processors of the given object - The mapping of processors can be changed at runtime
This is a further improvement of using Adapter. This solution needs Reflections library from: http://code.google.com/p/reflections/
Advantage:
- no if then else with instanceof
- no configuration
Disadvantage:
- Need Reflections library
- Can be slow in the beginning
This is it:
import java.lang.reflect.ParameterizedType;
public abstract class Processor<T> {
private final Class<T> processedClass;
public Processor() {
ParameterizedType parameterizedType = (ParameterizedType) getClass().getGenericSuperclass();
processedClass = (Class<T>) parameterizedType.getActualTypeArguments()[0];
}
public Class<T> getProcessedClass() {
return processedClass;
}
protected abstract void process(T message);
}
public class A {
}
public class B {
}
public class ProcessorA extends Processor<A> {
@Override
protected void process(A message) {
System.out.println("Processing object A");
}
}
public class ProcessorB extends Processor<B> {
@Override
protected void process(B message) {
System.out.println("Processing object B");
}
}
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.reflections.Reflections;
public class Adapter {
private Map<Class<?>, Processor<Class<?>>> mapping = new HashMap<Class<?>, Processor<Class<?>>>();
public Adapter() throws Exception {
Reflections r = new Reflections("");
Set<Class<? extends Processor>> subTypesOf = r.getSubTypesOf(Processor.class);
for (Iterator iterator = subTypesOf.iterator(); iterator.hasNext();) {
Class<? extends Processor> c = (Class<? extends Processor>) iterator.next();
Constructor<? extends Processor> constructor = c.getConstructor();
Processor p = constructor.newInstance();
mapping.put(p.getProcessedClass(), p);
}
}
public <T> Processor<T> getProcessor(T obj) {
return (Processor<T>) mapping.get(obj.getClass());
}
}
public class Main {
public static void main(String[] args)
throws Exception {
Adapter adapter = new Adapter();
A a = new A();
adapter.getProcessor(a).process(a);
B b = new B();
adapter.getProcessor(b).process(b);
}
}
Result:
14:01:37.640 [main] INFO org.reflections.Reflections - Reflections took 375 ms to scan 4 urls, producing 222 keys and 919 values
Processing object A
Processing object B
Template Method.
The base class implements the default behavior, the derived classes implement specific behavior.
Class BaseWithTemplateMethod {
void process() {
// Default behavior goes here
}
}
Class DerivedWithSpecific extends BaseWithTemplate {
@override
void process() {
// Specific behavior goes here
}
}
You can do many variations of this theme, such as encapsulating the behavior in another class, the configuring the instances at runtime as to which specific behavior they use, essentially using composition instead. This is particular useful in Java and other languages without multiple inheritance.
精彩评论