Bulider Design Pattern to create a generic method for methods with many parameters

I have an interface Itest

as well ClassA

and ClassB

implement this interface. testA

and testB

are methods in these classes, respectively.

testA(String a, String b, String c, D d, E e)

testB(String a, String b, String c, F f, G g) 

      

Here D

, E

, F

, G

- a custom data types (related to databases). I have simplified the methods, they actually have more parameters.

I need to make a generic method in testAB

in an interface Itest

and implement that in both classes, not have my own method.

testAB(String a, String b, String c, D d, E e, F f, G g)

      

As the number of parameters is greater, the general method testAB

will be painful for the user as he has to pass so many values null

.

  • Is it a must for the Design Design Pattern ?

  • If so, how to achieve this using this design pattern?

+3


source to share


4 answers


You can use the builder pattern here. You would like to have objects containing information passed to your methods.

These objects can be created using an internal builder, while fields can contain default values. Here's a quick example of how this might look.



ParamTestA<D, E> paramA = new ParamTestA<>.Builder(a, b, c).setD(d).setE(e).build();

testA(paramA);

      

+2


source


It looks like you are trying to do something wrong:

First you need a method with a lot of common 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 try to distract from the difference in method parameters

interface ITest {
   void test(String a, Map<String, Object> params);
}

      

you will get what you want, but you will lose the general type of validation.

I will recommend this option for you anyway, because you are responsible for passing parameters to your method.

+2


source


It looks like the main requirement you have is that you don't want the client to pass additional parameters when they are not required. You can solve your problem using a simple old method overloading

:

Change your interface ITest

to have one method calledtest

public interface ITest {
     public void test(String a,String b,String c,D d,E e,F f,G g);
}

      

Edit 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 client code can call any overloaded form they want without having to skip null

. Your overloaded methods will just send a call to the generic method (which gives you the benefit 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

      

Note that client code does not have to worry about passing additional parameters when they are not required. Also notice what the client's appearance looks like. Similar changes can be made to B

.


<sub> 1. You have the ability to make an ITest

abstract class. This will allow you to make the method test

in it an access specifier protected

. The reason for wanting an access specifier protected

is to restrict client classes to be able to access the method and always go through overloaded forms. This is an optional feature that you may want to consider in the future if you are stuck using it interface

at the moment.

<sub> 2. You can also take advantage of this Generics

to avoid having to write a new class every time a new object type is introduced, but as you can see from the other answers, this can complicate your code a lot. You can also add overloaded methods to the interface ITest

to make it part of your contract. However, I am deliberately leaving these parts out of the answer, because the essence of your problem can be solved with overloading

.

<sub> 3. The sample Builder

is a creation template. This excess in this case, since classes such as D

, E

, F

and G

are the object domain. Class A

and B

does not actually depend on them in the real sense, but instead use them as a source of data.

+2


source


Parameter Object Using Template Builder

First of all, the builder pattern is a factory instance where you get a simple POJO as a result after being called .build()

or something similar in the builders instance.

Hence, the builder often uses this syntax:

SomeClass instance = new SomeClass.Builder<>(requiredArgument).optionalArgumentX(x).build();

      

This pattern often goes hand in hand with the limited scope ( private

or protected

) constructor of a particular object, but does not insist on it.

Although Timo has already provided an example where you can use a combination of patterns Parameter Object

and Builder

, writing a builder that collects arguments already captured by another developer can lead to a lot of copy & paste code (don't repeat yourself).

So I ended up with a parent builder setup that might interest you, especially if you need to extend the created settings object in the future.

The core of this extensible builder pattern is an abstract TestParam

class, which also has an abstract constructor.

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 contains all the generic parameters ( a

, b

and c

) found in your example, and an additional optional parameter z

whose type can be passed generically. Besides the abstract definition, most of the material should be straightforward. The definition of a generic builder type is that we can actually create the proper child classes through the child builders.

Child class (including 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;
    }
}

      

Most of the stuff here is pretty straightforward, 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

    - the type of object being created with the help of builder ( TestParamA

    , TestParamB

    , ...)
  • b

    - the current instance of the constructor that creates the parameter object. This looks rather complicated, but ensures that the child builder is used and not discarded to the parent builder if you use the parent-builder method.
  • D

    , E

    , z

    - is the actual types of parameters passed to the builder

I am not posting TestParamB

here as it is almost identical TestParamA

, except that it defines builder operations withF(...)

and withG(...)

instead of withD(...)

and withE(...)

, and also prints F

> and G

.

You now have several options for using your builder in conjunction with method declarations. Since I'm not sure which method is best for you, I created a small test case with several different calls:

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

      

Running this test class returns the following result:

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 other classes created with new

are just POJOs that return their simple class name in toString()

.

As you can see, each called test method contains a corresponding child parameter object, created through the corresponding builder. For more general methods like test(TestParam<?> testParam)

or testCommon(...)

, you may need to pass the parameter object to a specific class before actually accessing those methods ( getD()

, ...) that are unique to specific classes, but I think you are familiar with this concept anyway ...

CONS

  • Writing a builder creates additional overhead compared to a traditional constructor call
  • Creating a new instance also includes the additional cost of additional characters to enter

PROS

  • Possible flexible ordering of parameters. You usually don't need to remember the order of the parameters, which is pretty good if you are dealing with more than 5+ parameters. However, the required parameters are often set inside a builder constructor and therefore require a fixed ordering unless they can be specified using constructor methods.
  • Supports grouping of related parameters (for example .dimensions(int x, int y, int width, int height)

    )
  • Enter safty
  • Extensibility (as shown in this post)
  • Generated types can be used both Parameter Objects

    and thus rely on polymorphism if the generated objects follow the parent-child structure
  • Increased readability. Even though the comments on this post state the builders increase readability if you go back to the code month later and have to remember that all of these parameters were passed in. Builders add a kind of lexical semantics to parameters. Thus, readability can be increased by structuring the flowing method calls appropriately.

When to (not) use Builders

That said, builders are good, but also come with an overhead. You shouldn't use them if only a few parameters or many different independent types are to be created, as a builder must be configured for each type. Here, a simple POJO instance for the first case and a generic factory pattern for the last case are 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 mechanism for extracting the internal type (such as Camel converters), use Map<String, Object>

as a parameter object instead . Camel uses this approach for its post headers. This approach also uses the Activiti BPMN engine. (explained by AdamSkywalker in this thread)

If you have a limited number of scripts and a clear number of parameters, use a simple method overload (as explained by Chetan Kinger).

If you're struggling with remembering the exact order of the parameters over time, maybe some class extension might happen in the future, or if you have a bunch of optional parameters (maybe even with some defaults), then the developers fit in just fine.

+1


source







All Articles