likes
comments
collection
share

一个小技巧,使用构建器让你的代码更优雅

作者站长头像
站长
· 阅读数 14

我正在参加「掘金·启航计划」

今天一如既往的打开掘金,一条系统消息提示我已经有555天没有发文了,然后看了下最后一次发文还是在2021年11月21年。说实话,平时工作日的时候,基本每天都会逛一逛掘金(装了掘金的插件),浏览器每次打开一个新的标签页,都会弹出来掘金最近的热门文章、Github以及沸点,和以前不大一样的是,之前更多的是关注哪个大佬又写了一篇文章,我要去拜读一下,而现在呢,更多的是看一下沸点...比如哪个小伙伴要结婚了,周五了,在线秒回之类的,然后慢慢看着评论,吃瓜吃的津津有味🤣。

平时也是会去看一些书籍的,只是没有太大的意愿整理成文,一是这样确实会有点耽误时间,需要对文章进行排版,测试用例也不能写的太随意,然后组织语言进行分析讲解,二是因为语言表达确实是我的一个弱项,最后一个也是比较重要的一个原因,我确实有点懒🥶。不过为了克服上面的种种困难,我还是要坚持写作,热爱学习,至于内容的话,我准备把最近学习的内容分享出来,内容是来源于Effective Java这本书,接下来的一段时间里,都会分享一些自己的学习内容。

学习的内容


1.使用静态工厂方法代替构造器

按照平时常规的开发习惯,如果我们想要一个对象,可以直接通过new来创建,也就是通过这个类提供的共有构造器来完成,而对于一些特殊场景,我们也可以用下面这种方式:

  • 常规写法示例
Test one = new Test();
Test two = new Test(1);
Test three = new Test(1,2);
Test four = new Test(1,2,3);
  • 静态工厂方法示例1
//eg2.
Integer param = Integer.valueOf(1);

//eg2 实现代码
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
  • 静态工厂方法示例2
//eg2
Stack stack = new Stack();
stack.add(1);
Collections.list(stack.elements());

//eg2 实现代码
public static <T> ArrayList<T> list(Enumeration<T> e) {
    ArrayList<T> l = new ArrayList<>();
    while (e.hasMoreElements())
        l.add(e.nextElement());
    return l;
}
  • 静态工厂方法示例3
//eg3
Calendar calendar = Calendar.getInstance();

//eg3 实现代码,这里就贴部分代码,可以打开源码自己看下
Calendar cal = null;
if (aLocale.hasExtensions()) {
    String caltype = aLocale.getUnicodeLocaleType("ca");
    if (caltype != null) {
        switch (caltype) {
            case "buddhist":
                cal = new BuddhistCalendar(zone, aLocale);
                break;
            case "japanese":
                cal = new JapaneseImperialCalendar(zone, aLocale);
                break;
            case "gregory":
                cal = new GregorianCalendar(zone, aLocale);
                break;
        }
    }
}
return cal;

小结:

我觉得使用静态工厂方法的几处优点还是不错的

  1. 如果一个类的构造器太多,在创建对象的时候就会比较懵,如果按照规范的名称命名静态工厂方法,这样后续代码的可读性是比较高的,比如常见的valueOf、getInstance这种
  2. 不一定每次调用都创建一个新的对象,也就是可以反复使用对象,比如示例1
  3. 我们可以对方法内部进行自定义,这样我们可以返回该类型的子类对象,比如示例3

书中给出的建议就是两者各有长处,而静态工厂经常更加合适,所以以后开发可以先考虑静态工厂👍


2.如何使用构建器

今天要分享的另一个知识点,就是使用构建器来创建实例,这种方式适合这种情况:当我们创建一个新的对象时,需要传入很多参数,多的时候甚至是几十上百个,这个时候如果用下面这两种常规写法,我觉得还是蛮累的...

//方式一
Test test = new Test(1,2,3,4,5,6,7,8,9,0);

//方式二
Test test = new Test();
test.setParam1(1);
test.setParam2(2);
test.setParam3(3);
test.setParam4(4);
test.setParam5(5);
test.setParam6(6);
test.setParam7(7);
....

第一种方式的可读性基本为0,如果有更多的参数,使用这种方式,一旦调用时候出现问题,需要去检查参数的时候,看到这种编码方式,我的内心是拒绝的。

第二种方式其实可读性还是可以的,大部分时间我们可能用的就是这种方式,也不会出啥问题的,我们可以先看一下下面这段代码

public class Test {
    static Fruit apple = null;

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
            apple = new Fruit();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                //error info
            }
            apple.setName("apple");
        });

        Thread t2 = new Thread(() -> {
            System.out.println("apple name:"+apple.getName());
        });
        t1.start();
        Thread.sleep(500);
        t2.start();
    }
}

@Data
class Fruit{
    private String name;
    
}

这段逻辑我最初是希望线程t1创建好Fruit,并且进行赋值,然后t2线程来获取t1线程创建的对象的name值,这里我是夸张了一点,来模拟实际可能出现t1还没有完成name赋值时,它的CPU时间片就已经用完,CPU就会切换到执行t2逻辑,导致未达到我们的预期。

这种情况下,我们就可以使用构建器来处理,我还是先直接上代码

public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;

    public static class Builder{
        //required parameters
        private final int servingSize;
        private final int servings;

        //Optional parameters
        private int calories;
        private int fat;

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

        public Builder calories(int var){
            calories = var;
            return this;
        }
        public Builder fat(int var){
            fat = var;
            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;
    }
}

使用方式 为了让代码更简洁点,参数上就写了两个必填参数(servingSize、servings),两个个性化参数(calories、fat)。首先是在原有类中增加一个静态内部类,内部类用于对所需要的参数进行赋值,当一切赋值完成后,就会调用内部类的build方法来实例化外部类。

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

使用场景 简单来说,这种方式适合构造器有多个参数,特别是有些参数是必传,而有些又是可选参数的时候,构建器模式无论是后续扩展还是可读性以及安全性上,都是很好用的。

有几点小细节我觉得有必要注意一下:

  • 内部类为静态内部类,这句话是废话...
  • 内部类的构造器和方法是public
  • 赋值每一个参数后,要return this,因为要连续对变量赋值
  • 外部类的构造器为private

总结

今天先分享上面几个知识点,对于上面这些知识点,其实小伙伴们看书也是一样的,但是还是建议小伙伴们看完之后可以发出来,和jy们一块讨论讨论,不然只看书容易犯困,分享出来可以让记忆更加深刻,还能学习一些额外的知识点,当然也避免不了被喷,这也是加深记忆的一种方式。

写在最后

最后,我想说,给我推荐到首页吧,我想要那俩陶瓷碗😂。