JDK 中用到的设计模式
工厂模式在 Calendar 类中的应用
大部分工厂类都是以 Factory 作为后缀来命名,并且工厂类主要负责创建对象这样一件事情。但是在实际的项目开发中,工厂类的设计更加灵活。比如,工厂模式在 Java JDK 中的一个应用 java.util.Calendar. 从命名上,无法看出它是一个工厂类。
Calendar 类提供了大量跟日期相关的功能代码,同时,又提供了一个 getInstance() 工厂方法,用来根据不同的 TimeZone 和 Locale 创建不同的Calendar 子类对象。
也就是说,功能代码和工厂方法代码耦合在了一个类中。它不单单是一个工厂类,所以并没有以 Factory 作为后缀来命名。
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;
}
//...
}
getInstance() 方法可以根据不同的 TimeZone 和 Locale 创建不同的 Calendar 子类对象,比如 BuddhistCalendar、JapaneseImperialCalendar、GregorianCalendar, 这些细节完全封装在工厂方法中,使用者只需要传递当前的时区和地址,就能获得一个 Calendar 类对象来使用,而获得的对象具体是那个 Calendar 子类的对象,使用在使用的时候并不关心。
建造者模式在 Calendar 类中的应用
Calendar 不仅仅用到了工厂模式,还用到了建造者模式。建造者模式有两种实现方式,一种是单独定义一个 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 的 Builder 类的 build() 方法,有点像是工厂模式,前面一半代码确实跟 getInstance() 工厂方法类似,根据不同的 type 创建了不同的 Calendar 子类。实际上,后面一半代码才属于标准的建造者模式,根据 setXXX() 方法设置的参数,来定制化刚刚创建的 Calendar 子类对象。
装饰器模式在 Collections 类中的应用
Collections 类是集合容器的工具类,提供了很多静态方法,用来创建各种集合容器,比如通过 unmodifiableCollection() 静态方法,来创建 UnmodifiableCollection 类对象。而 这些容器类中的 UnmodifiableCollection类、CheckedCollection 和 SynchronizedCollection 类,就是针对 Collection 类的装饰器类。
这三个装饰器类,在代码结构行几乎一样,以 UnmodifiableCollection 类来举例。UnmodifiableCollection 类是 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 类的装饰器类。
最关键的一点是,UnmodifiableCollection 的构造函数接收一个 Collection 类对象,然后对其所有的函数进行了包裹(Wrap): 重新实现(比如add() 函数)或者简单封装(比如 stream() 函数))。而简单的接口实现或者继承,并不会如此来实现 UnmodifiableCollection 类。
所以,从代码实现的角度来看,UnmodifiableCollection 类就是典型的装饰器类。
适配器模式在 Collections 类中的应用
适配器模式可以兼容老版本接口,老版本的 JDK 提供了 Enumeration 来遍历容器。新版本的 JDK 用 Iterator 类替代 Enumeration 类来遍历容器。为了兼容老版本代码(使用老版本 JDK 的代码),保留了 Enumeration 类,并且在 Collections 类中,仍然保留了 enumeration() 静态方法(使用这个静态方法来创建一个容器的 Enumeration 类对象)
在新版本的 JDK 中, 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();
}
};
}
模板模式在 Collections 中的应用
策略、模板、责任链三个模式常用在框架的设计中,提供框架的扩展点,让框架使用者,在不修改框架源码的情况下,基于扩展点定制化框架的功能。Java 中的 Collections 类的 sort() 函数就是利用率模板模式的这个扩展特性。
首先,来看看 Collections.sort() 函数是如何使用的。如下所示, 这个代码实现了按照不同的排序方式(按照年龄从小到大、按照名字字母序从小到大、按照成绩从大到小)对 students 数组进行排序。
public class Demo {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student("zhangsan", 18, 90));
students.add(new Student("lisi", 19, 80));
students.add(new Student("wangwu", 20, 70));
Collections.sort( students,new AgeAscComparator());
print(students);
Collections.sort( students,new NameAscComparator());
print(students);
Collections.sort( students,new ScoreDescComparator());
print(students);
}
public static void print(List<Student> students) {
System.out.println("排序:");
for (Student student : students) {
System.out.println(student.getName() + " " + student.getAge() + " " + student.getScore());
}
System.out.println("====================");
}
public static class AgeAscComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.getAge() - o2.getAge();
}
}
public static class NameAscComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.getName().compareTo(o2.getName());
}
}
public static class ScoreDescComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o2.getScore() - o1.getScore();
}
}
}
Collections.sort() 实现了对集合的排序。为了扩展性,它将其中比较大小,这部分逻辑,委派给用户来实现。如果我们把比较大小的这部分逻辑看作整个排序逻辑的其中一个步骤,那就可以把它看作模板模式。
但是,从代码实现的角度来看,是基于 Callback 回调机制实现的。
也有些资料中说, Collections.sort() 使用的是策略模式。这样的说法也不是没有道理的。如果把比较大小看作是排序逻辑中的一种算法或者策略,那么就可以把它看作一种策略模式的应用。
不过,这也不是典型的策略模式,在典型的策略模式中,策略模式分为策略的定义、创建、使用这三部分。策略通过工厂模式来创建,并且在程序运行期间,根据配置、用户输入、计算结果这些不确定因素,动态决定使用那种策略。
而在 Collections.sort() 函数中,策略的创建并非通过工厂模式,策略的模式也并非动态确定。
观察者模式在 JDK 中应用
Java JDK 提供了观察者模式的简单框架实现。在平时的开发中,如果不希望引入其它框架,可以直接使用 Java 语言本身提供的这个框架类。
Java Jdk 只包含两个类:java.util.Observable 和 java.util.Observer。 前者是被观察者,后者是观察者。
public interface Observer {
void update(Observable o, Object arg);
}
public class Observable {
private boolean changed = false;
private Vector<Observer> obs;
public Observable() {
obs = new Vector<>();
}
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}
public void notifyObservers() {
notifyObservers(null);
}
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
public synchronized void deleteObservers() {
obs.removeAllElements();
}
protected synchronized void setChanged() {
changed = true;
}
protected synchronized void clearChanged() {
changed = false;
}
}
对于 Observable、Observer 的代码实现,重点在于两个地方,一个是 changed 成员变量,另一个是 notifyObservers() 函数。
changed 成员变量
表明被观察者(Observable)有没有状态更新。当有状态更新时,需要手动调用setChanged() 函数,将 changed 置为 true。这样才能在调用 notifyObservers() 函数的时候,真正触发观察者(Observer)执行 update() 函数。 否则,即便你调用了 notifyObservers() 函数,update() 函数也不会被调用。
当通知观察者被观察者状态更新的时候,需要依次调用 setChanged() 和 notifyObservers() 两个函数,单独调用 notifyObservers() 函数是不起作用的。
notifyObservers() 函数
为了保证在多线程环境下,添加、移除、通知观察者三个操作之间不发生冲突,Observable 类中的大部分函数都通过 synchronized 加了锁, 不过,也有特例,notifyObservers() 者函数没有加 synchronized 锁。
notifyObservers() 函数是如何保证跟其它函数操作不冲突的呢?这种加锁方法是否存在问题?
notifyObservers() 函数之所以没有像其它函数那样,一把大锁加在整个函数上,主要还是处于性能的考虑。
notifyObservers() 函数依次执行每个观察者的 update() 函数,每个 update() 函数执行的逻辑提前未知,有可能会耗时。如果在 notifyObservers() 函数上加 synchronized 锁, notifyObservers() 函数持有锁的时间就有可能会很长,这样会导致其它线程迟迟获取不到锁,影响整个 Observable 类的并发性能。
Vector 类不是线程安全的,在多线程环境下,同时添加、删除、遍历 Vector 类对象中的元素,会出现不可预期的结果。
所以,在 JDK 的代码实现中,为了避免直接给 notifyObservers 函数加锁而出现性能问题,JDK 采用了一种折中方案。这个方案有点类似之前讲过的让迭代器支持 快照 的解决方案。
在 notifyObservers() 函数中,先拷贝一份观察者列表,赋值给函数的局部变量,局部变量是线程私有的,并不在线程间共享。这个拷贝出来的线程私有的观察者列表就相当于一个快照。
遍历快照,逐一执行每个观察者的 update() 函数。而这个遍历执行的过程是在快照这个局部遍历上操作的,不存在线程安全问题,不需要加锁。所以只需要对拷贝创建快照的过程枷锁,加锁的范围减少了很多,并发性能提高了。
为什么说这是一种折中的方案呢?这中加锁方法实际上是有一些问题的。在创建好快照之后,添加、删除观察者都不会更新快照,新加入的观察者就不会被通知到,新删除的观察者模式仍然会被通知到。
这种权衡是否能接受完全看你的业务场景。
单例模块在 Runtime 类种的应用
JDK 中 java.lang.Runtime 类就是一个单例类。之前的 Callback 回调,添加 shutdown hook 就是通过这个类来实现的。
每个 Java 应用在运行时启动一个 JVM 进程,每个 JVM 进程都只对应一个 Runtime 实例,用于查看 JVM 状态以及控制 JVM 行为。进程内唯一,所以比较适合设计为单例。在使用的时候, 通过 Runtime.getRuntime() 函数获取 Runtime 实例。
Runtime 类的代码实现如下所示。这里面只包含部分相关代码,其它代码做了省略。
/**
* Every Java application has a single instance of class
* <code>Runtime</code> that allows the application to interface with
* the environment in which the application is running. The current
* runtime can be obtained from the <code>getRuntime</code> method.
* <p>
* An application cannot create its own instance of this class.
*
* @author unascribed
* @see java.lang.Runtime#getRuntime()
* @since JDK1.0
*/
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
//....
public void addShutdownHook(Thread hook) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks"));
}
ApplicationShutdownHooks.add(hook);
}
//...
}