Java设计模式实战-Builder

本文介绍了Builder模式的使用场景、方式及原理分析,通过实例演示如何利用Builder模式创建复杂对象,如RpcRequest实例创建、netty中ServerBootstrap配置及storm中Topology构建。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.使用场景

Builder模式主要用于复杂对象(构造方法的参数过多)的创建、对象构造参数可选配置、创建不可变对象。实际开发多用于开源框架中配置对象的创建。详见 3.范例演示

下面首先介绍为啥要用Builder模式创建对象?

>>使用共有构造方法的问题:

  1. 不能灵活控制可选构造参数,使用包含所有参数的构造方法,不需要的参数也必须传,同时容易出现参数错位,不很容易发现。(构造方法重载可解决)
  2. 通过重载的构造方法可定制不同参数的构造方法,但可读性差,因为构造方法名完全相同,缺乏语义性,很难区别,只能根据参数列表选择。(使用JavaBean方式可解决)

>>使用JavaBean设置参数的问题:

  1. 通过set方法可灵活设置参数,可读性不错,但参数设置分为几次调用,不能保证对象状态的一致性,需要人为处理额外努力保证线程安全。
  2. public set方法阻止把类做成不可变类的可能。我们可能希望一个对象创建后不能被修改

>>Builder模式创建对象好处?

  1. 拥有JavaBean模式的可读性,可选设置对象参数,可以实现链式调用,代码简洁
  2. 拥有重载构造方法创建对象的安全性
  3. 构造参数校验和对象创建分离,创建对象前可完成参数校验

2.使用方式

  1. 不直接生成想要的对象,通过必要参数调用构造器,返回一个构造器对象Builder(此构造器对象拥有和目标对象的相同的成员变量)
  2. 通过构造器的不同参数的set方法,设置参数给构造器
  3. 最后通过Builder.build()方法,返回目标对象的实例,完成对象创建。
    //设置远程过程调用Rpc请求的参数
    RpcRequest request = new RpcRequest.Build()
        .interfaceName(method.getDeclaringClass().getName())
                .serviceVersion("0.0.1")
                .methodName(method.getName())
                .parameterTypes(method.getParameterTypes())
                .parameters(args).build();

3.范例演示

3.1 RpcRequest

场景描述:RpcRequest对象封装rpc(远程过程调用)的调用请求信息,包含远程服务的接口名、服务版本号、请求调用方法、请求参数类型、请求参数信息。RpcRequest对象的构造参数较多,包含一些可选参数,比如请求超时timeout信息,另外未来可能增加其他请求参数。同时请求对象一旦创建,对象信息不希望被修改。因此通过Builder构建者模式,为其创建对象实例。

package com.myron.netty.rpc;
import java.io.Serializable;
import java.util.UUID;

/**
 * 封装 RPC 请求
 * @author lin.r.x
 *
 */
public class RpcRequest implements Serializable {

    private static final long serialVersionUID = 1L;
    private String requestId;
    private String interfaceName;
    private String serviceVersion;
    private String methodName;
    private Class<?>[] parameterTypes;
    private Object[] parameters;

    public RpcRequest(Builder build) {
        this.requestId = build.requestId;
        this.interfaceName = build.interfaceName;
        this.serviceVersion = build.serviceVersion;
        this.methodName = build.methodName;
        this.parameterTypes = build.parameterTypes;
        this.parameters = build.parameters;
    }

    // 目标对象只有get方法无set方法,保证对象创建后,私有成员变量不被修改
    public String getRequestId() {
        return requestId;
    }

    public String getInterfaceName() {
        return interfaceName;
    }

    public String getServiceVersion() {
        return serviceVersion;
    }

    public String getMethodName() {
        return methodName;
    }

    public Class<?>[] getParameterTypes() {
        return parameterTypes;
    }

    public Object[] getParameters() {
        return parameters;
    }

    //RpcRequest解决Request参数过多,未来会添加新的参数,引入Build模式
    public static class Builder {
        private String requestId;
        private String interfaceName;
        private String serviceVersion;
        private String methodName;
        private Class<?>[] parameterTypes;
        private Object[] parameters;

        // 默认Build构造方法,用于初始化默认参数
        public Builder() {
            this.requestId = UUID.randomUUID().toString().replace("-", "");
        }

        // 带参数的Build构造方法,用于初始化必选参数
        public Builder(String requestId) {
            this.requestId = requestId;
        }

        // 传递构造器对象给目标类的构造方法,返回目标对象实例
        public RpcRequest build() {
            RpcRequest request = new RpcRequest(this);
            return request;
        }

        // 设置参数,并返回构造器本身,用于链式调用设置可选参数 
        public Builder interfaceName(String interfaceName) {
            this.interfaceName = interfaceName;
            return this;
        }

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

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

        public Builder parameterTypes(Class<?>[] parameterTypes) {
            this.parameterTypes = parameterTypes;
            return this;
        }

        public Builder parameters(Object[] parameters) {
            this.parameters = parameters;
            return this;
        }


    }
}

使用Builder创建RpcRequest实例

//设置远程过程调用Rpc请求的参数
    RpcRequest request = new RpcRequest.Build()
        .interfaceName(method.getDeclaringClass().getName())
                .serviceVersion("0.0.1")
                .methodName(method.getName())
                .parameterTypes(method.getParameterTypes())
                .parameters(args).build();

说明:以上对象创建的构造器,是通过类的静态内部类来实现,将构造器封装在类内部,这种方式使用Builder,使得代码冗长,在实际开发中也有将构造器独立于外部类,下面通过开源框架netty和strom,两个例子了解Builder设计模式运用。

