构建者模式:用组装电脑的故事理解复杂对象构建

43 阅读10分钟

一、故事解说:组装电脑的流水线

想象你去电脑城组装电脑,流程如下:

  1. 告诉店员需求:你说 “我要一台游戏电脑,显卡要 RTX4080,内存 32G”,店员不会直接扔给你一堆零件,而是按步骤组装。

  2. 流水线分工

    • Builder( builder 类) :像不同的工人,负责装主板、插内存、装显卡等具体步骤;
    • Director(指挥者) :像流水线班长,按顺序调用工人的动作(先装主板,再插内存,最后装显卡);
    • Product(产品) :最终组装好的电脑。
  3. 灵活定制:不同需求(游戏电脑、办公电脑)只需换 Builder,流水线(Director)流程不变。

二、构建者模式核心角色

  1. Builder:定义构建产品各部分的方法(如buildCPU()buildMemory());
  2. Director:定义构建顺序(如先装 CPU,再装内存);
  3. Product:最终复杂对象(如电脑)。

三、Android 常用构建者案例与实现

案例 1:AlertDialog.Builder(最经典的 Android 构建者)

java

// Product:AlertDialog
public class AlertDialog {
    private Context context;
    private String title;
    private String message;
    private String positiveButtonText;
    private DialogInterface.OnClickListener positiveButtonClickListener;
    // 省略其他属性(图标、Negative按钮等)
    
    // 构造函数私有,只能通过Builder创建
    private AlertDialog(Builder builder) {
        this.context = builder.context;
        this.title = builder.title;
        this.message = builder.message;
        this.positiveButtonText = builder.positiveButtonText;
        this.positiveButtonClickListener = builder.positiveButtonClickListener;
    }
    
    // Builder类:构建AlertDialog的各个部分
    public static class Builder {
        private Context context;
        private String title;
        private String message;
        private String positiveButtonText;
        private DialogInterface.OnClickListener positiveButtonClickListener;
        
        public Builder(Context context) {
            this.context = context;
        }
        
        // 构建各部分的方法(链式调用)
        public Builder setTitle(String title) {
            this.title = title;
            return this; // 返回自身,支持链式调用
        }
        
        public Builder setMessage(String message) {
            this.message = message;
            return this;
        }
        
        public Builder setPositiveButton(String text, DialogInterface.OnClickListener listener) {
            this.positiveButtonText = text;
            this.positiveButtonClickListener = listener;
            return this;
        }
        
        // 最后一步:创建Product
        public AlertDialog create() {
            return new AlertDialog(this);
        }
        
        // 直接显示对话框(简化版Director)
        public AlertDialog show() {
            AlertDialog dialog = create();
            dialog.show();
            return dialog;
        }
    }
}

// 使用方式
new AlertDialog.Builder(this)
    .setTitle("提示")
    .setMessage("确定要退出吗?")
    .setPositiveButton("确定", (dialog, which) -> finish())
    .setNegativeButton("取消", null)
    .show();

优点

  • 链式调用直观:代码像自然语言(“设置标题→设置消息→设置按钮”);

  • 必填参数明确:Builder 构造函数强制传入 Context,避免空指针;

  • 扩展性好:新增属性只需在 Builder 中添加方法,不影响原有逻辑。

缺点

  • 类数量增加:每个 Product 对应一个 Builder,项目庞大时类数翻倍;
  • 构建过程不可见:Director 逻辑嵌入在 Builder 的show()中,复杂流程难定制。

案例 2:Notification.Builder(Android 通知构建者)

java

// Product:Notification
public class Notification {
    private Context context;
    private int smallIcon;
    private CharSequence title;
    private CharSequence text;
    private PendingIntent contentIntent;
    // 省略其他属性(大图标、进度条等)
    
    private Notification(Builder builder) {
        this.context = builder.context;
        this.smallIcon = builder.smallIcon;
        this.title = builder.title;
        this.text = builder.text;
        this.contentIntent = builder.contentIntent;
    }
    
    // Builder类
    public static class Builder {
        private Context context;
        private int smallIcon;
        private CharSequence title;
        private CharSequence text;
        private PendingIntent contentIntent;
        
        public Builder(Context context, String channelId) {
            this.context = context;
            // Android 8.0+必须指定通知渠道
            NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                NotificationChannel channel = new NotificationChannel(
                    channelId, "渠道名称", NotificationManager.IMPORTANCE_DEFAULT);
                manager.createNotificationChannel(channel);
            }
        }
        
        public Builder setSmallIcon(int icon) {
            this.smallIcon = icon;
            return this;
        }
        
        public Builder setContentTitle(CharSequence title) {
            this.title = title;
            return this;
        }
        
        public Builder setContentText(CharSequence text) {
            this.text = text;
            return this;
        }
        
