1
当我们使用 new 关键字创建对象后,给对象的属性赋值有很多方式,如果参数很多,有些参数可选、有些参数必选,哪种赋值方式最好?下面我们来分析一下。

使用set方法

最简单的方式是 new 一个默认的对象,通过 set 方法给对象的属性赋值。

// JavaBeans Pattern - allows inconsistency, mandates mutability
public class NutritionFacts {
    // Parameters initialized to default values (if any)
    private int servingSize  = -1; // Required; no default value
    private int servings     = -1; // Required; no default value
    private int calories     = 0;
    private int fat          = 0;
    private int sodium       = 0;
    private int carbohydrate = 0;

    public NutritionFacts() { }

    // Setters
    public void setServingSize(int val)  { servingSize = val; }
    public void setServings(int val)    { servings = val; }
    public void setCalories(int val)    { calories = val; }
    public void setFat(int val)         { fat = val; }
    public void setSodium(int val)      { sodium = val; }
    public void setCarbohydrate(int val) { carbohydrate = val; }
}

创建对象的时候,只需要 set 即可,参数多的时候似乎有些冗长。

NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);

使用重载的构造函数

public class XMLConfigBuilder extends BaseBuilder {  
    private boolean parsed;  
    private final XPathParser parser;  
    private String environment;  
    private final ReflectorFactory localReflectorFactory;
    
    public XMLConfigBuilder(InputStream inputStream) {  
        this(inputStream, null, null);  
    }
    
    public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {  
        this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);  
    }
    
    private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {  
        super(new Configuration());  
        this.localReflectorFactory = new DefaultReflectorFactory();  
        ErrorContext.instance().resource("SQL Mapper Configuration");  
        this.configuration.setVariables(props);  
        this.parsed = false;  
        this.environment = environment;  
        this.parser = parser;  
    }

上面代码是 mybatis 里面 XMLConfigBuilder 对象创建的部分代码,使用重载函数创建对象。这种方式还不错。

静态工厂方法

静态工厂方法与设计模式中的工厂方法模式不同

如:构造一个 Double 对象,入参是 String 类型。

public static Double valueOf(String s) throws NumberFormatException {  
    return new Double(parseDouble(s));  
}

如:构造一个 DateTimeFormatter 对象,入参是 String 类型。

public static DateTimeFormatter ofPattern(String pattern) {  
    return new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter();  
}

上面的两个例子是JDK提供的,我们自己如何编写呢?

public static User of(Long userId, String userName, String type) {  
    User user = new User();  
    user.setUserId(userId);  
    user.setUserName(userName);  
    user.setType(type);  
    return user;  
}

使用静态方法,传入入参,在方法对象内部 new 对象,然后返回。

优点

  • 可以返回其返回类型的任何子类型的对象
  • 它们不需要每次调用时都创建一个新对象
  • 与构造方法不同,它们是有名字的
  • 返回对象的类可以根据输入参数的不同而不同

缺点:程序员很难找到它们,遵守标准的命名习惯,也可以弥补这一劣势

  • from —— 类型转换方法,它接受单个参数并返回此类型的相应实例,例如:

     Date d = Date.from(instant);
  • of —— 聚合方法,接受多个参数并返回该类型的实例,并把他们合并在一起,例如

    SetfaceCards = EnumSet.of(JACK, QUEEN, KING);
  • valueOf —— from 和 to 更为详细的替代 方式,例如:

    BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
  • getinstance —— 返回一个由其参数 (如果有的话) 描述的实例,但不能说它具有相同的值,例如:

       StackWalker luke = StackWalker.getInstance(options);

使用builder模式

// Builder Pattern
public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    public static class Builder {
        // 必选参数
        private final int servingSize;
        private final int servings;

        // 可选参数,初始化默认值
        private int calories      = 0;
        private int fat           = 0;
        private int sodium        = 0;
        private int carbohydrate  = 0;

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings    = servings;
        }

        public Builder calories(int val) { 
            calories = val;      
            return this;
        }

        public Builder fat(int val) { 
           fat = val;           
           return this;
        }

        public Builder sodium(int val) { 
           sodium = val;        
           return this; 
        }

        public Builder carbohydrate(int val) { 
           carbohydrate = val;  
           return this; 
        }

        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }

    private NutritionFacts(Builder builder) {
        servingSize  = builder.servingSize;
        servings     = builder.servings;
        calories     = builder.calories;
        fat          = builder.fat;
        sodium       = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }
}

