zoukankan      html  css  js  c++  java
  • 《Effective Java》 读书笔记(二) 在构造参数过多的时候优先考虑使用构造器

    刚开始看见这个标题的时候,我想到了python可以选择初始化参数的语法,C++、C#能有默认参数。

    为什么Java什么都没有~~

    好吧,我们是使用构造器来实现它。

    1.当一个类的构造函数需要很多构造函数的时候,编程人员往往容易混淆弄错,而且很多情况并不需要这么多的构造函数。

    因此:

    1)是用默认构造函数构造这个对象,并在之后使用setter方法给这个对象赋值。

         但是对于多线程中线程安全考虑来说,这样做排除了使类不可变的可能性,并且想要排除这种bug也是十分困难的

      (注:由于代码量的原因,并没有深刻理解到为什么会有这样的问题,但是大概的意思可能是由于多线程的问题,有些对对象属性的访问可能会在setter构造之前调用

         这种方式就好像生产车一样。先通过默认构造函数生产出来一个车的外壳,然后通过setter添加轮子,方向盘什么的。

         但是在多线程里面,由于没有保证生产出来外壳后,接下来就一定是添加轮子,方向盘。有可能有人就直接拿着车的外壳上路了。

       )

    因此这种方式在多线程的环境下不推荐使用

    2)使用Builder内置构造器

    /**
     * @Author dengchengchao
     * @Time 2018/5/10
     * @Description
     */
    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.servings = servings;
                this.servingSize = servingSize;
            }
    
            public Builder calories(int val) {
                this.calories = val;
                return this;
            }
    
            public Builder fat(int val) {
                this.fat = val;
                return this;
            }
    
            public Builder sodium(int val) {
                this.sodium = val;
                return this;
            }
    
            public Builder carbohydrate(int val) {
                this.carbohydrate = val;
                return 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来够着函数,这样我们就可以使用:

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

    很方便的选择初始化那些参数了。

    PS1:这样使用很简单,但是在写这个Builder的时候,简直不要再麻烦。推荐使用idea的插件:Builder Generator 能快速构造。

    PS2:可以看到示例代码很好的习惯,不会改变的属性都是用final,只平时写代码的时候,除了定义常量使用final,基本都没使用过,应该多思考思考,不过final在JavaBean中作为Json反序列对象得小心使用,不然很可能序列化失败。

    2.Builder模式也非常适合层次结构,可以使用平行层次的builder,每个嵌套在相应的类中,就好像披萨类,内部可以包含各种芝士构造器,大小构造器等。

    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();
    
             protected abstract T self();
         }
    
    
         Pizza(Builder<?> builder){
             toppings=builder.toppings.clone();
         }
    }

    public class NyPizza extends Pizza {
    
        public enum Size{SMALL,MEDIUM,LARGE}
    
        private final Size size;
    
        public static class  Builder extends Pizza.Builder{
            private final Size size;
    
            public Builder(Size size){
                this.size= Objects.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;
        }
    
    
        public static void main(String[] args) {
            Calzone calzone = new Builder().addTopping(Topping.MUSHROOM).build();
        }
    }


    PS:在写这段代码的时候,我发现了这种类声明方式:

    abstract static class Builder<T extends Builder<T>>

    刚开始我一直在想为什么需要<T extends Builder<T>>,Java 面向对象最大的特点之一不是多态么?何必这么麻烦的声明类型。

    经过仔细研究这段代码和根据后续代码来看:

    1.Builder类中包含了一个self方法,需要各个子类自己实现并返回this。

    2.从后面的代码来看有些子类包含了一些父类不存在的方法。

    3.如果不加<T extends Builder<T>>.那么父类有的通用的方法addTopping应该返回什么。

    想到这些问题后,再想想解决方法就明白书上为什么要这样写了:

    因为后面的子类包含了一些父类不存在的方法,而子类的每一个set都需要返回this.那么在调用父类中通用的方法addTopping()后,就无法再调用子类的方法了,应为addTopping()在编译的时候,是返回的父类Pizza类型。

    也就是说:

    Pizza calzone=new Calzone.Builder().addTopping(Toppig.MUSHROOM).sauceInside().build(); 

    是编译不过的,因为addTopping()返回的是Pizza类型,Pizza没有sauceInside()方法。

    同样的道理。探讨下如果不加<T extends Builder<T>> 用来指定真正的类型,那么

    Calzone calzone=new Calzone.Builder().addTopping(Toppig.MUSHROOM).sauceInside().build(); 

    也是无法编译成功的,仔细看看,

    Calzone.Builder().addTopping(Topping.MUSHROOM)
    

      在编译阶段是返回父类Pizza类型的,那么它所调用的build()也是返回的Pizza类型,而左边确实它的子类型:Calzone,如果不强制转换,那么是无法从范围小的类型自动转换为范围大的类型的。

    这样的代码,对于使用Pizaa类,Calzone类的用户来说,是非常疑惑的。

    因此,我们可以定义一个self,让子类型告诉这个方法的具体返回类型。

    3.对于上面的疑问,Effective也进行了解释,只是如果不认真的思考,是想不到为什么么,感觉作者只是一笔带过。

    具体的技巧为:

    1.在定义这个父类的时候,添加一个递归类型参数的泛型类型。例如Bulider<T extend Builder<T>>

    2.定义一个self()方法,让子类实现self方法并返回this。

    3.在父类所有需要返回this的地方使用self()方法

    这种技巧称为 模拟自我类型

    可以看见buid()方法返回的具体的子类类型。NyPizza.Builder返回NyPizza类型,Calzone.Builder返回Calzone类型。这种一个子类方法被声明为返回在超类中声明的返回类型的子类型,称为 协变返回类型

    协变返回类型这种技巧可以应用在父类需要返回this

    4.构造器方法也会带来一定的性能影响和使构造方法更加冗长的缺点,

    因此最好在只有足够的参数的时候才使用它,比如4个或则更多。但是值得注意的是可能你以前不是builder而是后来发现参数越来越多的时候再转换为builder模式的时候,过时的构造方法或静态工厂就会不好处理,因此,如果采用builder模式,最好一开始就考虑好,

    终止,当参数过多的时候,使用builder是一个不错的选择。特别是在许多参数是可选的的情况下。

     
  • 相关阅读:
    hdu Dragon Balls
    acm steps chapter4总结
    bit 最牛的回文
    hdu find the most comfortable road
    hdu Farm Irrigation
    acm steps chapter5总结
    C#日期
    MFC中的CFileFind类
    区别 cfilefind 的三个查询方法 GetFileName (),GetFilePath(),GetFileTitle()
    wubi安装ubuntu时要去官网下载系统映像文件问题的方法
  • 原文地址:https://www.cnblogs.com/dengchengchao/p/9021973.html
Copyright © 2011-2022 走看看