Reputation: 12876
Let's say my application has some services implemented as ClassA
and ClassB
. Both have some similarities but also differences.
start()
method with the same method signature but a different implementation.process()
method with a different signature and a different implementation.log()
method, i.e. the code is exactly the same.public class ClassA {
public String start(String s1, String s2) {
startImplementation();
return someString;
}
public String process(String s) {
processingImplementation();
return processedString;
}
private String log(String s) {
logImplementation();
return sharedString;
}
}
public class ClassB {
public String start(String s1, String s2) {
otherStartImplementation();
return someString;
}
public String process(Long l) {
otherProcessingImplementation();
return processedString;
}
private String log(String s) {
logImplementation();
return sharedString;
}
}
I'm having trouble thinking of a "design pattern" how I could organize this in a more generic way. As of 3. I could easily move this method to a superclass which ClassA
and ClassB
extend. But how would/could I design the application so that 1. and 2. are also taken into account?
Item 1. sounds a little bit like an interface to me but I don't have any idea how this could be combined with the superclass for item 3. And what about item 2?
Upvotes: 6
Views: 5263
Reputation: 9086
How about using composition over inheritance? The implementations of start
and process
could be provided by functions like in the example below:
import java.util.function.BiFunction;
import java.util.function.Function;
class X<T> {
public String start(BiFunction<String, String, String> f, String s1, String s2) {
return f.apply(s1, s2);
}
public String process(Function<T, String> f, T t) {
return f.apply(t);
}
// example
public static void main(String[] args) {
X<String> xString = new X();
xString.start((s1, s2) -> s1 + s2, "a", "b");
X<Long> xLong = new X();
xLong.process((t) -> { Long tt = t * 2;return tt.toString(); }, 4L);
}
}
Same as previous example, but with implementations provided in the constructor and using functional interfaces instead of lambdas.
import java.util.function.BiFunction;
import java.util.function.Function;
class StartFunctionExample implements BiFunction<String, String, String> {
@Override
public String apply(String s1, String s2) {
return s1 + s2;
}
}
class ProcessFunctionExample implements Function<Long, String> {
@Override
public String apply(Long t) {
Long tt = (t * 2);
return tt.toString();
}
}
class Z<T> {
private final BiFunction<String, String, String> startFunction;
private final Function<T, String> processFunction;
public Z(
BiFunction<String, String, String> startFunction,
Function<T, String> processFunction
) {
this.startFunction = startFunction;
this.processFunction = processFunction;
}
public String start(String s1, String s2) {
return startFunction.apply(s1, s2);
}
public String process(T t) {
return processFunction.apply(t);
}
// example
public static void main(String[] args) {
Z<Long> xLong = new Z(new StartFunctionExample(), new ProcessFunctionExample());
xLong.start("a", "b"); // ab
xLong.process(7L); // 14
}
}
Upvotes: 2
Reputation: 3951
Let a different class implement the logging, the processing can be done by classes implementing the service interface:
interface Processable {
String start(String s1, String s2);
process(String s);
}
class LogDecorator {
private Processable p;
public LogDecorator(Processable p) {
this.p = p;
}
public String start(String s1, String s2) {
p.start(s1, s2);
}
public String process(String s) {
p.process();
}
protected final String log(String s) {
// logging
}
}
Upvotes: 0
Reputation: 51037
I would design this so that class A
and class B
extend a generic abstract class, with a type parameter for the process
method's parameter type.
public abstract class BaseClass<T> {
public abstract String start(String s1, String s2);
public abstract String process(T value);
protected final String log(String s) {
// shared log implementation
}
}
public class A extends BaseClass<String> {
@Override
public String start(String s1, String s2) {
// A.start implementation
}
@Override
public String process(String s) {
// A.process implementation
}
}
public class B extends BaseClass<Long> {
@Override
public String start(String s1, String s2) {
// B.start implementation
}
@Override
public String process(Long l) {
// B.process implementation
}
}
In Java 9+, you could instead use a generic public interface Base<T>
instead of an abstract class, by giving log
a default implementation. However, that doesn't allow you to make log
only accessible to the implementing classes, and it doesn't prevent subclasses from overriding log
.
Upvotes: 6