我们将会深入的剖析几个经典的开源项目,对其中设计的设计原则,思想和模式,我们会对Java JDK,Unix,Google Guava,Spring,MyBatis这样几个开源项目进行分析,

首先我们看一个类,Java.util.Calender,从命名上来看,我们只知道这是一个时间相关的类,具体的设计模式不可知道

Calender类提供了大量和日期相关的功能代码,提供了一个getinstance()的工厂方法,用于根据不同TimeZone和Locale创建不同的Calendar子类独享,也就是功能代码和工厂方法代码耦合在了一个类中,所以,即便我们去查看他的源码,不细心的话,也很难看到工厂模式

Calendar类的相关代码如下,我们给出了getInstance()工厂的代码实现,从代码中可以看出,根据不同的TimeZone和Locale,创建不同的Calendar子类,比如BuddistCalendar,GregorianCalendar,具体的实现类不需要知道,我们只需要传递对应的时区和地址就可以了

public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar> {

//…

public static Calendar getInstance(TimeZone zone, Locale aLocale){

return createCalendar(zone, aLocale);

}

private static Calendar createCalendar(TimeZone zone,Locale aLocale) {

CalendarProvider provider = LocaleProviderAdapter.getAdapter(

CalendarProvider.class, aLocale).getCalendarProvider();

if (provider != null) {

try {

return provider.getInstance(zone, aLocale);

} catch (IllegalArgumentException iae) {

// fall back to the default instantiation

}

}

Calendar cal = null;

if (aLocale.hasExtensions()) {

String caltype = aLocale.getUnicodeLocaleType(“ca”);

if (caltype != null) {

switch (caltype) {

case “buddhist”:

cal = new BuddhistCalendar(zone, aLocale);

break;

case “japanese”:

cal = new JapaneseImperialCalendar(zone, aLocale);

break;

case “gregory”:

cal = new GregorianCalendar(zone, aLocale);

break;

}

}

}

if (cal == null) {

if (aLocale.getLanguage() == “th” && aLocale.getCountry() == “TH”) {

cal = new BuddhistCalendar(zone, aLocale);

} else if (aLocale.getVariant() == “JP” && aLocale.getLanguage() == “ja” && aLocale.getCountry() == “JP”) {

cal = new JapaneseImperialCalendar(zone, aLocale);

} else {

cal = new GregorianCalendar(zone, aLocale);

}

}

return cal;

}

//…

}

在其中不仅仅使用了工厂模式,还使用了建造者模式,建造者模式往常有两种实现方式,一种是将Builder设计为独立的类,一种是将Builder设计为内部类,Calendar使用了第二种思路

我们先来看代码

