I have an interface Itest
and ClassA
& ClassB
are implementing this interface. testA
& testB
are met
Looks like the core requirement that you have is that you don't want the client to be passing additional parameters when they are not required. You can solve your problem using plain old method overloading
:
Alter your ITest
interface to have a single method called test
public interface ITest {
public void test(String a,String b,String c,D d,E e,F f,G g);
}
Alter A
as follows :
public class A implements ITest {
//this is an overload - v1
public void test(String a,String b,String c,D d,E e) {
//dispatch the call to the overriden method
test(a,b,c,d,e,null,null);
}
//this is an overload - v2
public void test(String a,String b,String c,E e,F f) {
//dispatch the call to the overriden method
test(a,b,c,null,null,e,f);
}
@Override
//this is an overriden method - v3
public void test(String a,String b,String c,D d,E e,F f,G g) {
if(d!=null && e!=null) {
//use a,b,c,d,e and do something
}
if(f!=null && g!=null) {
//use a,b,c,f,g and do something
}
}
}
Now the client code can call whichever overloaded form they want without the need to pass null
. Your overloaded methods will simply dispatch the call to a common method (which gives you the advantage of code reuse):
classAObj.test("1","2","3",new D(),new E());//calls overloaded method - v1
classAObj.test("1","2","3",new F(),new G());//calls overloaded method - v2
classAObj.test("1","2","3",new D(),new E(),new F(),new G());//calls overriden method - v3
Notice how the client code does not have to worry about passing additional parameters when they are not required. Also notice how clean the client calls look. Similar changes can be made in B
as well.
1. You have an option to make ITest
an abstract class. This will allow you to make the test
method in it have the protected
access specifier. The reason for wanting a protected
access specifier is to restrict client classes from being able to access the method and always go through the overloaded forms instead. This is an add-on feature you can consider implementing in the future if you are stuck with using an interface
at the moment.
2. You could also take advantage of Generics
to avoid the need to write a new class every-time a new object type is introduced but as you can see from the other answers, this can easily complicate your code to a great extent. You could also add the overloaded methods to ITest
interface to make it a part of its contract. However, I am deliberately leaving these parts out of my answer because the crux of your problem can be solved by using overloading
.
3. The Builder
pattern is a creational pattern. It's an overkill in this particular case since classes such as D
,E
,F
and G
are domain objects. Class A
and B
don't really depend on them in the real sense but use them as a source of data instead.
Yep here you could use the builder pattern. You would like to have objects holding the information passed to your methods.
These objects could be created with an internal builder, while the fields may hold default values. Here a small example how this could look like.
ParamTestA<D, E> paramA = new ParamTestA<>.Builder(a, b, c).setD(d).setE(e).build();
testA(paramA);
Feels like you trying to do something wrong:
First you want a method with lots of generic parameters in your interface
interface ITest<D,E,F,G> {
void test(String a, D d, E e, F f, G g)
}
This is wrong because your interface is tightly coupled with implementation details
If you will try to abstract from difference in method parameters
interface ITest {
void test(String a, Map<String, Object> params);
}
you will get what you want, but you will loose generic type check.
Anyway I will recommend you this variant, because you are responsible for passing parameters to your method.
Parameter Object through Builder-Pattern
First and foremost, The builder pattern is kind of an instance factory where you get a simple POJO as a result after calling .build()
or something similar to that on the builders instance.
The builder therefore follows often this kind of syntax:
SomeClass instance = new SomeClass.Builder<>(requiredArgument).optionalArgumentX(x).build();
This pattern often goes hand in hand with a limited scope (private
or protected
) of the constructor of the concrete object, but does not insist on that.
Although Timo has already given an example where you can use a combination of the Parameter Object
and Builder
patterns, writing a builder which collects arguments already captured by an other builder before may lead to plenty of copy& paste code (don't repeat yourself).
I've therefore come up with a parental builder setup which might be of interest to you especially if you might need to extend the generated parameter object in future.
The core of this extensible builder pattern is an abstract TestParam
class which has also an abstract builder defined.
public abstract class TestParam<Z>
{
public static abstract class CommonBuilder<T extends CommonBuilder<T, Z>, Z>
{
protected final String a;
protected final String b;
protected final String c;
protected Z z = null;
public CommonBuilder(String a, String b, String c)
{
this.a = a;
this.b = b;
this.c = c;
}
public T withOptionalZ(Z z)
{
this.z = z;
return (T)this;
}
public abstract <T> T build();
}
protected final String name;
protected final String a;
protected final String b;
protected final String c;
protected Z z = null;
protected TestParam(String name, String a, String b, String c)
{
this.name = name;
this.a = a;
this.b = b;
this.c = c;
}
protected TestParam(String name, String a, String b, String c, Z z)
{
this.name = name;
this.a = a;
this.b = b;
this.c = c;
this.z = z;
}
public String getA()
{
return a;
}
public String getB()
{
return b;
}
public String getC()
{
return c;
}
protected abstract String getContent();
@Override
public String toString()
{
return name+"[A: " + a + ", B: " + b + ", C: " + c + (z != null ? ", Z: " + z.toString() : "") + getContent() +"]";
}
}
This abstract class have all the common parameters (a
, b
and c
) found in your example and an additional optional parameter z
whose type can be passed generically. Except from the abstract definition most of the stuff should be straight forward. The definition of the generic builder type is so that we can actually create proper child classes via child builders.
A child class (including a child builder) can now look like this:
public class TestParamA<D,E,Z> extends TestParam<Z>
{
public static class Builder<T extends TestParamA<D,E,Z>, B extends TestParamA.Builder<? extends TestParamA<D,E,Z>, ? extends B, D,E,Z>, D,E,Z> extends TestParam.CommonBuilder<TestParamA.Builder<?,?, D,E,Z>, Z>
{
protected D d;
protected E e;
public Builder(String a, String b, String c)
{
super(a, b, c);
}
public B withD(D d)
{
this.d = d;
return (B)this;
}
public B withE(E e)
{
this.e = e;
return (B)this;
}
@Override
public <T> T build()
{
TestParamA t = new TestParamA("TestParamA", a, b, c, z, d, e);
return (T)t;
}
}
protected final D d;
protected final E e;
protected TestParamA(String name, String a, String b, String c, Z z, D d, E e)
{
super(name, a, b, c, z);
this.d = d;
this.e = e;
}
public D getD()
{
return d;
}
public E getE()
{
return e;
}
@Override
protected String getContent()
{
return ", D: " + d + ", E: " + e;
}
}
Here most of the stuff is quite simple except for the generic type definition:
Builder<T extends TestParamA<D,E,Z>,
B extends TestParamA.Builder<? extends TestParamA<D,E,Z>, ? extends B, D,E,Z>,
D,E,Z>
extends TestParam.CommonBuilder<TestParamA.Builder<?,?, D,E,Z>, Z>
T
is the type of the object to create via the builder (TestParamA
, TestParamB
, ...)B
is the current instance of the builder which creates the parameter object. This looks rather complicated but gurantees that the child-builder is used and not fallback to the parent-builder if you use a method from the parent-builder.D
, E
, Z
are the actual types of the parameters passed to the builderI don't post TestParamB
here as this is almost identical to TestParamA
except that it defines builder-operations withF(...)
and withG(...)
instead of withD(...)
and withE(...)
and also prints the F
and G
equivalent output.
You have now a couple of options to use your builder in conjunction with method declarations. As I'm not sure which method is best suited for you, I've created a small test-case with multiple different invocations:
public class Main
{
public static void main(String ... args)
{
TestParamA<D,E,?> a = new TestParamA.Builder<>("a","b","c").withD(new D()).withE(new E()).build();
TestParamB<F,G,String> b = new TestParamB.Builder<>("a","b","c").withF(new F()).withG(new G()).withOptionalZ("z").build();
TestParam<String> c = new TestParamA.Builder<>("a","b","c").withD(new D()).withE(new E()).withOptionalZ("z").build();
TestParam d = new TestParamB.Builder<>("a","b","c").withF(new F()).withG(new G()).build();
test(a);
test(b);
test(c);
test(d);
test(new TestParamA.Builder<>("a","b","c").withD(new D()).withE(new E()));
test(new TestParamB.Builder<>("a","b","c").withF(new F()).withG(new G()).withOptionalZ("z"));
testCommon(new TestParamA.Builder<>("a","b","c").withD(new D()).withE(new E()).withOptionalZ("z"));
testCommon(new TestParamB.Builder<>("a","b","c").withF(new F()).withG(new G()));
}
public static void test(TestParamA<?,?,?> testParam)
{
System.out.println("Test for ParamA: " + testParam.toString());
}
public static void test(TestParamB<?,?,?> testParam)
{
System.out.println("Test for ParamB: " + testParam.toString());
}
public static void test(TestParam<?> testParam)
{
System.out.println("Test for Param: " + testParam.toString());
}
public static void test(TestParamA.Builder<?,?,?,?,?> builder)
{
System.out.println("Test for BuilderA: " + builder.build().toString());
}
public static void test(TestParamB.Builder<?,?,?,?,?> builder)
{
System.out.println("Test for BuilderB: " + builder.build().toString());
}
public static void testCommon(TestParam.CommonBuilder<?,?> builder)
{
System.out.println("Test for CommonBuilder: " + builder.build().toString());
}
}
On running this test class the following output should be returned:
Test for ParamA: TestParamA[A: a, B: b, C: c, D: D, E: E]
Test for ParamB: TestParamB[A: a, B: b, C: c, Z: z, F: F, G: G]
Test for Param: TestParamA[A: a, B: b, C: c, Z: z, D: D, E: E]
Test for Param: TestParamB[A: a, B: b, C: c, F: F, G: G]
Test for BuilderA: TestParamA[A: a, B: b, C: c, D: D, E: E]
Test for BuilderB: TestParamB[A: a, B: b, C: c, Z: z, F: F, G: G]
Test for CommonBuilder: TestParamA[A: a, B: b, C: c, Z: z, D: D, E: E]
Test for CommonBuilder: TestParamB[A: a, B: b, C: c, F: F, G: G]
new D()
and the other classes created with new
are just simple POJOs which return their simple class name in toString()
.
As can be seen each invoked test-method contains the appropriate child parameter object created through the corresponding builder. For more general methods like test(TestParam<?> testParam)
or testCommon(...)
you might need to cast the parameter object to the concrete class before actually gaining access to those methods (getD()
, ...) unique to the concrete classes - but I guess you are familiar with that concept anyway.
CONS
PROS
.dimensions(int x, int y, int width, int height)
)Parameter Objects
and thus rely on polymorphism if the created objects follow a parent-child structureWhen (not) to use Builders
That being said, builders are nice but also come with overhead. You should not use them if there are only few parameters or many different independent types should be created as a builder needs to be set up for each type. Here a simple POJO instantiation for the first case and a general factory pattern for the latter case is superior IMO.
If your approach needs to be as flexible as possible and you don't need to rely on type safety or provide some internal type extraction mechanism (like Camel's type converters), use Map<String, Object>
as parameter object instead. Camel uses this approach for its message headers. Also the Activiti BPMN engine uses this approach. (explained by AdamSkywalker in this thread)
If you have a limited number of scenarios and a clear number of parameters, use simple method overloading (as explained by Chetan Kinger).
If you struggle with remembering the exact order of parameters over time, there might be some kind of class extension going on in the future or if you have a bunch of optional parameters (maybe even with some default values) builders come in nicely.