专栏名称: 武哥聊编程
这里有技术,有段子,有生活,也有资源,要不然怎么叫 “私房菜” 呢?
目录
相关文章推荐
武汉大学学生会  ·  图集|愿除旧妄生新意 ·  16 小时前  
武汉大学  ·  本命年!武大也属蛇 ·  3 天前  
51好读  ›  专栏  ›  武哥聊编程

我的强迫症系列之@Builder和建造者模式

武哥聊编程  · 公众号  ·  · 2020-04-15 10:00

正文

前言

备受争议的 Lombok ,有的人喜欢它让代码更整洁,有的人不喜欢它,巴拉巴拉一堆原因。在我看来 Lombok 唯一的缺点可能就是需要安装插件了,但是对于业务开发的项目来说,它的优点远远超过缺点。

我们可以看一下,有多少项目使用了Lombok(数量还在疯涨中...)

尽管如此,我们今天也只是单纯的来看一下@Builder()这个东西

@Builder的使用

使用@Builder修饰类

@Data
@Builder
public class UserDO {

    private Long id;

    private String name;
}

使用建造者模式创建类

@Test
public void test() {
    UserDO userDO = UserDO.builder()
            .id(1L)
            .name("iisheng")
            .build();
    System.out.println(userDO);
}

编译后源码

执行 javac -cp ~/lombok.jar UserDO.java -verbose .java 编译成 .class 文件。

通过IDE查看该 .class 源码

下面展示的是被我处理后的源码,感兴趣的同学,可以自己执行上面命令,查看完整源码

public class UserDO {
    private Long id;
    private String name;

    public String toString() {
        return "UserDO(id=" 
            + this.getId() + ", name=" + this.getName() + ")";
    }

    UserDO(Long var1, String var2) {
        this.id = var1;
        this.name = var2;
    }

    public static UserDO.UserDOBuilder builder() {
        return new UserDO.UserDOBuilder();
    }

    private UserDO() {
    }

    public static class UserDOBuilder {
        private Long id;
        private String name;

        UserDOBuilder() {
        }

        public UserDO.UserDOBuilder id(Long var1) {
            this.id = var1;
            return this;
        }

        public UserDO.UserDOBuilder name(String var1) {
            this.name = var1;
            return this;
        }

        public UserDO build() {
            return new UserDO(this.id, this.name);
        }
    }
}

由此,我们可以看出来Builder的实现步骤:

  • UserDO 中创建静态 UserDOBuilder
  • 编写设置属性方法,返回 UserDOBuilder 对象
  • 编写 build() 方法,返回 UserDO 对象

是不是很简单?我曾经看过不知道哪个大佬说的一句话,整洁的代码不是说,行数更少,字数更少,而是阅读起来逻辑更清晰。所以,我觉得,哪怕我们不用@Builder,也应该多用这种建造者模式。

是时候看看什么是建造者模式了!

建造者模式

UML类图

这是大部分书籍网络中的建造者模式类图

产品类

public class Product {

    private String name;

    private Integer val;

    Product(String name, Integer val) {
        this.name = name;
        this.val = val;
    }

    @Override
    public String toString() {
        return "Product is " + name + " value is " + val;
    }
}

抽象建造者

public




    
 abstract class Builder {

    protected Integer val;

    protected String name;

    // 设置产品不同部分,以获得不同的产品
    public abstract void setVal(Integer val);

    // 设置名字 公用方法
    public void setName(String name) {
        this.name = name;
    }

    // 建造产品
    public abstract Product buildProduct();
}

具体建造者

public class ConcreteBuilder extends Builder {

    @Override
    public void setVal(Integer val) {
        /**
         * 产品类内部的逻辑
         * 实际存储的值是 val + 100
         */

        this.val = val + 100;
    }

    @Override
    // 组建一个产品
    public Product buildProduct() {
        // 这块还可以写特殊的校验逻辑
        return new Product(name, val);
    }
}

导演类

public class Director {

    private Builder builder = new ConcreteBuilder();