public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar> {

//…

public static class Builder {

private static final int NFIELDS = FIELD_COUNT + 1;

private static final int WEEK_YEAR = FIELD_COUNT;

private long instant;

private int[] fields;

private int nextStamp;

private int maxFieldIndex;

private String type;

private TimeZone zone;

private boolean lenient = true;

private Locale locale;

private int firstDayOfWeek, minimalDaysInFirstWeek;

public Builder() {}

public Builder setInstant(long instant) {

if (fields != null) {

throw new IllegalStateException();

}

this.instant = instant;

nextStamp = COMPUTED;

return this;

}

//…省略n多set()方法

public Calendar build() {

if (locale == null) {

locale = Locale.getDefault();

}

if (zone == null) {

zone = TimeZone.getDefault();

}

Calendar cal;

if (type == null) {

type = locale.getUnicodeLocaleType(“ca”);

}

if (type == null) {

if (locale.getCountry() == “TH” && locale.getLanguage() == “th”) {

type = “buddhist”;

} else {

type = “gregory”;

}

}

switch (type) {

case “gregory”:

cal = new GregorianCalendar(zone, locale, true);

break;

case “iso8601”:

GregorianCalendar gcal = new GregorianCalendar(zone, locale, true);

// make gcal a proleptic Gregorian

gcal.setGregorianChange(new Date(Long.MIN_VALUE));

// and week definition to be compatible with ISO 8601

setWeekDefinition(MONDAY, 4);

cal = gcal;

break;

case “buddhist”:

cal = new BuddhistCalendar(zone, locale);

cal.clear();

break;

case “japanese”:

cal = new JapaneseImperialCalendar(zone, locale, true);

break;

default:

throw new IllegalArgumentException(“unknown calendar type: ” + type);

}

cal.setLenient(lenient);

if (firstDayOfWeek != 0) {

cal.setFirstDayOfWeek(firstDayOfWeek);

cal.setMinimalDaysInFirstWeek(minimalDaysInFirstWeek);

}

if (isInstantSet()) {

cal.setTimeInMillis(instant);

cal.complete();

return cal;

}

if (fields != null) {

boolean weekDate = isSet(WEEK_YEAR) && fields[WEEK_YEAR] > fields[YEAR];

if (weekDate && !cal.isWeekDateSupported()) {

throw new IllegalArgumentException(“week date is unsupported by ” + type);

}

for (int stamp = MINIMUM_USER_STAMP; stamp < nextStamp; stamp++) {

for (int index = 0; index <= maxFieldIndex; index++) {

if (fields[index] == stamp) {

cal.set(index, fields[NFIELDS + index]);

break;

}

}

}

if (weekDate) {

int weekOfYear = isSet(WEEK_OF_YEAR) ? fields[NFIELDS + WEEK_OF_YEAR] : 1;

int dayOfWeek = isSet(DAY_OF_WEEK) ? fields[NFIELDS + DAY_OF_WEEK] : cal.getFirstDayOfWeek();

cal.setWeekDate(fields[NFIELDS + WEEK_YEAR], weekOfYear, dayOfWeek);

}

cal.complete();

}

return cal;

}

}

}

既然已经有了getInstance()工厂方法来创建Calendar对象,为何还要使用Builder来创建Calendar类对象呢?两者的区别在哪呢?

实际上,我们讲两种模式的时候,对于之间的区别做了详细的对比

工厂类模式根据给定的参数来创建不同类型,但具有相同父类或者接口的对象

建造者模式来创建一种类型复杂的对象,通过设置不同的可选参数,定制化的创建不同的对象

粗看Calendar的Build()方法,我们看出来,这有点工厂模式的影子在里面,我们根据不同的type来创建了不同的Calendar子类,然后在根据不同的参数细致的定制化之前的Calendar子类对象

这就是实际开发中,将工厂模式和建造模式搭配起来混用的实现,我们可以再实际的项目开发过程中,将不同的模式混合起来搭配使用

装饰器类在Collections类中的使用,

我们在JavaIO类库中也使用了装饰器模式,Collections类是一个集合容器的工具类,提供了很多的静态方法,用于创建各种的集合容器,比如通过unmodifiableCollection()静态方法,创建UnmodifiableCollection类对象,这些容器类,例如 UnmodifiableCollection类和CheckCollection类和SynchronizedCollection类,就是Collection类的装饰器类

我们拿UnmodfifableCollection类来举例下,这是Collections类的一个内部类,我们把相关的代码摘出来了

