我们将会深入的剖析几个经典的开源项目,对其中设计的设计原则,思想和模式,我们会对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对象,这种模式其实更像是装饰器模式的实现