图文并茂讲解@Builder和建造者模式

来源于公众号如逆水行舟 ,

作者iisheng

前言

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

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

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

@Builder的使用


编译后源码

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

通过IDE查看该.class源码

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



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

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

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

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

建造者模式

UML类图

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

产品类

<code>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;     } }/<code>

抽象建造者

<code>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(); }/<code>

具体建造者

<code>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);     } }/<code>

导演类

<code>public class Director {     private Builder builder = new ConcreteBuilder();     public Product getAProduct() {         // 设置不同的零件,产生不同的产品         builder.setName("ProductA");         builder.setVal(2);         return builder.buildProduct();     } }/<code>

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

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

调整后的产品类

<code>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;     } }/<code>

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

调整后的具体建造者

<code>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);     } }/<code>

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

JDK中的建造者模式

StringBuilder (截取部分源码)

抽象建造者



具体建造者

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

<code>public static void main(String[] args) {     StringBuilder sb = new StringBuilder();     sb.append("Love ")       .append("iisheng !")       .insert(0, "I ");     System.out.println(sb); }/<code>

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

Guava Cache中的建造者模式

如何使用 Guava Cache?


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

产品接口

<code>@DoNotMock("Use CacheBuilder.newBuilder().build()") @GwtCompatible public interface Cache {   @Nullable   V getIfPresent(@CompatibleWith("K") Object key);   V get(K key, Callable extends V> loader) throws ExecutionException;   void put(K key, V value);   long size();   ConcurrentMap asMap();   void cleanUp(); }/<code>

另一个产品接口

<code>@GwtCompatible public interface LoadingCache extends Cache, Function {   V get(K key) throws ExecutionException;   V getUnchecked(K key);   void refresh(K key);      @Deprecated   @Override   V apply(K key);   @Override   ConcurrentMap asMap(); }/<code>

产品实现类

<code>static class LocalManualCache implements Cache, 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 V 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);     } }/<code>

另一个产品实现类

<code>static class LocalLoadingCache extends LocalManualCache         implements LoadingCache {     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);     } }/<code>

实际产品实现类LocalCache

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

LocalCache继承AbstractCache,我们先看AbstractCache:

<code>@GwtCompatible public abstract class AbstractCache implements Cache {   /** 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();   } }/<code>

再来看,LocalCache:

<code>@GwtCompatible(emulated = true) class LocalCache extends AbstractMap implements ConcurrentMap {   /** 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 K, V> defaultLoader;   /**    * Creates a new, empty map with the specified strategy, initial capacity and concurrency level.    */   LocalCache(       CacheBuilder super K, ? super V> builder, @Nullable CacheLoader super K, V> loader) {     concurrencyLevel = Math.min(builder.getConcurrencyLevel(), MAX_SEGMENTS);     maxWeight = builder.getMaximumWeight();     weigher = builder.getWeigher();     expireAfterAccessNanos = builder.getExpireAfterAccessNanos();     expireAfterWriteNanos = builder.getExpireAfterWriteNanos();     refreshNanos = builder.getRefreshNanos();     defaultLoader = loader;     int initialCapacity = Math.min(builder.getInitialCapacity(), MAXIMUM_CAPACITY);     if (evictsBySize() && !customWeigher()) {       initialCapacity = (int) Math.min(initialCapacity, maxWeight);     }          // Find the lowest power-of-two segmentCount that exceeds concurrencyLevel, unless     // maximumSize/Weight is specified in which case ensure that each segment gets at least 10     // entries. The special casing for size-based eviction is only necessary because that eviction     // happens per segment instead of globally, so too many segments compared to the maximum size     // will result in random eviction behavior.     int segmentShift = 0;     int segmentCount = 1;     while (segmentCount /<code>

建造者

<code>@GwtCompatible(emulated = true) public final class CacheBuilder {   long maximumSize = UNSET_INT;      long expireAfterWriteNanos = UNSET_INT;      Supplier extends StatsCounter> statsCounterSupplier = NULL_STATS_COUNTER;   public CacheBuilder maximumSize(long maximumSize) {     checkState(         this.maximumSize == UNSET_INT, "maximum size was already set to %s", this.maximumSize);     checkState(         this.maximumWeight == UNSET_INT,         "maximum weight was already set to %s",         this.maximumWeight);     checkState(this.weigher == null, "maximum size can not be combined with weigher");     checkArgument(maximumSize >= 0, "maximum size must not be negative");     this.maximumSize = maximumSize;     return this;   }      public CacheBuilder expireAfterWrite(long duration, TimeUnit unit) {     checkState(         expireAfterWriteNanos == UNSET_INT,         "expireAfterWrite was already set to %s ns",         expireAfterWriteNanos);     checkArgument(duration >= 0, "duration cannot be negative: %s %s", duration, unit);     this.expireAfterWriteNanos = unit.toNanos(duration);     return this;   }      public CacheBuilder recordStats() {     statsCounterSupplier = CACHE_STATS_COUNTER;     return this;   }      public  Cache build() {     checkWeightWithWeigher();     checkNonLoadingCache();     return new LocalCache.LocalManualCache<>(this);   }      public  LoadingCache build(       CacheLoader super K1, V1> loader) {     checkWeightWithWeigher();     return new LocalCache.LocalLoadingCache<>(this, loader);   } }/<code>

Guava Cache的代码还是蛮复杂的,来一张UML图,便于理解

LoadingCache接口继承了Cache接口,两个接口都定义了缓存的基本方法CacheLoader是LocalCache的成员变量LocalCache继承AbstractMap,是真正意义上的产品类LocalManualCache是CacheBuilder的build()方法产生的对象的类,LocalManualCache因为有LocalCache作为成员变量,使得它成为了产品类,LocalManualCache实现了Cache接口LocalLoadingCache继承了LocalManualCache,是CacheBuilder的build(CacheLoader super K1, V1> loader)方法产生的对象的类,LocalLoadingCache实现了LoadingCache接口

总结

什么时候适合使用建造者模式?

创建对象参数过多的时候

创建一个有很多属性的对象,如果参数在构造方法中写,看起来很乱,一长串不说,还很容易写错。

对象的部分属性是可选择的时候

创建的对象有很多属性是可选择的那种,常见的比如配置类等,不同使用者有不同的配置。

对象创建完成后,就不能修改内部属性的时候

不提供set()方法,使用建造者模式一次性把对象创建完成。

建造者模式和工厂模式的区别是什么?

建造者模式,通过设置不同的可选参数,“定制化”的创建不同的对象工厂模式,是直接创建不同但是相关类型的对象(继承同一父类或者接口的一组子类)

最后想说的

由@Builder想到的建造者模式,然后看了StringBuilder以及Guava Cache的源码,其中还是有很多值得我们学习的地方。

建造者模式,可能不同的人有不同的理解,不同的实现有不同的方法,但是我们只有深刻的理解了其中的设计思想,才不至于在项目中生搬硬套,才能灵活运用。