Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java design pattern for two classes sharing identical and similar but different methods

Let's say my application has some services implemented as ClassA and ClassB. Both have some similarities but also differences.

  1. Both classes have a start() method with the same method signature but a different implementation.
  2. Both classes have a process() method with a different signature and a different implementation.
  3. Both classes have an identical log() method, i.e. the code is exactly the same.

Class A

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;
    }
}

Class B

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?

like image 253
Robert Strauch Avatar asked Jan 25 '20 20:01

Robert Strauch


People also ask

Can different classes have methods with the same name?

Yes, we can define multiple methods in a class with the same name but with different types of parameters. Which method is to get invoked will depend upon the parameters passed.

Can we use multiple design patterns in single project?

You do not have to use one pattern for every piece of functionality. In fact, many pieces of functionality don't use patterns at all, and some pieces will use multiple patterns.

Which is the most used design pattern in java?

Factory Design Pattern One of the most popular design patterns used by software developers is a factory method. It is a creational pattern that helps create an object without the user getting exposed to creational logic.


2 Answers

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.

like image 130
kaya3 Avatar answered Oct 09 '22 14:10

kaya3


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
    }
}
like image 36
David Soroko Avatar answered Oct 09 '22 14:10

David Soroko