        public Builder setContentIntent(PendingIntent intent) {
            this.contentIntent = intent;
            return this;
        }
        
        public Notification build() {
            return new Notification(this);
        }
    }
}

// 使用方式
Notification notification = new Notification.Builder(this, "channel_id")
    .setSmallIcon(R.drawable.ic_notify)
    .setContentTitle("新消息")
    .setContentText("您有10条未读消息")
    .setContentIntent(clickIntent)
    .build();
notificationManager.notify(1, notification);

优点

  • 分步骤构建复杂对象:通知包含图标、文字、点击事件等 10 + 属性,构建者模式避免参数混乱;

  • 默认值合理:未设置的属性(如大图标)使用系统默认,降低使用门槛;

  • 版本兼容:Builder 内部处理 Android 8.0 + 的通知渠道适配,调用方无感知。

缺点

  • 参数校验延迟:直到build()才发现必填参数缺失(如未设置 smallIcon 会报错);
  • 构建过程耦合:Builder 内部包含版本兼容逻辑,违反 “单一职责原则”。

案例 3:OkHttpClient.Builder(网络请求客户端构建者)

java

// Product:OkHttpClient
public class OkHttpClient {
    private final Dispatcher dispatcher;
    private final List<Interceptor> interceptors;
    private final List<Interceptor> networkInterceptors;
    private final Proxy proxy;
    // 省略其他属性(连接超时、读写超时等)
    
    private OkHttpClient(Builder builder) {
        this.dispatcher = builder.dispatcher;
        this.interceptors = builder.interceptors;
        this.networkInterceptors = builder.networkInterceptors;
        this.proxy = builder.proxy;
    }
    
    // Builder类
    public static class Builder {
        private Dispatcher dispatcher;
        private final List<Interceptor> interceptors = new ArrayList<>();
        private final List<Interceptor> networkInterceptors = new ArrayList<>();
        private Proxy proxy = Proxy.NO_PROXY;
        private int connectTimeout = 10; // 默认10秒
        private int readTimeout = 10;
        private int writeTimeout = 10;
        
        public Builder() {
            this.dispatcher = new Dispatcher();
        }
        
        public Builder connectTimeout(int timeout, TimeUnit unit) {
            this.connectTimeout = (int) unit.toSeconds(timeout);
            return this;
        }
        
        public Builder addInterceptor(Interceptor interceptor) {
            interceptors.add(interceptor);
            return this;
        }
        
        public Builder addNetworkInterceptor(Interceptor interceptor) {
            networkInterceptors.add(interceptor);
            return this;
        }
        
        public Builder proxy(Proxy proxy) {
            this.proxy = proxy;
            return this;
        }
        
        public OkHttpClient build() {
            // 校验参数
            if (dispatcher == null) {
                throw new IllegalStateException("dispatcher required");
            }
            return new OkHttpClient(this);
        }
    }
}

// 使用方式
OkHttpClient client = new OkHttpClient.Builder()
    .connectTimeout(15, TimeUnit.SECONDS) // 连接超时15秒
    .readTimeout(20, TimeUnit.SECONDS)    // 读取超时20秒
    .addInterceptor(new LoggingInterceptor()) // 添加日志拦截器
    .addNetworkInterceptor(new CacheInterceptor()) // 添加网络缓存拦截器
    .build();

优点

  • 灵活扩展:通过addInterceptor可无限添加自定义功能(日志、缓存、token 拦截等);

  • 默认值合理:提供常用默认参数(10 秒超时),简单场景无需配置;

  • 线程安全:Builder 构建过程是线程安全的,最终 OkHttpClient 实例也是不可变对象。

缺点

  • 参数关联性弱:部分参数(如超时时间)之间的关联性需要调用方自己保证;
  • 构建成本高:每个 OkHttpClient 实例包含独立的拦截器列表,频繁创建会消耗内存。

四、构建者模式的适用场景与总结

适用场景

  1. 对象构建复杂:如 AlertDialog 包含 10 + 属性,参数组合多;
  2. 需要多配置版本:如通知可配置为普通通知、进度通知、悬挂通知;
  3. 构建过程需分步骤:如 OkHttpClient 需先配置超时,再添加拦截器。

核心优点

  • 解耦构建与表示:Director(构建流程)和 Product(最终对象)分离,方便修改流程;
  • 链式调用易读:代码如 “设置 A→设置 B→设置 C”,比参数列表更清晰;
  • 强制分步构建:避免 “半初始化” 对象(必须按步骤完成所有必要配置)。

核心缺点

  • 类数量翻倍:每个 Product 对应一个 Builder,小型项目可能 “小题大做”;
  • 构建过程隐藏:复杂流程的优化(如并行构建)难以实现,因为流程嵌入在 Builder 中。

