设计模式-第四课-建造者模式

        建造者模式是一种创建型设计模式,它通过将一个复杂的对象的构造过程拆分成多个简单的步骤,以及定义一个统一的构造过程,来创建一个完整的对象。在实际开发中,建造者模式经常用于创建复杂的对象,尤其是那些拥有大量属性的对象。

        下面我们通过 Java 代码来展示一下建造者模式的实现。我们以创建一个电脑对象为例,这个电脑对象有很多属性,包括 CPU、内存、硬盘、显卡、显示器等。

首先,我们定义一个电脑对象的基本属性:

public class Computer {
    private String cpu;
    private String memory;
    private String hardDisk;
    private String graphicsCard;
    private String monitor;
    
    public Computer(String cpu, String memory, String hardDisk, String graphicsCard, String monitor) {
        this.cpu = cpu;
        this.memory = memory;
        this.hardDisk = hardDisk;
        this.graphicsCard = graphicsCard;
        this.monitor = monitor;
    }
    
    // getters and setters
}

然后,我们定义一个电脑建造者,用来构建电脑对象:

public class ComputerBuilder {
    private String cpu;
    private String memory;
    private String hardDisk;
    private String graphicsCard;
    private String monitor;
    
    public ComputerBuilder setCpu(String cpu) {
        this.cpu = cpu;
        return this;
    }
    
    public ComputerBuilder setMemory(String memory) {
        this.memory = memory;
        return this;
    }
    
    public ComputerBuilder setHardDisk(String hardDisk) {
        this.hardDisk = hardDisk;
        return this;
    }
    
    public ComputerBuilder setGraphicsCard(String graphicsCard) {
        this.graphicsCard = graphicsCard;
        return this;
    }
    
    public ComputerBuilder setMonitor(String monitor) {
        this.monitor = monitor;
        return this;
    }
    
    public Computer build() {
        return new Computer(cpu, memory, hardDisk, graphicsCard, monitor);
    }
}

        在建造者中,我们为每个属性提供了一个 set 方法,并且返回值是建造者本身,这样可以支持链式调用。在 build 方法中,我们根据属性构造一个电脑对象。

        最后,我们使用建造者来构造一个电脑对象:

Computer computer = new ComputerBuilder()
 .setCpu("Intel Core i7")
 .setMemory("16GB")
 .setHardDisk("1TB SSD")
 .setGraphicsCard("NVIDIA GeForce RTX 3080")
 .setMonitor("27 inch 4K")
 .build();

        在实际开发中,建造者模式可以用于创建一些比较复杂的对象,例如数据库连接池、配置对象等。它可以将创建过程抽象出来,并且将创建过程的实现和使用分离开来,从而提高了代码的可读性和可维护性。

        Spring在构建UriComponents的内容时,就用到了建造者模式:

public abstract class UriComponents implements Serializable {
 
    private static final String DEFAULT_ENCODING = "UTF-8";
 
    // 用于分割uri的正则表达式,下面会说到
    private static final Pattern NAMES_PATTERN = Pattern.compile("\\{([^/]+?)\\}");
 
 
    private final String scheme;
 
    private final String fragment;
 
 
    protected UriComponents(String scheme, String fragment) {
        this.scheme = scheme;
        this.fragment = fragment;
    }
 
 
    // 多个Components对应的getter方法
 
    /**
     * 返回URL的scheme.
     */
    public final String getScheme() {
        return this.scheme;
    }
 
    /**
     * 返回URL的fragment.
     */
    public final String getFragment() {
        return this.fragment;
    }
 
    /**
     * 返回URL的schemeSpecificPar
     */
    public abstract String getSchemeSpecificPart();
 
    /**
     * 返回userInfo
     */
    public abstract String getUserInfo();
 
    /**
     * 返回URL的host
     */
    public abstract String getHost();
 
    /**
     * 返回URL的port
     */
    public abstract int getPort();
 
    /**
     * 返回URL的path
     */
    public abstract String getPath();
 
    /**
     * 返回URL的path部分的集合
     */
    public abstract List<String> getPathSegments();
 
    /**
     * 返回URL的query部分
     */
    public abstract String getQuery();
 
    /**
     * 返回URL的query参数map
     */
    public abstract MultiValueMap<String, String> getQueryParams();
 
 
    /**
     * 将URL的components用特定的编码规则编码并返回,默认为utf-8
     */
    public final UriComponents encode() {
        try {
            return encode(DEFAULT_ENCODING);
        }
        catch (UnsupportedEncodingException ex) {
            // should not occur
            throw new IllegalStateException(ex);
        }
    }
 
    /**
     * 编码的抽象方法,传入相应的编码规则
     */
    public abstract UriComponents encode(String encoding) throws UnsupportedEncodingException;
 
    /**
     * 将URL中的模板参数换成对应的值
     */
    public final UriComponents expand(Map<String, ?> uriVariables) {
        Assert.notNull(uriVariables, "'uriVariables' must not be null");
        return expandInternal(new MapTemplateVariables(uriVariables));
    }
 
    /**
     * 将URL中的模板参数换成对应的值,输入为数组
     */
    public final UriComponents expand(Object... uriVariableValues) {
        Assert.notNull(uriVariableValues, "'uriVariableValues' must not be null");
        return expandInternal(new VarArgsTemplateVariables(uriVariableValues));
    }
 
    /**
     * 将URL中的模板参数换成对应的值,输入为UriTemplateVariables
     */
    public final UriComponents expand(UriTemplateVariables uriVariables) {
        Assert.notNull(uriVariables, "'uriVariables' must not be null");
        return expandInternal(uriVariables);
    }
 