    public Product getAProduct() {
        // 设置不同的零件,产生不同的产品
        builder.setName("ProductA");
        builder.setVal(2);
        return builder.buildProduct();
    }
}

我更喜欢这样的建造者模式类图

Product 的创建,也依赖于 Builder 。代码只需要将上面的 Product ConcreteBuilder 调整一下即可。

调整后的产品类

public class Product {

    private String name;

    private Integer val;

    Product(Builder builder) {
        this.name = builder.name;
        this.val = builder.val;
    }

    @Override
    public String toString() {
        return "Product is " + name + " value is " + val;
    }
}

这代码只是将构造方法改了,使用 Builder 来创建 Product 对象。

调整后的具体建造者

public class ConcreteBuilder extends Builder {

    @Override
    public void setVal(Integer val) {
        /**
         * 产品类内部的逻辑
         * 实际存储的值是 val + 100
         */

        this.val = val + 100;
    }

    @Override
    // 组建一个产品
    public Product buildProduct() {
        // 这块还可以写特殊的校验逻辑
        return new Product(this);
    }
}

相应的使用带 Builder Product 的构造方法。

JDK中的建造者模式

StringBuilder (截取部分源码)

抽象建造者

abstract class AbstractStringBuilder implements AppendableCharSequence {

    /**
     * The value is used for character storage.
     */

    char[] value;

    /**
     * The count is the number of characters used.
     */

    int count;
    
    public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }

    // Documentation in subclasses because of synchro difference
    @Override
    public AbstractStringBuilder append(CharSequence s) {
        if (s == null)
            return appendNull();
        if (s instanceof String)
            return this.append((String)s);
        if (s instanceof AbstractStringBuilder)
            return this.append((AbstractStringBuilder)s);

        return this.append(s, 0, s.length());
    }

    public AbstractStringBuilder delete(int start, int end) {
        if (start 0)
            throw new StringIndexOutOfBoundsException(start);
        if (end > count)
            end = count;
        if (start > end)
            throw new StringIndexOutOfBoundsException();
        int len = end - start;
        if (len > 0) {
            System.arraycopy(value, start+len, value, start, count-end);
            count -= len;
        }
        return  this;
    }
}

具体建造者

public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.SerializableCharSequence
{
    @Override
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }

    @Override
    public StringBuilder append(CharSequence s) {
        super.append(s);
        return this;
    }

    /**
     * @throws StringIndexOutOfBoundsException {@inheritDoc}
     */

    @Override
    public StringBuilder delete(int start, int end) {
        super.delete(start, end);
        return this;
    }
}

StringBuilder 中的建造者模式比较简单,但是我的确没找到 StringBuilder 非要用建造者模式的原因,或许就是想让我们写下面这样的代码?

public static void main(String[] args) {
    StringBuilder sb = new StringBuilder();
    sb.append("Love ")
      .append("iisheng !")
      .insert(0"I ");

    System.out.println(sb);
}

但是我希望你能通过 StringBuilder ,感受一下建造者模式的气息

Guava Cache中的建造者模式

如何使用 Guava Cache?

public static void main(String[] args) {

    LoadingCache cache = CacheBuilder.newBuilder()
            // 最多存放十个数据
            .maximumSize(10)
            // 缓存10秒
            .expireAfterWrite(10, TimeUnit.SECONDS)
            .build(new CacheLoader() {
                // 默认返回-1,也可以是查询操作,如从DB查询
                @Override
                public Integer load(String key) throws Exception {
                    return -1;
                }
            });
            
    // 只查询缓存,没有命中,即返回 null
    System.out.println(cache.getIfPresent("key1"));
    // put数据,放在缓存中
    cache.put("key1"1);
    // 再次查询,已经存在缓存中
    System.out.println(cache.getIfPresent("key1"));

    //查询缓存,未命中,调用load方法,返回 -1
    try {
        System.out.println(cache.get("key2"));
    } catch (ExecutionException e) {
        e.printStackTrace();
    }

}

下面是截取建造者模式相关的部分代码

产品接口

@DoNotMock("Use CacheBuilder.newBuilder().build()")
@GwtCompatible
public interface Cache<KV{