3.2 netty中运用场景

netty 是提供nio的网络通信框架,这里主要关注Netty服务端启动需要先创建服务器启动辅助类ServerBootstrap,它提供了一系列的方法用于设置服务器端启动相关的参数。然而在创建ServerBootstrap实例时,发现ServerBootstrap只有一个无参的构造函数,事实上它需要与多个其它组件或者类交互。ServerBootstrap构造函数没有参数的原因是因为它的参数太多了,Netty创造者为了解决这个问题,就引入了Builder模式。

package com.myron.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class TimeServer {
    public void bind(int port) {
        //配置服务端的NIO线程组
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        ServerBootstrap b = new ServerBootstrap();
        b.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 1024)
                //绑定I/O事件的处理类主要用于处理网络I/O事件,例如记录日志、对消息进行编解码
                .childHandler(new ChildChannelHandler());
        try {
            //绑定端口,同步等待成功
            ChannelFuture f = b.bind(port).sync();
            //等待服务端监听端口关闭
            f.channel().closeFuture().sync();//方法阻塞等待服务端链路关闭之后main函数才退出
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {

        @Override
        protected void initChannel(SocketChannel channel) throws Exception {
            channel.pipeline().addLast(new TimeServerHandler());

        }

    }

    public static void main(String[] args) {
        int port = 8080;
        if (args != null && args.length > 0) {
            try {
                port = Integer.parseInt(args[0]);
            } catch (NumberFormatException e) {
                // 使用默认值
            }
        }
        new TimeServer().bind(port);
    }

}
3.3 storm中运用场景

storm是一个流式实时计算的框架,主要用于大数据领域的实时计算。storm中有个重要的对象,Topology拓扑对象,用于描述实时计算任务中数据流的拓扑节点结构。计算拓扑 根据具体业务需要创建,因此非常适合Builder模式构建对象

演示计算wordcount的拓扑对象创建代码

package com.myron.storm.wordcount.topology;

import org.apache.storm.Config;
import org.apache.storm.LocalCluster;
import org.apache.storm.topology.TopologyBuilder;
import org.apache.storm.tuple.Fields;

import com.myron.storm.wordcount.bolt.ReportBolt;
import com.myron.storm.wordcount.bolt.SplitSentenceBolt;
import com.myron.storm.wordcount.bolt.WordCountBolt;
import com.myron.storm.wordcount.spout.SentenceSpout;

/**
 * Topology定义计算所需要的spout和bolt
 * 
 * @author Administrator
 *
 */
public class WordCountTopology {
    private static final String SENTENCE_SPOUT_ID = "sentence-spout";
    private static final String SPLIT_BOLT_ID = "split-bolt";
    private static final String COUNT_BOLT_ID = "count-bolt";
    private static final String REPORT_BOLT_ID = "report-bolt";
    private static final String TOPOLOGY_NAME = "topology-bolt";

    public static void main(String[] args) throws Exception {
        //实例化spout和bolt
        SentenceSpout spout = new SentenceSpout();
        SplitSentenceBolt splitBolt = new SplitSentenceBolt();
        WordCountBolt countBolt = new WordCountBolt();
        ReportBolt reportBolt = new ReportBolt();

        //TopologyBuilder提供流式接口风格API定义topology组件之间的数据流
        TopologyBuilder builder = new TopologyBuilder();
        builder.setSpout(SENTENCE_SPOUT_ID, spout);

        // SentenceSpout --> SplitSentenceBolt
        // BoltDeclarer.shuffleGrouping():告诉Storm,sentenceSpout 发射的tuple随机均匀分发给SplitBolt
        builder.setBolt(SPLIT_BOLT_ID, splitBolt).shuffleGrouping(SENTENCE_SPOUT_ID);

        // SplitSentenceBolt --> WordCountBolt
        // BoltDeclarer.fieldsGrouping():告诉Storm,所有"word"字段值的tuple被路由到同一个WordCountBolt
        builder.setBolt(COUNT_BOLT_ID, countBolt).fieldsGrouping(SPLIT_BOLT_ID, new Fields("word"));

        // WordCountBolt --> ReportBolt
        // BoltDeclarer.globalGrouping():告诉Storm,所有的WordCountBolt 路由到唯一ReportBolt任务中
        builder.setBolt(REPORT_BOLT_ID, reportBolt).globalGrouping(COUNT_BOLT_ID);

        // Config对象代表了对topology所有组件全局生效的配置参数集合
        Config config = new Config();

        //Storm本地模式模拟一个完整的集群
        LocalCluster cluster = new LocalCluster();

        //提交拓扑到集群
        cluster.submitTopology(TOPOLOGY_NAME, config, builder.createTopology());
        Thread.sleep(50000);
        cluster.killTopology(TOPOLOGY_NAME);
        cluster.shutdown();
    }

}

4.原理分析

Builder模式构建复杂对象,同时拥有的公共构造方法和JavaBean方式(成员变量setter方法)优点,其实不难发现,Builder模式本质其实只是把上面两种方式组合起来,通过Builder对象的setter方法可选接受参数,调用Builder.build()方法时,将Builder对象接收到客户端参数,一次性调用目标类的带有全部参数的构造方法,并返回创建对象。这种通过第三方辅助类来协助处理,在其他设计模式也很常见。

最后想说,Builder模式增加Builder对象创建的开销,也增加代码量,因此在创建简单对象时,没必要使用。深刻理解每个设计模式针对的场景,比硬记重要。另外了解在开源产品中使用,可以大大加深理解,看看大咖是怎么用的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值