我们首先说了,如何将项目中的和业务无关的模块,进行抽离出来,设计成为独立的类库,框架,功能组件.

我们还会利用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是在确认无误后,可以说是创建了一个新的不变集合,然后遍历传入的集合添加到新集合中,自然不会受到影响

发表评论

邮箱地址不会被公开。 必填项已用*标注