Android 中的最佳实践

  • 优先使用系统 Builder:如 AlertDialog.Builder、Notification.Builder,避免重复造轮子;

  • 自定义 Builder 时

    1. 在 Builder 构造函数中强制传入必填参数;
    2. build()方法中做参数校验;
    3. 使用不可变对象(Product 属性用 final 修饰),避免构建后被修改。### 一、构建者模式:用组装汽车的故事理解
      想象你要定制一辆汽车,有很多可选配置:
  • 发动机:燃油、电动、混动?

  • 颜色:红色、蓝色、黑色?

  • 内饰:真皮、织物、仿皮?

  • 附加功能:自动驾驶、座椅加热、全景天窗?

如果直接去汽车工厂(传统构造函数),可能需要这样的代码:

java

Car car = new Car("燃油发动机", "红色", "真皮", true, false, true); 
// 参数太多,顺序容易记混,还得为不需要的功能传null

构建者模式就像找一个 “汽车定制顾问”:

java

Car car = new CarBuilder()
    .setEngine("电动")
    .setColor("蓝色")
    .setInterior("仿皮")
    .addAutopilot()
    .build(); // 按需配置,最后一键生成

核心优点

  1. 链式调用:代码更易读,参数顺序不敏感;
  2. 可选配置:不需要的参数可以不设置,避免传 null;
  3. 不可变对象:构建完成后对象状态不可修改(通过 private final 字段)。

二、Android 常用构建者案例实现

1. 自定义 Dialog(类似 AlertDialog)

java

// 产品类:Dialog
public class CustomDialog {
    private final Context context;
    private final String title;
    private final String message;
    private final String positiveText;
    private final String negativeText;
    private final OnClickListener positiveListener;
    private final OnClickListener negativeListener;
    private final boolean cancelable;

    private CustomDialog(Builder builder) {
        this.context = builder.context;
        this.title = builder.title;
        this.message = builder.message;
        this.positiveText = builder.positiveText;
        this.negativeText = builder.negativeText;
        this.positiveListener = builder.positiveListener;
        this.negativeListener = builder.negativeListener;
        this.cancelable = builder.cancelable;
    }

    // 显示对话框
    public void show() {
        // 实际实现:创建并显示Dialog
        Log.d("CustomDialog", "Showing dialog with title: " + title);
    }

    // 构建者类
    public static class Builder {
        private final Context context;
        private String title;
        private String message;
        private String positiveText = "确定";
        private String negativeText = "取消";
        private OnClickListener positiveListener;
        private OnClickListener negativeListener;
        private boolean cancelable = true;

        public Builder(Context context) {
            this.context = context;
        }

        public Builder setTitle(String title) {
            this.title = title;
            return this;
        }

        public Builder setMessage(String message) {
            this.message = message;
            return this;
        }

        public Builder setPositiveButton(String text, OnClickListener listener) {
            this.positiveText = text;
            this.positiveListener = listener;
            return this;
        }

        public Builder setNegativeButton(String text, OnClickListener listener) {
            this.negativeText = text;
            this.negativeListener = listener;
            return this;
        }

        public Builder setCancelable(boolean cancelable) {
            this.cancelable = cancelable;
            return this;
        }

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

// 使用示例
CustomDialog dialog = new CustomDialog.Builder(context)
    .setTitle("提示")
    .setMessage("确定要删除这条数据吗?")
    .setPositiveButton("确认", v -> { /* 处理确认逻辑 */ })
    .setNegativeButton("取消", null)
    .setCancelable(false)
    .build();
dialog.show();

优点

  • 可选参数灵活(如不需要标题可以不设置);

  • 链式调用更直观;

  • 避免创建过多构造函数重载。

缺点

  • 代码量增加(需要额外的 Builder 类);
  • 只能用于需要多参数配置的场景。

2. 网络请求参数构建(类似 OkHttp 的 Request.Builder)

java

// 产品类:HttpRequest
public class HttpRequest {
    private final String url;
    private final String method;
    private final Map<String, String> headers;
    private final Map<String, String> params;
    private final String body;
    private final int timeout;

    private HttpRequest(Builder builder) {
        this.url = builder.url;
        this.method = builder.method;
        this.headers = builder.headers;
        this.params = builder.params;
        this.body = builder.body;
        this.timeout = builder.timeout;
    }

    // 发送请求
    public void execute() {
        // 实际实现:发送网络请求
        Log.d("HttpRequest", "Executing " + method + " request to " + url);
    }

    // 构建者类
    public static class Builder {
        private final String url;
        private String method = "GET";
        private Map<String, String> headers = new HashMap<>();
        private Map<String, String> params = new HashMap<>();
        private String body;
        private int timeout = 30000; // 默认30秒超时

        public Builder(String url) {
            this.url = url;
        }

