10k

设计模式之美-课程笔记48-开源实战3-从Guava学习开发通用模块

  • 本章节主要分三部分
    • Guava的简单介绍,引出如何开发一个通用模块;
    • Guava中的设计模式,以及Immutable模式;
    • 通过Guava介绍函数式编程

Guava介绍

  1. Google内部的Java开发工具库的开源版本。

如何发现通用的功能模块

  1. 在业务开发中,跟业务无关的通用功能模块一般有三种:类库、框架、功能组件。
  2. Guava属于类库,提供一组API接口。EventBus、DI容器属于框架。提供骨架代码,能让业务人员聚焦在业务开发部分,在预留的扩展点力填充业务代码。ID生成器、性能计数器属于功能组件,提供一组有特殊功能的API接口,与类库不同的是它更聚焦和重量级(依赖外部系统,并非一个简单的小API功能)。
  3. 通用功能模块:复用业务无关
    1. 不能复用就无法抽离出来多次使用;
    2. 业务相关的就会做成微服务(独立的系统)。

如何开发通用的功能模块

  1. 用户为程序员的产品。产品意识。
  2. 服务意识,答疑解惑产品使用。

辩证的看,不要总是想着一开始就过度优化,想要设计组建。一开始可以注重一下设计让通用的部分和业务尽量解耦。等时机成熟了再从项目中剥离出来。

Reference

Guava

Guava中的设计模式

Builder模式

public class CacheDemo {
  public static void main(String[] args) {
    Cache<String, String> cache = CacheBuilder.newBuilder()
            .initialCapacity(100)
            .maximumSize(1000)
            .expireAfterWrite(10, TimeUnit.MINUTES)
            .build();

    cache.put("key1", "value1");
    String value = cache.getIfPresent("key1");
    System.out.println(value);
  }
}
  1. Builder 模式在构造缓存过程中,允许多个参数的定制化传入。
  2. 使用构造者模式而不是多个setter是因为在build函数中我们需要对缓存的参数进行一些校验。而setter不能优雅的实现这个参数校验。
public <K1 extends K, V1 extends V> Cache<K1, V1> build() {
  this.checkWeightWithWeigher();
  this.checkNonLoadingCache();
  return new LocalManualCache(this);
}

private void checkNonLoadingCache() {
  Preconditions.checkState(this.refreshNanos == -1L, "refreshAfterWrite requires a LoadingCache");
}

private void checkWeightWithWeigher() {
  if (this.weigher == null) {
    Preconditions.checkState(this.maximumWeight == -1L, "maximumWeight requires weigher");
  } else if (this.strictParsing) {
    Preconditions.checkState(this.maximumWeight != -1L, "weigher requires maximumWeight");
  } else if (this.maximumWeight == -1L) {
    logger.log(Level.WARNING, "ignoring weigher specified without maximumWeight");
  }

}

Wrapper模式

ForwardingCollection 的实现:

@GwtCompatible
public abstract class ForwardingCollection<E> extends ForwardingObject implements Collection<E> {
  protected ForwardingCollection() {
  }

  protected abstract Collection<E> delegate();

  public Iterator<E> iterator() {
    return this.delegate().iterator();
  }

  public int size() {
    return this.delegate().size();
  }

  @CanIgnoreReturnValue
  public boolean removeAll(Collection<?> collection) {
    return this.delegate().removeAll(collection);
  }

  public boolean isEmpty() {
    return this.delegate().isEmpty();
  }

  public boolean contains(Object object) {
    return this.delegate().contains(object);
  }

  @CanIgnoreReturnValue
  public boolean add(E element) {
    return this.delegate().add(element);
  }

  @CanIgnoreReturnValue
  public boolean remove(Object object) {
    return this.delegate().remove(object);
  }

  public boolean containsAll(Collection<?> collection) {
    return this.delegate().containsAll(collection);
  }

  @CanIgnoreReturnValue
  public boolean addAll(Collection<? extends E> collection) {
    return this.delegate().addAll(collection);
  }

  @CanIgnoreReturnValue
  public boolean retainAll(Collection<?> collection) {
    return this.delegate().retainAll(collection);
  }

  public void clear() {
    this.delegate().clear();
  }

  public Object[] toArray() {
    return this.delegate().toArray();
  }
  
  //...省略部分代码...
}

用法

public class AddLoggingCollection<E> extends ForwardingCollection<E> {
  private static final Logger logger = LoggerFactory.getLogger(AddLoggingCollection.class);
  private Collection<E> originalCollection;

  public AddLoggingCollection(Collection<E> originalCollection) {
    this.originalCollection = originalCollection;
  }

  @Override
  protected Collection delegate() {
    return this.originalCollection;
  }

  @Override
  public boolean add(E element) {
    logger.info("Add element: " + element);
    return this.delegate().add(element);
  }

  @Override
  public boolean addAll(Collection<? extends E> collection) {
    logger.info("Size of elements to add: " + collection.size());
    return this.delegate().addAll(collection);
  }

}

相当于对原来的类进行了一层包装和增强。

代理模式、装饰器、适配器模式可以统称为Wrapper模式。