  @Nullable
  getIfPresent(@CompatibleWith("K") Object key);

  get(K key, Callable extends V> loader) throws ExecutionException;

  void put(K key, V value);

  long size();

  ConcurrentMap asMap();

  void cleanUp();
}

另一个产品接口

@GwtCompatible
public interface LoadingCache<KVextends Cache<KV>, Function<KV{

  get(K key) throws ExecutionException;

  getUnchecked(K key);

  void refresh(K key);
  
  @Deprecated
  @Override
  apply(K key);

  @Override
  ConcurrentMap asMap();
}

产品实现类

static class LocalManualCache<KVimplements Cache<KV>, Serializable {
    
    final LocalCache localCache;
    
    LocalManualCache(CacheBuilder super K, ? super V> builder) {
      this(new LocalCache(builder, null));
    }
    
    private LocalManualCache(LocalCache localCache) {
      this.localCache = localCache;
    }
    
    // Cache methods
    
    @Override
    public @Nullable getIfPresent(Object key) {
      return localCache.getIfPresent(key);
    }
    
    @Override
    public V get(K key, final Callable extends V> valueLoader) throws ExecutionException {
      checkNotNull(valueLoader);
      return localCache.get(
          key,
          new CacheLoader() {
            @Override
            public V load(Object key) throws Exception {
              return valueLoader.call();
            }
          });
    }
    
    @Override
    public void put(K key, V value) {
      localCache.put(key, value);
    }

    
    @Override
    public long size() {
      return localCache.longSize();
    }
    
    @Override
    public ConcurrentMap asMap() {
      return localCache;
    }
    
    @Override
    public void cleanUp() {
      localCache.cleanUp();
    }
    
    // Serialization Support
    
    private static final long serialVersionUID = 1;
    
    Object writeReplace() {
      return new ManualSerializationProxy<>(localCache);
    }
}

另一个产品实现类

static class LocalLoadingCache<KVextends LocalManualCache<KV>
        implements LoadingCache<KV
{

    LocalLoadingCache(
        CacheBuilder super K, ? super V> builder, CacheLoader super K, V> loader) {
      super(new LocalCache(builder, checkNotNull(loader)));
    }
    
    // LoadingCache methods
    @Override
    public V get(K key) throws ExecutionException {
      return localCache.getOrLoad(key);
    }
    
    @Override
    public V getUnchecked(K key) {
      try {
        return get(key);
      } catch (ExecutionException e) {
        throw new UncheckedExecutionException(e.getCause());
      }
    }
    
    @Override
    public void refresh(K key) {
      localCache.refresh(key);
    }
    
    @Override
    public final V apply(K key) {
      return getUnchecked(key);
    }
    
    // Serialization Support
    private static final long serialVersionUID = 1;
    
    @Override
    Object writeReplace() {
      return new LoadingSerializationProxy<>(localCache);
    }
}

实际产品实现类LocalCache

上面两个产品类实际上,内部使用的是 LocalCache 来存储数据。我们再看下 LocalCache 的实现。

LocalCache 继承 AbstractCache ,我们先看 AbstractCache

@GwtCompatible
public abstract class AbstractCache<KVimplements Cache<KV{

  /** Constructor for use by subclasses. */
  protected AbstractCache() {}

  @Override
  public V get(K key, Callable extends V> valueLoader) throws ExecutionException {
    throw new UnsupportedOperationException();
  }

  @Override
  public void put(K key, V value) {
    throw new UnsupportedOperationException();
  }

  @Override
  public void cleanUp() {}

  @Override
  public long size() {
    throw new UnsupportedOperationException();
  }

  @Override
  public ConcurrentMap asMap() {
    throw new UnsupportedOperationException();
  }

}

再来看, LocalCache

@GwtCompatible(emulated = true)
class LocalCache<KVextends AbstractMap<KVimplements ConcurrentMap<KV{

  /** How long after the last write to an entry the map will retain that entry. */
  final long expireAfterWriteNanos;
  
  /** The default cache loader to use on loading operations. */
  final @Nullable CacheLoader super






请到「今天看啥」查看全文