        public Builder setMethod(String method) {
            this.method = method;
            return this;
        }

        public Builder addHeader(String key, String value) {
            this.headers.put(key, value);
            return this;
        }

        public Builder addParam(String key, String value) {
            this.params.put(key, value);
            return this;
        }

        public Builder setBody(String body) {
            this.body = body;
            return this;
        }

        public Builder setTimeout(int timeout) {
            this.timeout = timeout;
            return this;
        }

        public HttpRequest build() {
            // 可以添加参数校验逻辑
            if (!Arrays.asList("GET", "POST", "PUT", "DELETE").contains(method)) {
                throw new IllegalArgumentException("Invalid HTTP method: " + method);
            }
            return new HttpRequest(this);
        }
    }
}

// 使用示例
HttpRequest request = new HttpRequest.Builder("https://api.example.com/data")
    .setMethod("POST")
    .addHeader("Content-Type", "application/json")
    .addParam("page", "1")
    .addParam("size", "20")
    .setBody("{"key": "value"}")
    .setTimeout(60000)
    .build();
request.execute();

优点

  • 参数配置灵活(如不需要 body 可以不传);

  • 可以添加参数校验(如 build () 中检查 method 合法性);

  • 支持默认值(如 timeout 默认 30 秒)。

缺点

  • 对于简单请求(如只有 URL 的 GET 请求),显得冗余;
  • 构建过程中无法中途修改已设置的参数(如需修改需重新创建 Builder)。

3. 自定义 View 属性配置(类似 View 的 LayoutParams)

java

// 产品类:自定义View
public class CustomView extends View {
    private int textSize;
    private int textColor;
    private String text;
    private Drawable background;
    private int padding;

    public CustomView(Context context) {
        this(context, null);
    }

    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // 解析XML属性
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CustomView);
        textSize = ta.getDimensionPixelSize(R.styleable.CustomView_textSize, 16);
        textColor = ta.getColor(R.styleable.CustomView_textColor, Color.BLACK);
        text = ta.getString(R.styleable.CustomView_text);
        ta.recycle();
    }

    // 使用构建者更新属性
    public void update(Builder builder) {
        if (builder.textSize != -1) this.textSize = builder.textSize;
        if (builder.textColor != 0) this.textColor = builder.textColor;
        if (builder.text != null) this.text = builder.text;
        if (builder.background != null) this.background = builder.background;
        if (builder.padding != -1) this.padding = builder.padding;
        invalidate(); // 刷新视图
    }

    // 构建者类
    public static class Builder {
        private int textSize = -1;
        private int textColor = 0;
        private String text;
        private Drawable background;
        private int padding = -1;

        public Builder setTextSize(int textSize) {
            this.textSize = textSize;
            return this;
        }

        public Builder setTextColor(int textColor) {
            this.textColor = textColor;
            return this;
        }

        public Builder setText(String text) {
            this.text = text;
            return this;
        }

        public Builder setBackground(Drawable background) {
            this.background = background;
            return this;
        }

        public Builder setPadding(int padding) {
            this.padding = padding;
            return this;
        }

        public void applyTo(CustomView view) {
            view.update(this);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 绘制逻辑...
    }
}

// 使用示例
CustomView view = findViewById(R.id.customView);
new CustomView.Builder()
    .setText("Hello Builder")
    .setTextColor(Color.RED)
    .setPadding(16)
    .applyTo(view);

优点

  • 可以选择性更新部分属性(如只更新 text,不影响其他属性);

  • 避免在 View 中定义大量 setter 方法;

  • 可以在运行时动态修改 View 属性。

缺点

  • 需要在 View 内部添加额外的 update () 方法;
  • 不适用于一次性初始化(XML 布局中无法直接使用)。

三、构建者模式优缺点总结

优点缺点
链式调用,代码更易读需要创建额外的 Builder 类,代码量增加
可选参数配置,无需传 null对于简单对象(参数少)显得冗余
支持参数校验(在 build () 中检查)构建过程中无法中途修改已设置的参数
生成不可变对象(通过 private final)不适用于参数经常变化的场景
支持默认值

四、使用场景建议

推荐在以下情况使用构建者模式:

  1. 参数超过 3 个,尤其是包含可选参数时;

  2. 对象需要不可变(构建后状态不能修改);

  3. 参数配置复杂,需要进行校验或依赖关系处理(如 A 参数存在时 B 必须存在);

  4. 同一个对象需要多种配置组合(如 Dialog 可能有标题 + 消息,或只有消息)。

Android 系统中,构建者模式广泛应用于:

  • AlertDialog.Builder

  • Notification.Builder

  • RetrofitRequest.Builder

  • OkHttpRequest.BuilderOkHttpClient.Builder

通过构建者模式,可以让代码更简洁、更安全,同时提升 API 的易用性。