如果不使用这个Forwarding Collection类,而是让AddLoggingCollection直接实现Collection接口他就要实现所有的方法,尽管只有add和addAll方法需要增强。

Guava 的forwardingXXX提供了一组可以缺省的类,只需要增强需要增强的类即可,其他的已经在forwardingXXX类中实现了代理。

Immutable模式

  1. 一个对象在创建后就不再改变这就是所谓的不变模式。
  2. 普通不变模式: 对象中包含的引用对象是可以改变的。深度不变模式:引用也不可以改变。常说的是普通不变模式。(区别类似于深浅拷贝)。
// 普通不变模式
public class User {
  private String name;
  private int age;
  private Address addr;
  
  public User(String name, int age, Address addr) {
    this.name = name;
    this.age = age;
    this.addr = addr;
  }
  // 只有getter方法,无setter方法...
}

public class Address {
  private String province;
  private String city;
  public Address(String province, String city) {
    this.province = province;
    this.city= city;
  }
  // 有getter方法,也有setter方法...
}

// 深度不变模式
public class User {
  private String name;
  private int age;
  private Address addr;
  
  public User(String name, int age, Address addr) {
    this.name = name;
    this.age = age;
    this.addr = addr;
  }
  // 只有getter方法,无setter方法...
}

public class Address {
  private String province;
  private String city;
  public Address(String province, String city) {
    this.province = province;
    this.city= city;
  }
  // 只有getter方法,无setter方法..
}
  1. 显式的设置对象的不可变,不为其提供setter,能避免其意外的被修改。一般用于多线程环境下。

  2. Guava提供的一种不变集合。属于普通不变类,整个集合的对象不增删但是队形的成员变量(属性)可以改变。

  3. 与JDK中不变集合的区别:JDK使用的是引用(浅拷贝)而guava用的是深拷贝。
public class ImmutableDemo {
  public static void main(String[] args) {
    List<String> originalList = new ArrayList<>();
    originalList.add("a");
    originalList.add("b");
    originalList.add("c");

    List<String> jdkUnmodifiableList = Collections.unmodifiableList(originalList);
    List<String> guavaImmutableList = ImmutableList.copyOf(originalList);

    //jdkUnmodifiableList.add("d"); // 抛出UnsupportedOperationException
    // guavaImmutableList.add("d"); // 抛出UnsupportedOperationException
    originalList.add("d");

    print(originalList); // a b c d
    print(jdkUnmodifiableList); // a b c d
    print(guavaImmutableList); // a b c
  }

  private static void print(List<String> list) {
    for (String s : list) {
      System.out.print(s + " ");
    }
    System.out.println();
  }
}

借助Guava学习函数式编程

什么是函数式编程

  1. 程序可以用一系列数学函数表达式的组合来表示。
  2. 他有自己的应用场景: 科学计算、数据处理、统计分析。
  3. 与面向过程类似,以函数为组织单元。他们的区别在于他的函数是无状态的。
  4. 无状态指的是函数内部涉及的都是局部变量,与面向过程不一样(共享变量)。
// 有状态函数: 执行结果依赖b的值是多少即便入参相同多次执行函数函数的返回值有可能不同因为b值有可能不同
int b;
int increase(int a) {
  return a + b;
}

// 无状态函数执行结果不依赖任何外部变量值只要入参相同不管执行多少次函数的返回值就相同
int increase(int a, int b) {
  return a + b;
}

Java对函数式编程的支持

函数主要指的是数学概念的函数。

public class FPDemo {
  public static void main(String[] args) {
    Optional<Integer> result = Stream.of("f", "ba", "hello")
            .map(s -> s.length())
            .filter(l -> l <= 3)
            .max((o1, o2) -> o1-o2);
    System.out.println(result.get()); // 输出2
  }
}

Stream 类

支持级联函数操作

add(multiply(subtract(3,1),2),5);vssubtract(3,1).multiply(2).add(5);

Lambda 表达式

简化函数编写

// Stream中map函数的定义:
public interface Stream<T> extends BaseStream<T, Stream<T>> {
  <R> Stream<R> map(Function<? super T, ? extends R> mapper);
  //...省略其他函数...
}

// Stream中map的使用方法:
Stream.of("fo", "bar", "hello").map(new Function<String, Integer>() {
  @Override
  public Integer apply(String s) {
    return s.length();
  }
});

// 用Lambda表达式简化后的写法:
Stream.of("fo", "bar", "hello").map(s -> s.length());

函数接口

数接口就是接口。不过,它也有自己特别的地方,那就是要求只包含一个未实现的方法。因为只有这样,Lambda 表达式才能明确知道匹配的是哪个接口。如果有两个未实现的方法,并且接口入参、返回值都一样,那 Java 在翻译 Lambda 表达式的时候,就不知道表达式对应哪个方法了。

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);  // 只有这一个未实现的方法

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t); // 只有这一个未实现的方法

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

Guava对函数式编程的增强

  1. 避免过度使用,降低代码可读性
  2. 主要增强的是集合便利,简化代码。
Thoughts? Leave a comment