使用方式:

NutritionFacts cocaCola =  new  NutritionFacts.Builder(240,  8)  .calories(100).sodium(35).carbohydrate(27).build();

builder 模式只需要记住他们的代码编写方式即可,我总觉得这样写代码量超多。

编码步骤:

  • 编写原生对象 Person 的属性
public class Person {  
    private Integer id;  
    private String name;  
    private Integer age;  
}
  • 编写 Builder 静态内部类,属性将上面的属性复制即可。
public class Person {  
    private Integer id;  
    private String name;  
    private Integer age;   
  
    public static class Builder {  
        private Integer id;  
        private String name;  
        private Integer age;  
    }
}
  • 编写私有的创建 Person 的构造器,入参是 Builder 对象。
public class Person {  
    private Integer id;  
    private String name;  
    private Integer age;  
  
    public static class Builder {  
        private Integer id;  
        private String name;  
        private Integer age;  
    }
 
    private Person(Builder builder) {  
        id = builder.id;  
        name = builder.name;  
        age = builder.age;  
    }
}
  • Builder 对象创建构造器,将必填的属性作为入参,如 id
public class Person {
    private Integer id;
    private String name;
    private Integer age;

    public static class Builder {
        private Integer id;
        private String name;
        private Integer age;

        public Builder(Integer id) {
            this.id = id;
        }
    }
    private Person(Builder builder) {
        id = builder.id;
        name = builder.name;
        age = builder.age;
    }
}
  • 将其它可选属性,通过方法为其赋值,并且返回 Builder 对象。如下面的 name 和 age 。
public class Person {
    private Integer id;
    private String name;
    private Integer age;

    public static class Builder {
        private Integer id;
        private String name;
        private Integer age;
        public Builder(Integer id) {
            this.id = id;
        }
        public Builder name(String name) {
            this.name = name;
            return this;
        }
        public Builder age(Integer age) {
            this.age = age;
            return this;
        }
    }
    private Person(Builder builder) {
        id = builder.id;
        name = builder.name;
        age = builder.age;
    }
}
  • 使用 build() 方法返回 Person 对象。
public class Person {
    private Integer id;
    private String name;
    private Integer age;

    public static class Builder {
        private Integer id;
        private String name;
        private Integer age;

        public Builder(Integer id) {
            this.id = id;
        }
        public Builder name(String name) {
            this.name = name;
            return this;
        }
        public Builder age(Integer age) {
            this.age = age;
            return this;
        }
        public Person build() {
            return new Person(this);
        }
    }
    private Person(Builder builder) {
        id = builder.id;
        name = builder.name;
        age = builder.age;
    }
}

通过以上几步,就可以完成 Builder 模式的设计。


mybatis 中的 SqlSessionFactoryBuilder 使用了 builder 模式的变体。

public class SqlSessionFactoryBuilder {  
  
    public SqlSessionFactory build(InputStream inputStream) {  
        return build(inputStream, null, null);  
    }  
  
    public SqlSessionFactory build(InputStream inputStream, String environment) {  
        return build(inputStream, environment, null);  
    }  
  
    public SqlSessionFactory build(InputStream inputStream, Properties properties) {  
        return build(inputStream, null, properties);  
    }  
  
    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {  
        try {  
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);  
            return build(parser.parse());  
        } catch (Exception e) {  
            throw ExceptionFactory.wrapException("Error building SqlSession.", e);  
        } finally {  
            ErrorContext.instance().reset();  
            try {  
                inputStream.close();  
            } catch (IOException e) {  
                // Intentionally ignore. Prefer previous error.    
            }  
        }  
    }  
  
    public SqlSessionFactory build(Configuration config) {  
        return new DefaultSqlSessionFactory(config);  
    }  
  
}

这种方式的使用场景是:传入的参数并不多,但是对于参数的处理及其复杂,需要生成的对象的入参也不多,这个时候这种模式就非常适用。


指尖改变世界
27 声望6 粉丝