我们首先说了,如何将项目中的和业务无关的模块,进行抽离出来,设计成为独立的类库,框架,功能组件.
我们还会利用Guava来讲解其中涉及的经典设计模式,主要有Builder模式,Wrapper模式,以及Immutable模式
1.Builder模式
在项目开发中,我们经常会用到缓存,可以有效的提高访问的速度,如果使用了Redis,Memcache,需要单独的部署一套缓存的系统,这个系统具有一定的出错概率,而且一个系统需要的外部组件越多,整体维护性就要降低,维护成本要升高
取而代之,我们可以在自己的系统内部构建一个内存缓存,跟系统集成在一起,开发,部署,那么如何去构建内存缓存呢?可以基于JDK提供的类,比如HashMap,从0开始,开发内存缓存,不过,从0开发一个内存缓存,涉及的工作会很多,比如,缓存的淘汰策略,为了简化开发,我们可以使用Google Guava提供的现成的缓存工具类
com.google.common.cache.*
使用Google Guava来建立一个内存的缓存并不难,
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); } } |
上面的Cache的对象,通过CacheBuilder来创建的,使用了建造者模式
毕竟,一个Cache缓存中,需要配置很多的参数,比如过期的时间,淘汰的策略,最大缓存的大小,相应的Cache类有这些成员变量,如果在构造函数中统统设置,那么构造函数会很长,而且又有很多的参数不需要用户设置,那么还需要设置多个不同参数列表的构造函数
为了避免构造函数的参数列表过长,不同的构造函数数量很多,我们一般有两种解决方案,其中之一就是Builder模式,或者使用无参构造函数来创建一个对象,在通过setXXX()来逐一设置需要的设置的成员变量
但是普遍还是选择使用了Builder模式,原因可以参考下面的build()
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”); } } |
看,在build创建新的Cache对象的时候,进行了两个校验方法的校验,
如果采用了无参的构造函数加上setXXX()的方法的方案,两个校验无处安放了,创建了Cache对象是不合法的
Wrapper模式在Guava中的应用
在Google Guava的collection包的路径下,有一组以Forwarding开头命名的类
这组Forwarding类很多,但是实现的方式都很类似,比如Collection部分中的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(); } //…省略部分代码… } |
光看ForwardingCollection的代码实现,看起来像是个装饰器类
其实本质上,可以作为装饰器类,也可以作为代理类去使用
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); } } |
上面可以看出,AddLoggingCollection是基于代理模式实现的一个代理类,在原始的Collection类基础上,进行了增强功能,在add相关的操作,都进行日志的记录
对于代理模式,装饰器模式,适配器模式都可以被称为Wrapper模式
通过Wrapper类二层封装了原始类,并且利用组合来调用原始类的实现,将Wrapper类的函数实现委托给原始类的函数完成
public interface Interf {
void f1(); void f2(); } public class OriginalClass implements Interf { @Override public void f1() { //… } @Override public void f2() { //… } } public class WrapperClass implements Interf { private OriginalClass oc; public WrapperClass(OriginalClass oc) { this.oc = oc; } @Override public void f1() { //…附加功能… this.oc.f1(); //…附加功能… } @Override public void f2() { this.oc.f2(); } } |
如果不去使用这个ForwardingCollecgtion类,而是直接让AddLoggingCollection代理类直接实现Collection接口,那么Collection接口中的所有方法,都要在AddLoggingCollection类中实现以便,但是真正的需要日志的功能的接口只有add()和addAll()两个函数,其他函数,最好还是委托给原始的类对象去实现
为了简化Wrapper模式的代码实现,Guava提供了这些缺省的Forwarding类,方便在进行装饰或者代理的时候,进行扩展,实现自己关心的方法
对于不关心的方法,使用缺省的Forwarding类的实现
Immutable模式在Guava中的应用
所谓的不变模式
相比较于设计模式,更符合是一种设计思路,就是一个对象在创建之后,就不在改变,就是所谓的不变模式,其中涉及的类就是不变类,对象就是不变对象,在Java中,最常见的不变类就是String
不变模式可以分为两种,一种是普通的不变模式,另一种是深度的不变模式
普通不变模式是指,对象中包含的引用对象可以改变的,这也是我们常指的不变模式,但是深度不变模式是对象包含的引用对象也不可变,两者的关系类似深拷贝和浅拷贝
// 普通不变模式
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方法.. } |
对于某些业务场景,如果一个对象创建后就不会被改变,那么就可以设计为不变类,简单的不变类不难设计,只要所有的成员变量都只能通过构造函数来设置好,不暴露任何set等修改成员变量的方法,除此外,因为数据不变,所以不存在并发读写的问题
在此基础上,出现了不变结合的问题,对于Java JDK,提供了UnmodifiableCollection,UnmodifiableList,UnmodifiableSet,UnmodifiableMap…
Google Guava提供了ImmutableCollection和ImmutableList,不变模式分为了普通不变和深度不变
上面提供的不变集合都是普通不变
但是两者的实现并不一样
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(); } } |
课后总结
首先是几个常用的设计模式
Builder模式
Wrapper模式
Immutable模式
这些都是潜藏在程序设计中,我们要学习的,是蕴含的思想
课堂思考
Java JDK的Unmodififable和Guava的Immutable两个API
一个是原始集合增加数据后,不变集合的数据随之增加了
Google Guava的不变集合数据并没有增加,如何实现的呢?
对于课后问题,JDK中的UnmodifiableList可以理解为装饰器类,部分操作委托了原本的list,例如get,对于一些修改操作,诸如,set,add,会抛出异常,所以会在原本的集合修改的时候,收到影响
而ImmutableList是在确认无误后,可以说是创建了一个新的不变集合,然后遍历传入的集合添加到新集合中,自然不会受到影响