    /**
     * 将URL中的模板参数换成对应的值的最终的实现方法
     */
    abstract UriComponents expandInternal(UriTemplateVariables uriVariables);
 
    /**
     * 处理URL
     */
    public abstract UriComponents normalize();
 
    /**
     * 返回URL的string
     */
    public abstract String toUriString();
 
    /**
     * 返回URI格式的方法
     */
    public abstract URI toUri();
 
    @Override
    public final String toString() {
        return toUriString();
    }
 
    /**
     * 将这些Components的值赋给其builder类
     */
    protected abstract void copyToUriComponentsBuilder(UriComponentsBuilder builder);

        这些方法大多对应url的某一部分,这些能帮助Spring对请求的内容进行识别。

        究竟是如何让请求的uri生成相应的UriComponents类呢?就要看看UriComponentsBuilder这个类了。

/**
 * 默认构造方法,其中path的构造类为CompositePathComponentBuilder,它为UriComponentsBuilder的内部静态类,主要实现对url的path部分进行构造。
 */
protected UriComponentsBuilder() {
    this.pathBuilder = new CompositePathComponentBuilder();
}
 
/**
 * 创建一个传入UriComponentsBuilder类的深拷贝对象
 */
protected UriComponentsBuilder(UriComponentsBuilder other) {
    this.scheme = other.scheme;
    this.ssp = other.ssp;
    this.userInfo = other.userInfo;
    this.host = other.host;
    this.port = other.port;
    this.pathBuilder = other.pathBuilder.cloneBuilder();
    this.queryParams.putAll(other.queryParams);
    this.fragment = other.fragment;
}

        这里面的参数又是怎么来的?在UriComponentsBuilder中,有不少的方法来支撑这个:

// 如果传入String就用正则然后一个个获取
 public static UriComponentsBuilder fromUriString(String uri) {
        Assert.notNull(uri, "URI must not be null");
        Matcher matcher = URI_PATTERN.matcher(uri);
        if (matcher.matches()) {
            UriComponentsBuilder builder = new UriComponentsBuilder();
            String scheme = matcher.group(2);
            String userInfo = matcher.group(5);
            String host = matcher.group(6);
            String port = matcher.group(8);
            String path = matcher.group(9);
            String query = matcher.group(11);
            String fragment = matcher.group(13);
            boolean opaque = false;
            String ssp;
            if (StringUtils.hasLength(scheme)) {
                ssp = uri.substring(scheme.length());
                if (!ssp.startsWith(":/")) {
                    opaque = true;
                }
            }

            builder.scheme(scheme);
            if (opaque) {
                ssp = uri.substring(scheme.length()).substring(1);
                if (StringUtils.hasLength(fragment)) {
                    ssp = ssp.substring(0, ssp.length() - (fragment.length() + 1));
                }

                builder.schemeSpecificPart(ssp);
            } else {
                builder.userInfo(userInfo);
                builder.host(host);
                if (StringUtils.hasLength(port)) {
                    builder.port(port);
                }

                builder.path(path);
                builder.query(query);
            }

            if (StringUtils.hasText(fragment)) {
                builder.fragment(fragment);
            }

            return builder;
        } else {
            throw new IllegalArgumentException("[" + uri + "] is not a valid URI");
        }
    }

// 用httpurl的String,类似上面的,但是正则表达式不同,而且会有写特殊的处理
    public static UriComponentsBuilder fromHttpUrl(String httpUrl) {
        Assert.notNull(httpUrl, "HTTP URL must not be null");
        Matcher matcher = HTTP_URL_PATTERN.matcher(httpUrl);
        if (matcher.matches()) {
            UriComponentsBuilder builder = new UriComponentsBuilder();
            String scheme = matcher.group(1);
            builder.scheme(scheme != null ? scheme.toLowerCase() : null);
            builder.userInfo(matcher.group(4));
            String host = matcher.group(5);
            if (StringUtils.hasLength(scheme) && !StringUtils.hasLength(host)) {
                throw new IllegalArgumentException("[" + httpUrl + "] is not a valid HTTP URL");
            } else {
                builder.host(host);
                String port = matcher.group(7);
                if (StringUtils.hasLength(port)) {
                    builder.port(port);
                }

                builder.path(matcher.group(8));
                builder.query(matcher.group(10));
                return builder;
            }
        } else {
            throw new IllegalArgumentException("[" + httpUrl + "] is not a valid HTTP URL");
        }
    }

    public static UriComponentsBuilder fromHttpRequest(HttpRequest request) {
        return fromUri(request.getURI()).adaptFromForwardedHeaders(request.getHeaders());
    }

        从springMVC通过UriComponentsBuilder构建UriComponents类的整个源码与流程中,我们可以窥见建造者模式在其中发挥的巨大作用。
        它通过builder类,提供了多种UriComponents的初始化方式,并能根据不同情况,返回不同的UriComponents子类。充分的将UriComponents类本身与它的构造过程解耦合。
        除了这个UriComponentsBuilder之外,还有很多,比如创建新的bean的BeanDefinitionBuilder,还有Java中的StringBuilder,还有Mybatis中的SqlsessionFactoryBuilder等等等等。

        试想一下,如果不使用建造者模式,而是将大量的初始化方法直接类或其子类中,它的代码将变得非常庞大和冗余。而建造者模式可以帮助我们很好的解决这一问题。所以,如果我们在写代码时,某个复杂的类有多种初始化形式或者初始化过程及其繁琐,并且还对应多个复杂的子类(总之就是构造起来很麻烦),我们就可以用建造者模式,将该类和该类的构造过程解耦。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

心若自由何处是束缚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值