public class Collections {

private Collections() {}

public static <T> Collection<T> unmodifiableCollection(Collection<? extends T> c) {

return new UnmodifiableCollection<>(c);

}

static class UnmodifiableCollection<E> implements Collection<E>,   Serializable {

private static final long serialVersionUID = 1820017752578914078L;

final Collection<? extends E> c;

UnmodifiableCollection(Collection<? extends E> c) {

if (c==null)

throw new NullPointerException();

this.c = c;

}

public int size()                   {return c.size();}

public boolean isEmpty()            {return c.isEmpty();}

public boolean contains(Object o)   {return c.contains(o);}

public Object[] toArray()           {return c.toArray();}

public <T> T[] toArray(T[] a)       {return c.toArray(a);}

public String toString()            {return c.toString();}

public Iterator<E> iterator() {

return new Iterator<E>() {

private final Iterator<? extends E> i = c.iterator();

public boolean hasNext() {return i.hasNext();}

public E next()          {return i.next();}

public void remove() {

throw new UnsupportedOperationException();

}

@Override

public void forEachRemaining(Consumer<? super E> action) {

// Use backing collection version

i.forEachRemaining(action);

}

};

}

public boolean add(E e) {

throw new UnsupportedOperationException();

}

public boolean remove(Object o) {

hrow new UnsupportedOperationException();

}

public boolean containsAll(Collection<?> coll) {

return c.containsAll(coll);

}

public boolean addAll(Collection<? extends E> coll) {

throw new UnsupportedOperationException();

}

public boolean removeAll(Collection<?> coll) {

throw new UnsupportedOperationException();

}

public boolean retainAll(Collection<?> coll) {

throw new UnsupportedOperationException();

}

public void clear() {

throw new UnsupportedOperationException();

}

// Override default methods in Collection

@Override

public void forEach(Consumer<? super E> action) {

c.forEach(action);

}

@Override

public boolean removeIf(Predicate<? super E> filter) {

throw new UnsupportedOperationException();

}

@SuppressWarnings(“unchecked”)

@Override

public Spliterator<E> spliterator() {

return (Spliterator<E>)c.spliterator();

}

@SuppressWarnings(“unchecked”)

@Override

public Stream<E> stream() {

return (Stream<E>)c.stream();

}

@SuppressWarnings(“unchecked”)

@Override

public Stream<E> parallelStream() {

return (Stream<E>)c.parallelStream();

}

}

}

为什么说UnmodifiableCollection是Collection的装饰器类呢?

因为在UnmodifiableCollection的构造函数中接收了一个Collection类的对象,然后对其所有的函数进行了包裹Wrap,并且重新实现了一些函数,或者对某些函数进行了简单的封装,而部分简单的接口并没有去实现,从实现的角度来说,UnmodifiableCollection是典型的装饰器类

适配器模式在Collections类中的应用

适配器模式用于兼容老版本的接口

老版的JDK提供了Enumeration类来遍历容器,新版的JDK使用Iterator类来替代了Enumeration类遍历容器,同时,为了兼容老版本的代码,我们保留了enumeration()静态方法,我们通过这个静态函数来创建一个容器的Enumeration类对象

这样,新版中Enumeration类就是适配器类,适配了客户端代码(使用了老版本Enumeration)和新版本JDK中的迭代器类Iterator类,让老版本的业务代码无须更换调用方式直接使用

/**

* Returns an enumeration over the specified collection.  This provides

* interoperability with legacy APIs that require an enumeration

* as input.

*

* @param  <T> the class of the objects in the collection

* @param c the collection for which an enumeration is to be returned.

* @return an enumeration over the specified collection.

* @see Enumeration

*/

public static <T> Enumeration<T> enumeration(final Collection<T> c) {

return new Enumeration<T>() {

private final Iterator<T> i = c.iterator();

public boolean hasMoreElements() {

return i.hasNext();

}

public T nextElement() {

return i.next();

}

};

}

本章重点

我们着重讲了工厂模式,建造者模式,装饰器模式,适配器模式,这四种模式在Java JDK中的应用

在此中,我们了解到,在实际的项目开发中,我们对于模式的应用应该更加的灵活,可以直接搭配的使用

比如,JavaJDK中的Calendar类,就耦合了业务功能代码,工厂方法,建造者类三种类型的代码

在建造者模式中,前半部分是工厂方法的代码实现,后面才是真正的建造者模式

课后思考

StringBuilder是否是使用了建造者模式?

个人认为,是属于建造者模式的,在其中,最主要的append方法,是将其抛给了父类AbstractStringBuilder,然后返回自己,其父类AbstractStringBuilder中维护了一个数组,并且可以动然扩容,在我们最后获取结果的toString()方法中,就是直接new String对象,这种模式其实更像是装饰器模式的实现

发表评论

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