Java设计模式实战-Builder

来源:互联网 发布:ip网络广播控制中心 编辑:程序博客网 时间:2024/05/22 13:18

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

原创粉丝点击