有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java中最终字段初始化的oop问题:使用多个构造函数避免代码重复

我有一个课程,有一些私人的最终字段:

public class ClassA {
  private final Object field1;
  private final Object field2;
  ...
}

该类有几个不同的构造函数,具有各种参数:

public ClassA(Object arg1, Object arg2);
public ClassA(int arg1, String arg2, boolean arg3);

这些构造函数计算要放入最终字段的值

理想情况下,我想做这样的事情:

public ClassA(Object arg1, Object arg2) {
  ... // error check params
  Object value1 = ... // calculate value 1 based on args
  Object value2 = ... // calculate value 2 based on args

  init(value1, value2);
}

public ClassA(int arg1, String arg2, boolean arg3) {
  ... // error check params
  Object value1 = ... // calculate value 1 based on args
  Object value2 = ... // calculate value 2 based on args

  init(value1, value2);
}

private void init(Object arg1, Object arg2) {
  ... // error checks on combination of calculated arg1 and arg2 values
  ... // in reality there are more than 2 args, and this logic is fairly complex
  field1 = arg1;
  field2 = arg2;
  ... // other common initialization code, depends on field1 and field2
}

为了重用分配和通用初始化代码。但是,由于字段是最终字段,因此只能在构造函数调用中分配它们

在这种情况下,无法避免使用公共构造函数生成对象,因此任何类型的工厂方法都是不可能的。最好将arg1和arg2值的组合错误检查与它们对ClassA字段的赋值保持在同一块中。否则,我会将init()拆分为两个函数,一个预分配final字段,一个后分配final字段,并使我的构造函数看起来像:

public ClassA(int arg1, String arg2, boolean arg3) {
  ... // error check params
  Object value1 = ... // calculate value 1 based on args
  Object value2 = ... // calculate value 2 based on args

  preinit(value1, value2);  // error checks combination of values
  field1 = value1;
  field2 = value2;
  postinit();  // other common initialization code
}

建议?有没有办法避免这种情况,或者我被困在分割init()函数的过程中了


共 (5) 个答案

  1. # 1 楼答案

    为什么不在静态工厂方法中创建对象value1value2?然后,您可以使用不同的工厂方法替换当前的构造函数,并使用单个(私有)构造函数执行init中现在所做的操作:

    public static createWithObjectParams(Object arg1, Object arg2) {
      ... // error check params
      Object value1 = ... // calculate value 1 based on args
      Object value2 = ... // calculate value 2 based on args
    
      return new ClassA(value1, value2);
    }
    
    public static createWithPrimitiveParams(int arg1, String arg2, boolean arg3) {
      ... // error check params
      Object value1 = ... // calculate value 1 based on args
      Object value2 = ... // calculate value 2 based on args
    
      return new ClassA(value1, value2);
    }
    
    private ClassA(Object arg1, Object arg2) {
      ... // error checks on combination of calculated arg1 and arg2 values
      ... // in reality there are more than 2 args, and this logic is fairly complex
      field1 = arg1;
      field2 = arg2;
      ... // other common initialization code, depends on field1 and field2
    }
    
  2. # 2 楼答案

    不知道这是否是一个选项,但您可以使用依赖项注入,也就是说,您可以创建字段在其他地方引用的对象,然后将它们传递到构造函数中。这使你的类更容易测试,也更灵活

    public class ClassA {
      private final Object field1;
      private final Object field2;
    
      public Class A(Object field1, Object field2) {
            this.field1 = field1;
            this.field2 = field2;
      }
    }
    
    
    public class AppFactory {
        private AppFactory() {}
    
        public static ClassA newInstance(Object arg1, Object arg2) {
            // create the fields there
            ...
    
            // pass them in
            return new ClassA(field1, field2);         
        }
    
        public static ClassA newInstance(int arg1, String arg2, boolean arg3) {
            // create fields there, in a different way
    
            // pass them in
            return new ClassA(field1, field2);
        }
    }
    

    这样,你就可以:

    • 按其超类型引用构造函数参数
    • 在工厂中,返回ClassA的子类型
    • 为字段传入模拟对象(虚拟对象),以便单独测试类
    • 还有很多好处
  3. # 3 楼答案

    首先,我会尝试查看生成器模式,但如果您坚持将其作为构造函数,并且字段应该是最终字段,请使用私有构造函数:

    public class ClassA {
        private final Object field1;
        private final Object field2;
    
        public ClassA(Object arg1, Object arg2) {
            this(calc1(arg1, arg2), calc2(arg1, arg2), true);
        }
    
        public ClassA(int arg1, String arg2, boolean arg3) {
            this(calc1(arg1, arg2, arg3), calc2(arg1, arg2, arg3), true);
        }
    
        private ClassA(Object arg1, Object arg2, boolean a) {
            field1 = arg1;
            field2 = arg2;
        }
    
        private static Object calc1(int arg1, String arg2, boolean arg3) {
            return ... // calculate value 1 based on args
        }
    
        private static Object calc2(int arg1, String arg2, boolean arg3) {
            return ... // calculate value 2 based on args
        }
    
        private static Object calc1(Object arg1, Object arg2) {
            return ... // calculate value 1 based on args
        }
    
        private static Object calc2(Object arg1, Object arg2) {
            return ... // calculate value 2 based on args
        }
    
    }
    
  4. # 4 楼答案

    您可以将所有内容下推到一个2参数构造函数,以便它是唯一一个实际初始化最终字段并进行验证的构造函数

    例如

    public ClassA(int arg1, String arg2, boolean arg3) {
       this(deriveO1(arg1, arg2, arg3));
       this(deriveO2(arg1, arg2, arg3));
    }
    
    public ClassA(Object arg1, Object arg2) {
       field1 = arg1;
       field2 = arg2;
       // do initialization checks, you can have this in a separate method, or check directly here.   
    }
    

    如果你不想直接调用两个对象的版本(例如,假设你想在赋值之前进行预验证),那么就声明一个“伪”参数。这感觉有点错误,但如果你想让公共构造函数和私有构造函数在语义上具有相同的参数(它们必须在语法上是不同的),这确实是唯一的选择

    public ClassA(int arg1, String arg2, boolean arg3) {
       Object o1 = ...; 
       Object o2 = ...;
       this(o1, o2, false);
    }
    
    public ClassA(Object arg1, Object arg2) {
       this(arg1, arg2, false);
    }
    
    private ClassA(Object arg1, Object arg2, boolean dummy) {
       field1 = arg1;
       field2 = arg2;
       // do initialization checks, you can have this in a separate method, or check directly here.   
    }
    
  5. # 5 楼答案

    您可以将构造函数链接在一起,而不是使用单独的init方法。因此,计算fieldfield2,然后调用初始化它们的构造函数。基本上,将init替换为:

    private classA(Object arg1, Object arg2) {
      ... // error checks on combination of calculated arg1 and arg2 values
      ... // in reality there are more than 2 args, and this logic is fairly complex
      field1 = arg1;
      field2 = arg2;
      ... // other common initialization code, depends on field1 and field2
    }
    

    当然,只有一个构造函数的参数为2Objects