zoukankan      html  css  js  c++  java
  • 设计模式实战系列之@Builder和建造者模式

    前言

    备受争议的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。代码只需要将上面的ProductConcreteBuilder调整一下即可。

    调整后的产品类

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

    相应的使用带BuilderProduct的构造方法。

    JDK中的建造者模式

    StringBuilder (截取部分源码)

    抽象建造者

    abstract class AbstractStringBuilder implements Appendable, CharSequence {
    
        /**
         * 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.Serializable, CharSequence
    {
        @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<String, Integer> cache = CacheBuilder.newBuilder()
                // 最多存放十个数据
                .maximumSize(10)
                // 缓存10秒
                .expireAfterWrite(10, TimeUnit.SECONDS)
                .build(new CacheLoader<String, Integer>() {
                    // 默认返回-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<K, V> {
    
      @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<K, V> asMap();
    
      void cleanUp();
    }
    

    另一个产品接口

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

    产品实现类

    static class LocalManualCache<K, V> implements Cache<K, V>, Serializable {
        
        final LocalCache<K, V> localCache;
        
        LocalManualCache(CacheBuilder<? super K, ? super V> builder) {
          this(new LocalCache<K, V>(builder, null));
        }
        
        private LocalManualCache(LocalCache<K, V> 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<Object, V>() {
                @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<K, V> 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<K, V> extends LocalManualCache<K, V>
            implements LoadingCache<K, V> {
    
        LocalLoadingCache(
            CacheBuilder<? super K, ? super V> builder, CacheLoader<? super K, V> loader) {
          super(new LocalCache<K, V>(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<K, V> implements Cache<K, V> {
    
      /** 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<K, V> asMap() {
        throw new UnsupportedOperationException();
      }
    
    }
    

    再来看,LocalCache

    @GwtCompatible(emulated = true)
    class LocalCache<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V> {
    
      /** 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 < concurrencyLevel && (!evictsBySize() || segmentCount * 20 <= maxWeight)) {
          ++segmentShift;
          segmentCount <<= 1;
        }
        this.segmentShift = 32 - segmentShift;
        segmentMask = segmentCount - 1;
    
        this.segments = newSegmentArray(segmentCount);    
      }
    }
    

    建造者

    @GwtCompatible(emulated = true)
    public final class CacheBuilder<K, V> {
    
      long maximumSize = UNSET_INT;
      
      long expireAfterWriteNanos = UNSET_INT;
      
      Supplier<? extends StatsCounter> statsCounterSupplier = NULL_STATS_COUNTER;
    
      public CacheBuilder<K, V> 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<K, V> 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<K, V> recordStats() {
        statsCounterSupplier = CACHE_STATS_COUNTER;
        return this;
      }
      
      public <K1 extends K, V1 extends V> Cache<K1, V1> build() {
        checkWeightWithWeigher();
        checkNonLoadingCache();
        return new LocalCache.LocalManualCache<>(this);
      }
      
      public <K1 extends K, V1 extends V> LoadingCache<K1, V1> build(
          CacheLoader<? super K1, V1> loader) {
        checkWeightWithWeigher();
        return new LocalCache.LocalLoadingCache<>(this, loader);
      }
    
    }
    

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

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

    总结

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

    创建对象参数过多的时候

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

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

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

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

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

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

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

    最后想说的

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

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

    参考文献:

    [1]:《设计模式之禅》

    [2]:《Effective Java中文版》

    [3]:《设计模式之美 建造者模式》

    欢迎关注个人微信公众号【如逆水行舟】,用心输出基础、算法、源码系列文章。

  • 相关阅读:
    遇到的问题
    getContextPath、getServletPath、getRequestURI的区别
    js判断是否是ie浏览器
    js判断浏览器类型和版本
    最短JS判断是否为IE6(IE的写法)
    Console命令详解,让调试js代码变得更简单
    让table中td的内容靠上对齐
    <c:out>标签中的escapeXML属性
    指纹识别技术设计的注意事项
    嵌入式指纹检索系统设计
  • 原文地址:https://www.cnblogs.com/iisheng/p/13490377.html
Copyright © 2011-2022 走看看