Builde设计模式/ImageLoader例

1 使用Builder创建Config

/**
 * config的Builder模式创建
 * Config只有变量和私有构造 === 设置项在builder中完成
 */
public class ImageLoaderConfig {
    //线程默认数 cup数量+1
    int threadCount = Runtime.getRuntime().availableProcessors() + 1;

    private ImageLoaderConfig() {
    }
    
    // builder
    public static class Builder {
        //线程默认数 cup数量+1
       private int threadCount = Runtime.getRuntime().availableProcessors() + 1;

        public Builder setThreadCount(int count) {
            threadCount = Math.max(1, count);
            return this;
        }

        void applyConfig(ImageLoaderConfig config) {
            config.threadCount = this.threadCount;
        }

        public ImageLoaderConfig create() {
            ImageLoaderConfig config = new ImageLoaderConfig();
            applyConfig(config);
            return config;
        }
    }
}

2 ImageLoader

/**
 * 图片加载器,使用config
 */
public class ImageLoader {
    private ImageLoaderConfig mConfig;
    private ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    public void init(ImageLoaderConfig config){
        mConfig = config;
        freshConfig();
    }

    private void freshConfig() {
       mExecutorService.shutdown();
       mExecutorService = null;
       //设置新的线程数量
       mExecutorService = Executors.newFixedThreadPool(mConfig.threadCount);
    }
}

3 使用


/**
 * 使用
 */
public class User {
    public void use(){
        //使用builder模式创建config
        ImageLoaderConfig imageLoaderConfig = new ImageLoaderConfig.Builder()
                .setThreadCount(3)
                .create();
        ImageLoader imageLoader = new ImageLoader();
        imageLoader.init(imageLoaderConfig);//初始化config
    }
}

为什么一定要弄一个Builder内部类?

使用Builder大概有两个用途

解决具有大量参数的构造函数不好用的问题

解决让Object始终保持valid状态的问题

下面分别说说两个用途。

Java支持使用constructor初始化一个object,但是如果object的成员非常多,constructor就不好用了。巨长的参数列表很难分清楚哪个是哪个。这时最好用某种方式让哪个字段是哪个更加清晰一点。Builder模式算是可以解决这个问题。此外题主在题目中那种让setter返回this的形式也可以解决。并且,能用"."连接下来配合IDE的自动提示还能够提高编码速度,这算是一个额外的便利。

如果追一下oop设计的本源,一个object从出生开始到被销毁,应该始终维护一种“不变量“(invariant),或者叫做始终处于valid状态。

比如,设计了一个Rectangle类,有两个字段width和height。为了让object的状态始终valid,我要求width和height必须都是正数,并且width与height之积不得超过100。假设class的定义是这样的:

public class Rectangle { private int width; private int height; } 如果要用setter初始化的话,那么object就会在完全被set好之前处于“invalid”的状态。

Rectangle r = new Rectange(); // r is invalid, not good r.setWidth(2); // r is invalid, not good r.setHeight(3); // r is valid 为了避免这种invalid状态的发生,就要求使用构造函数一次性初始化好所有的成员。但是如果是一个很多成员的class,构造函数不好用,那么唯一合理的办法就是做一个builder。这样builder就可以分多步初始化所有成员,build的结果出来就是一个处于valid状态的object。

当然,这个事情并不一定要这么解决,比如如果业务允许,你可以给Rectangle的成员设置合理的初始值,然后再用setter改,像这样:

public class Rectangle { private int width = 1; private int height = 1; } Rectangle r = new Rectangle(); r.setWidth(3); r.setHeight(4); 有一类很特别的object就是“不可变object”。不可变是个很好的避免程序出问题的方法。具体“为什么不可变是一件好事情”的原因这里不展开了。为了得到一个不可变的object,是不可能使用任何setter方法的,必须使用构造函数一上来就把所有数据都设置好。因为多参数构造函数不好用,所以这里就得靠builder。

public class ImmutableObject {
    private int value;
     
    public ImmutableObject(int value) {
        this.value = value;
    }
     
    public int getValue() {
        return this.value;
    }
}

public class Rectangle { private final int width; private final int height; public Rectangle(int w, int h) { if (w <= 0 || h <= 0 || w * h > 100) { throw new RuntimeException("this rectangle is not valid!"); } this.width = w; // 留意因为是final,所以必须在这里就初始化 this.height = h; } }

public class RectangleBuilder { private int width; private int height; public RectangleBuilder setWidth(int w) {this.width = w; return this;) public RectangleBuilder setHeight(int h) {this.height = h; return this;} public Rectangle build() { return new Rectangle(this.width, this.height); } }

RectangleBuilder rb = new RectangleBuilder(); rb.setWidth(3).setHeight(2); Rectangle r = rb.build(); java对于final成员的要求是最晚构造函数得初始化,否则编译报错。这在有些时候不太好用。我们可能希望某个字段在第一次设置后就可以保持不变了。kotlin有个lazyinit的保留字实现了这个特性。 此外,上面这一坨代码就是Builder模式的正规写法,非常的繁琐。好在有Lombok的@Builder帮忙自动生成,不需要手写。 此外,也许你并不在意对象一直处于valid状态,只要在真正使用成员干活之前确保valid就行。那么就直接在干活前加判断是否valid就好。

public class Rectangle { private int width; private int height; // ... public draw() { if (width <= 0 || height <= 0 || width * height > 100) { throw new RuntimeException("this rectangle is not valid!"); } // do the real drawing work } } 如果这样做都不够灵活,你甚至都可以做一个public isValid()的方法让外界在调用关键动作之前,手动先验证Object的状态是valid。

还有一大类Object其实是“Data Object”,即用来做数据结构的。比如函数间传递一些参数,从接口或者数据库读出来的数据要有个存的地方等。这类Object压根就没什么“valid状态”一说,或者说,其是否合法完全是看业务场景的上下文,难以仅通过Object里数据本身就能判定的。对于这类object,直接用java bean规范new一个出来,然后挨个set就好。或者,按照我的想法,setter都是多余的,全部public成员直接赋值就足够了。

注意区分Data Object和面向对象里的那个Object,它们本质上是不同的东西 总结一下,如果你用Java,并且:

你的类里的成员很多

你希望维持object自始至终处于valid状态/不可变

那么你需要一个builder。至于是不是内部类我觉得都可以。也许内部类会让人觉得“XXX.Builder属于XXX“,感觉上好些。

但是做了Builder后,还要做些额外工作告诉类的使用者“你应该用builder来创建object,而不是直接new“,这需要一些沟通、文档之类的工作量。

反之,如果:

你的object字段数量很少,构造函数够用了

你压根就不在意object始终处于valid状态,或者你有别的规范来约束object是不是能用来干活

你的object是“data object”

那八成就不太需要做个builder。题主的写法也许已经足够好了。

对于一些语言,如kotlin,javascript,scala,python等,因为他们的语法本身就能支持builder的功能,基本上也就不需要手工实现builder了。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值