Bulider Design Pattern为具有大量参数的方法制作通用方法

时间:2015-06-04 09:23:24

标签: java design-patterns

我有一个界面ItestClassA& ClassB正在实现此接口。 testA& testB分别是这些类中的方法。

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

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

此处DEFG是自定义数据类型(与数据库相关)。我简化了方法实际上他们有更多的参数。

我需要在testAB接口的Itest中创建一个泛型方法,并在两个类中实现它,而不是拥有自己的方法。

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

由于参数的数量越多,泛型方法testAB对用户来说就会很痛苦,因为他必须传递这么多null个值。

  • 这是 Bulider设计模式的用例吗?

  • 如果是,如何使用此设计模式实现此目的?

4 个答案:

答案 0 :(得分:2)

是的,你可以使用构建器模式。 您希望将包含信息的对象传递给您的方法。

可以使用内部构建器创建这些对象,而字段可以包含默认值。这是一个小例子,看起来像这样。

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

testA(paramA);

答案 1 :(得分:2)

感觉就像你试图做错了一样:

首先,您需要一个在界面中包含大量通用参数的方法

List<KalturaCategory> subCategories = new ArrayList<KalturaCategory>();
traverse( category, subCategories );

这是错误的,因为您的界面与实现细节紧密结合

如果你试图从方法参数的差异中抽象出来

interface ITest<D,E,F,G> {
   void test(String a, D d, E e, F f, G g)
}

你会得到你想要的东西,但你会失去通用类型检查。

无论如何,我会推荐你​​这个变种,因为你负责将参数传递给你的方法。

答案 2 :(得分:2)

看起来您的核心要求是您不希望客户端在不需要时传递其他参数。您可以使用普通的方法overloading解决问题:

ITest界面改为使用名为test

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

更改A如下:

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

现在,客户端代码可以调用他们想要的任何重载表单,而无需传递null。您的重载方法将简单地将调用分派给一个公共方法(这为您提供了代码重用的优势):

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

请注意客户端代码在不需要时不必担心传递其他参数。还要注意客户端调用的外观。也可以在B中进行类似的更改。

<子> 1。您可以选择将ITest作为抽象类。这将允许您使其中的test方法具有protected访问说明符。想要protected访问说明符的原因是限制客户端类无法访问该方法,而是始终通过重载的表单。这是一个附加功能,如果您目前仍在使用interface,可以考虑在将来实施。

<子> 2。您还可以利用Generics来避免在每次引入新对象类型时编写新类,但从其他答案中可以看出,这很容易使您的代码在很大程度上复杂化。您还可以将重载方法添加到ITest接口,以使其成为合同的一部分。但是,我故意将这些部分从我的答案中删除,因为问题的关键可以通过使用overloading来解决。

<子> 3。 Builder模式是一种创造模式。由于DEFG等类是域对象,因此在此特定情况下这是一种矫枉过正。类AB并不真正依赖于它们,而是将它们用作数据源。

答案 3 :(得分:1)

参数对象通过Builder-Pattern

首先,构建器模式是一种实例工厂,在调用.build()或类似于构建器实例的类似之后,您将得到一个简单的POJO。

因此,构建器通常遵循这种语法:

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

这种模式通常与具体对象的构造函数的有限范围(privateprotected)齐头并进,但并不坚持。

虽然Timo已经给出了一个可以使用Parameter ObjectBuilder模式组合的示例,但编写一个构建器来收集其他构建器之前已经捕获的参数可能会导致大量复制和放大;粘贴代码(不要重复自己)。

因此,我提出了一个父母建设者设置,您可能会感兴趣,特别是如果您将来可能需要扩展生成的参数对象。

这个可扩展构建器模式的核心是一个抽象的TestParam类,它还定义了一个抽象构建器。

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

此抽象类具有在您的示例中找到的所有常用参数(abc)以及其类型可以一般传递的其他可选参数z 。除了抽象的定义,大多数东西应该是直截了当的。通用构建器类型的定义是,我们实际上可以通过子构建器创建适当的子类。

子类(包括子构建器)现在看起来像这样:

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

除了泛型类型定义之外,大部分内容都非常简单:

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是通过构建器创建的对象的类型(TestParamATestParamB,...)
  • B是构建参数对象的构建器的当前实例。这看起来相当复杂,但保证使用子构建器,如果您使用父构建器中的方法,则不会回退到父构建器。
  • DEZ是传递给构建器的参数的实际类型

我不会在此TestParamB发帖,因为它与TestParamA几乎相同,只是它定义了构建器操作withF(...)withG(...)而不是{{1} }和withD(...)并打印withE(...)F等效输出。

现在有几个选项可以将构建器与方法声明结合使用。由于我不确定哪种方法最适合您,我已经创建了一个包含多个不同调用的小型测试用例:

G

运行此测试类时,应返回以下输出:

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

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()创建的其他类只是简单的POJO,它们在new中返回其简单的类名。

可以看出,每个调用的测试方法都包含通过相应构建器创建的相应子参数对象。对于更常见的方法,如toString()test(TestParam<?> testParam),您可能需要将参数对象强制转换为具体类,然后才能实际获取对具体特有的方法(testCommon(...),...)的访问权限课程 - 但我想你无论如何都熟悉这个概念。

<强> CONS

  • 与传统的构造函数调用相比,编写构建器会产生额外的开销
  • 创建新实例还需要额外填写额外字符的费用

<强>赞成

  • 可以灵活调整参数顺序。通常你不必记住参数的顺序,如果你处理5个以上的参数,这是非常好的。但是,必需的参数通常在构建器的构造函数中指定,因此需要固定的顺序,除非可以使用builder-methods指定它们。
  • 支持对相关参数(例如getD()
  • 进行分组
  • 输入安全
  • 可扩展性(如本文所示)
  • 生成的类型可以用作.dimensions(int x, int y, int width, int height),因此如果创建的对象遵循父子结构,则依赖于多态性
  • 提高可读性支持。虽然在这篇文章的评论中有争议,但如果你在几个月后返回代码并且必须记住所有这些参数传递的内容,那么构建器会提高可读性。构建者为参数添加某种词汇语义。因此,通过适当地构造流畅的方法调用,可以提高可读性

何时(不)使用建设者

话虽这么说,建设者很好,但也带来了开销。如果只有少数参数或者应创建许多不同的独立类型,则不应使用它们,因为需要为每种类型设置构建器。这里第一个案例的简单POJO实例化和后一个案例的一般工厂模式是优秀的IMO。

如果您的方法需要尽可能灵活,并且您不需要依赖类型安全或提供一些内部类型提取机制(如Camel的类型转换器),请使用Parameter Objects而是作为参数对象。 Camel将此方法用于其邮件头。 Activiti BPMN引擎也使用这种方法。 (由AdamSkywalker在这篇帖子中解释)

如果您的场景数量有限且参数数量明显,请使用简单的方法重载(如Chetan Kinger所述)。

如果你很难记住参数随时间的确切顺序,将来可能会有某种类扩展,或者如果你有一堆可选参数(甚至可能有一些默认值),那么构建器很好地进入