《Effctive Java》阅读笔记

121 阅读6分钟

静态工厂方法代替构造器

不等同于设计模式里的工厂方法。

程序员应该让类提供一个公有的静态工厂方法,它只是一个返回类的实例的静态方法。

优势

  • 有名称 可以增加代码可读性,当一个类需要多个带有相同签名的构造器时,可以通过静态工厂方法代替构造器,并且仔细选择名称以便突出静态工厂方法之间的区别。
  • 不必在每次调用它们的时候都创建一个新对象 将构建好的实例存储起来反复使用,类似于享元模式。重复的调用返回相同对象,有助于类总能严格控制在某个时刻哪些实例应该存在。
  • 返回原返回类型的任何子类型的对象 一种灵活的实现如API可以返回对象,同时又不会使对象的类变成公有的。以这种方式隐藏实现类会使API变得简洁。适用于基于接口的框架。
    1.Java8允许接口中包含静态方法,但需要将静态方法背后的大部分实现代码单独放进一个包级私有的类中并要求接口的所有静态成员必须是公有的。 2.Java9允许接口有私有静态方法,但静态域和静态成员仍为公有。
  • 返回对象的类可以随着每次调用而发生变化,这取决于静态工厂方法的参数值。
  • 方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不存在

缺点

  • 类如果不含公有的或者受保护的构造器,就不能被子类化
  • 程序员很难发现它们

标准命名习惯

  • from——类型转换方法,一个参数,返回一个对应实例
  • of——聚合方法,带有多个参数返回一个实例把它们都合并起来
  • valueOf——比from和of更繁琐的一种替代方法
  • instancegetInstance——返回实例是通过方法参数来描述的,但是不能说与参数具有同样的值
  • createnewInstance——如上,但能确保每次返回都是新实例
  • getType——类似getInstance,工厂方法处于不同的类中的时候使用
  • newType——类似newInstance,工厂方法处于不同的类中的时候使用

遇到多个构造器参数时考虑使用构建器

  • JavaBean模式 JavaBean模式,先调用一个无参构造器来创建对象,再调用setter方法设置每个必要的参数,以及每个相关可选参数。
    但这个方法存在缺点,构造过程中JavaBean可能处于不一致的状态并且类不可变的可能性不复存在,程序员需要加倍努力保证线程安全。
    类无法仅仅通过检验构造器参数的有效性来保证一致性。\
  • 建造者模式 不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器(或静态工厂),得到一个builder对象。然后客户端在builder对象上调用类似于setter方法设置相关可选参数。最后客户端调用无参的build方法来生成通常是不可变的对象。

实例

//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 {
        //Required parameters
        private final int servingSize;
        private final int servings;

        //Optional parameters - initialized to default values
        private int calories = 0;
        private final int fat = 0;
        private final int sodium = 0;
        private final 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不可变,所有默认参数值都单独放在一个地方
  • builder的设值方法返回builder本身,以便把调用链接起来,得到一个流式API
NutritionFacts cocaCola = new NutritionFacts.Builder(240,8)
        .calories(100).sodium(35).carbohydrate(27).build();

这样编写和阅读都更容易了。Builder模式模拟了具名的可选参数

  • 具名参数的使用主要体现在函数的调用上面。 通过具名参数,可以指定特定参数的值。
  • 可以在builder的构造器和方法中检查参数的有效性 从build复制完参数之后,要检查对象域,若检查失败就抛出IllegalArgumentException,其中会想详细说明哪些参数无效。 Builder模式也适用于类层次结构。抽象类有抽象的builder,具体类有具体的builder
//Builder pattern for class hierachies
public abstract class Pizza {
    public enum Topping{HAM, MUSHROOM, ONION, PEPPER, SAUSAGE}
    final Set<Topping> toppings;

    abstract static class Builder<T extends Builder<T>> {
        EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);
        public T addTopping(Topping topping) {
            toppings.add(Objects.requireNonNull(topping));
            return self();
        }

        abstract Pizza build();
        //Subclassed must override this method to return "This"
        protected abstract T self();
    }
    Pizza(Builder<?> builder) {
        toppings = builder.toppings.clone();
    }
}

类型是泛型,其带有一个递归类型参数。
它和抽象的self方法一样,允许在子类中适当地进行方法链接,不需要转换类型。 两个具体子类

  • 经典纽约风味的披萨 需要一个尺寸参数
public class NyPizza extends Pizza {
    public enum size {SMALL, MEDIUM, LARGE}
    private final Size size;

    public static class Builder extends Pizza.Builder<Builder> {
        private final Size size;

        public Builder(Size size) {
            this.size = Object.requireNonNull(size);
        }

        @Override
        public NyPizza build() {
            return new NyPizza(this);
        }

        @Override
        protected Builder self() {
            return this;
        }
    }

    private NyPizza(Builder builder) {
        super(builder);
        size = builder.size;
    }
}
  • 馅料内置的半月型披萨 需要指定馅料是内置还是外置
public class Calzone extends Pizza {
    private final boolean sauceInside;

    public static class Builder extends Pizza.Builder<Builder> {
        private boolean sauceInside = false;

        public Builder sauceInside() {
            sauceInside = true;
            return this;
        }

        @Override
        public Calzone build() {
            return new Calzone(this);
        }

        @Override
        protected Builder self() {
            return this;
        }

        private Calzone(Builder builder) {
            super(builder);
            sauceInside = builder.sauceInside;
        }
    }
}

注意每个子类的构建器中的build方法都声明返回正确的子类。

在该方法中,子类方法声明返回超级类中声明的返回类型的子类型,被称作协变返回类型。允许客户端无须转换类型就能使用这些构建器,使用枚举常量静态导入:

MyPizza pizza = new NyPizza.Builder(SMALL)
    .addTopping(SAUSAGE).addTopping(ONION).build();
Calzone calzone = new Calzone.Builder()
    .addTopping(HAM).sauceInside().build;

十分灵活,多次调用某一个方法使传入的参数集中到一个域。利用单个builder构建多个对象,builder的参数在调用build方法来创建对象期间进行调整,也可以随着不同对象而改变。builder可以自动填充某些域,如创建对象时自增序号。

不足

创建对象必须创建构建器,在注重性能的场景下可能不适用。