CategoryResourceRepost/极客时间专栏/设计模式之美/开源与项目实战:开源实战/76 | 开源实战一(上):通过剖析Java JDK源码学习灵活应用设计模式.md
louzefeng d3828a7aee mod
2024-07-11 05:50:32 +00:00

360 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<audio id="audio" title="76 | 开源实战一通过剖析Java JDK源码学习灵活应用设计模式" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ba/31/bac570414d9984d28fea9e761fa7f631.mp3"></audio>
从今天开始,我们就正式地进入到实战环节。实战环节包括两部分,一部分是开源项目实战,另一部分是项目实战。
在开源项目实战部分我会带你剖析几个经典的开源项目中用到的设计原则、思想和模式这其中就包括对Java JDK、Unix、Google Guava、Spring、MyBatis这样五个开源项目的分析。在项目实战部分我们精心挑选了几个实战项目手把手地带你利用之前学过的设计原则、思想、模式来对它们进行分析、设计和代码实现这其中就包括鉴权限流、幂等重试、灰度发布这样三个项目。
接下来的两节课我们重点剖析Java JDK中用到的几种常见的设计模式。学习的目的是让你体会在真实的项目开发中要学会活学活用切不可过于死板生搬硬套设计模式的设计与实现。除此之外针对每个模式我们不可能像前面学习理论知识那样分析得细致入微很多都是点到为止。在已经具备之前理论知识的前提下我想你可以跟着我的指引自己去研究有哪里不懂的话也可以再回过头去看下之前的理论讲解。
话不多说,让我们正式开始今天的学习吧!
## 工厂模式在Calendar类中的应用
在前面讲到工厂模式的时候大部分工厂类都是以Factory作为后缀来命名并且工厂类主要负责创建对象这样一件事情。但在实际的项目开发中工厂类的设计更加灵活。那我们就来看下工厂模式在Java JDK中的一个应用java.util.Calendar。从命名上我们无法看出它是一个工厂类。
Calendar类提供了大量跟日期相关的功能代码同时又提供了一个getInstance()工厂方法用来根据不同的TimeZone和Locale创建不同的Calendar子类对象。也就是说功能代码和工厂方法代码耦合在了一个类中。所以即便我们去查看它的源码如果不细心的话也很难发现它用到了工厂模式。同时因为它不单单是一个工厂类所以它并没有以Factory作为后缀来命名。
Calendar类的相关代码如下所示大部分代码都已经省略我只给出了getInstance()工厂方法的代码实现。从代码中我们可以看出getInstance()方法可以根据不同TimeZone和Locale创建不同的Calendar子类对象比如BuddhistCalendar、JapaneseImperialCalendar、GregorianCalendar这些细节完全封装在工厂方法中使用者只需要传递当前的时区和地址就能够获得一个Calendar类对象来使用而获得的对象具体是哪个Calendar子类的对象使用者在使用的时候并不关心。
```
public abstract class Calendar implements Serializable, Cloneable, Comparable&lt;Calendar&gt; {
//...
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(&quot;ca&quot;);
if (caltype != null) {
switch (caltype) {
case &quot;buddhist&quot;:
cal = new BuddhistCalendar(zone, aLocale);
break;
case &quot;japanese&quot;:
cal = new JapaneseImperialCalendar(zone, aLocale);
break;
case &quot;gregory&quot;:
cal = new GregorianCalendar(zone, aLocale);
break;
}
}
}
if (cal == null) {
if (aLocale.getLanguage() == &quot;th&quot; &amp;&amp; aLocale.getCountry() == &quot;TH&quot;) {
cal = new BuddhistCalendar(zone, aLocale);
} else if (aLocale.getVariant() == &quot;JP&quot; &amp;&amp; aLocale.getLanguage() == &quot;ja&quot; &amp;&amp; aLocale.getCountry() == &quot;JP&quot;) {
cal = new JapaneseImperialCalendar(zone, aLocale);
} else {
cal = new GregorianCalendar(zone, aLocale);
}
}
return cal;
}
//...
}
```
## 建造者模式在Calendar类中的应用
还是刚刚的Calendar类它不仅仅用到了工厂模式还用到了建造者模式。我们知道建造者模式有两种实现方法一种是单独定义一个Builder类另一种是将Builder实现为原始类的内部类。Calendar就采用了第二种实现思路。我们先来看代码再讲解相关代码我贴在了下面。
```
public abstract class Calendar implements Serializable, Cloneable, Comparable&lt;Calendar&gt; {
//...
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(&quot;ca&quot;);
}
if (type == null) {
if (locale.getCountry() == &quot;TH&quot; &amp;&amp; locale.getLanguage() == &quot;th&quot;) {
type = &quot;buddhist&quot;;
} else {
type = &quot;gregory&quot;;
}
}
switch (type) {
case &quot;gregory&quot;:
cal = new GregorianCalendar(zone, locale, true);
break;
case &quot;iso8601&quot;:
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 &quot;buddhist&quot;:
cal = new BuddhistCalendar(zone, locale);
cal.clear();
break;
case &quot;japanese&quot;:
cal = new JapaneseImperialCalendar(zone, locale, true);
break;
default:
throw new IllegalArgumentException(&quot;unknown calendar type: &quot; + 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) &amp;&amp; fields[WEEK_YEAR] &gt; fields[YEAR];
if (weekDate &amp;&amp; !cal.isWeekDateSupported()) {
throw new IllegalArgumentException(&quot;week date is unsupported by &quot; + type);
}
for (int stamp = MINIMUM_USER_STAMP; stamp &lt; nextStamp; stamp++) {
for (int index = 0; index &lt;= 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子类对象。
你可能会说,这还能算是建造者模式吗?我用[第46讲](https://time.geekbang.org/column/article/199674)的一段话来回答你:
>
我们也不要太学院派,非得把工厂模式、建造者模式分得那么清楚,我们需要知道的是,每个模式为什么这么设计,能解决什么问题。只有了解了这些最本质的东西,我们才能不生搬硬套,才能灵活应用,甚至可以混用各种模式,创造出新的模式来解决特定场景的问题。
实际上从Calendar这个例子我们也能学到不要过于死板地套用各种模式的原理和实现不要不敢做丝毫的改动。模式是死的用的人是活的。在实际上的项目开发中不仅各种模式可以混合在一起使用而且具体的代码实现也可以根据具体的功能需求做灵活的调整。
## 装饰器模式在Collections类中的应用
我们前面讲到Java IO类库是装饰器模式的非常经典的应用。实际上Java的Collections类也用到了装饰器模式。
Collections类是一个集合容器的工具类提供了很多静态方法用来创建各种集合容器比如通过unmodifiableColletion()静态方法来创建UnmodifiableCollection类对象。而这些容器类中的UnmodifiableCollection类、CheckedCollection和SynchronizedCollection类就是针对Collection类的装饰器类。
因为刚刚提到的这三个装饰器类在代码结构上几乎一样所以我们这里只拿其中的UnmodifiableCollection类来举例讲解一下。UnmodifiableCollection类是Collections类的一个内部类相关代码我摘抄到了下面你可以先看下。
```
public class Collections {
private Collections() {}
public static &lt;T&gt; Collection&lt;T&gt; unmodifiableCollection(Collection&lt;? extends T&gt; c) {
return new UnmodifiableCollection&lt;&gt;(c);
}
static class UnmodifiableCollection&lt;E&gt; implements Collection&lt;E&gt;, Serializable {
private static final long serialVersionUID = 1820017752578914078L;
final Collection&lt;? extends E&gt; c;
UnmodifiableCollection(Collection&lt;? extends E&gt; 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 &lt;T&gt; T[] toArray(T[] a) {return c.toArray(a);}
public String toString() {return c.toString();}
public Iterator&lt;E&gt; iterator() {
return new Iterator&lt;E&gt;() {
private final Iterator&lt;? extends E&gt; 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&lt;? super E&gt; 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&lt;?&gt; coll) {
return c.containsAll(coll);
}
public boolean addAll(Collection&lt;? extends E&gt; coll) {
throw new UnsupportedOperationException();
}
public boolean removeAll(Collection&lt;?&gt; coll) {
throw new UnsupportedOperationException();
}
public boolean retainAll(Collection&lt;?&gt; coll) {
throw new UnsupportedOperationException();
}
public void clear() {
throw new UnsupportedOperationException();
}
// Override default methods in Collection
@Override
public void forEach(Consumer&lt;? super E&gt; action) {
c.forEach(action);
}
@Override
public boolean removeIf(Predicate&lt;? super E&gt; filter) {
throw new UnsupportedOperationException();
}
@SuppressWarnings(&quot;unchecked&quot;)
@Override
public Spliterator&lt;E&gt; spliterator() {
return (Spliterator&lt;E&gt;)c.spliterator();
}
@SuppressWarnings(&quot;unchecked&quot;)
@Override
public Stream&lt;E&gt; stream() {
return (Stream&lt;E&gt;)c.stream();
}
@SuppressWarnings(&quot;unchecked&quot;)
@Override
public Stream&lt;E&gt; parallelStream() {
return (Stream&lt;E&gt;)c.parallelStream();
}
}
}
```
看了上面的代码请你思考一下为什么说UnmodifiableCollection类是Collection类的装饰器类呢这两者之间可以看作简单的接口实现关系或者类继承关系吗
我们前面讲过装饰器模式中的装饰器类是对原始类功能的增强。尽管UnmodifiableCollection类可以算是对Collection类的一种功能增强但这点还不具备足够的说服力来断定UnmodifiableCollection就是Collection类的装饰器类。
实际上最关键的一点是UnmodifiableCollection的构造函数接收一个Collection类对象然后对其所有的函数进行了包裹Wrap重新实现比如add()函数或者简单封装比如stream()函数。而简单的接口实现或者继承并不会如此来实现UnmodifiableCollection类。所以从代码实现的角度来说UnmodifiableCollection类是典型的装饰器类。
## 适配器模式在Collections类中的应用
在[第51讲](https://time.geekbang.org/column/article/205912)中我们讲到适配器模式可以用来兼容老的版本接口。当时我们举了一个JDK的例子这里我们再重新仔细看一下。
老版本的JDK提供了Enumeration类来遍历容器。新版本的JDK用Iterator类替代Enumeration类来遍历容器。为了兼容老的客户端代码使用老版本JDK的代码我们保留了Enumeration类并且在Collections类中仍然保留了enumaration()静态方法因为我们一般都是通过这个静态函数来创建一个容器的Enumeration类对象
不过保留Enumeration类和enumeration()函数,都只是为了兼容,实际上,跟适配器没有一点关系。那到底哪一部分才是适配器呢?
在新版本的JDK中Enumeration类是适配器类。它适配的是客户端代码使用Enumeration类和新版本JDK中新的迭代器Iterator类。不过从代码实现的角度来说这个适配器模式的代码实现跟经典的适配器模式的代码实现差别稍微有点大。enumeration()静态函数的逻辑和Enumeration适配器类的代码耦合在一起enumeration()静态函数直接通过new的方式创建了匿名类对象。具体的代码如下所示
```
/**
* Returns an enumeration over the specified collection. This provides
* interoperability with legacy APIs that require an enumeration
* as input.
*
* @param &lt;T&gt; 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 &lt;T&gt; Enumeration&lt;T&gt; enumeration(final Collection&lt;T&gt; c) {
return new Enumeration&lt;T&gt;() {
private final Iterator&lt;T&gt; i = c.iterator();
public boolean hasMoreElements() {
return i.hasNext();
}
public T nextElement() {
return i.next();
}
};
}
```
## 重点回顾
好了,今天的内容到此就讲完了。我们一块来总结回顾一下,你需要重点掌握的内容。
今天我重点讲了工厂模式、建造者模式、装饰器模式、适配器模式这四种模式在Java JDK中的应用主要目的是给你展示真实项目中是如何灵活应用设计模式的。
从今天的讲解中,我们可以学习到,尽管在之前的理论讲解中,我们都有讲到每个模式的经典代码实现,但是,在真实的项目开发中,这些模式的应用更加灵活,代码实现更加自由,可以根据具体的业务场景、功能需求,对代码实现做很大的调整,甚至还可能会对模式本身的设计思路做调整。
比如Java JDK中的Calendar类就耦合了业务功能代码、工厂方法、建造者类三种类型的代码而且在建造者类的build()方法中,前半部分是工厂方法的代码实现,后半部分才是真正的建造者模式的代码实现。这也告诉我们,在项目中应用设计模式,切不可生搬硬套,过于学院派,要学会结合实际情况做灵活调整,做到心中无剑胜有剑。
## 课堂讨论
在Java中经常用到的StringBuilder类是否是建造者模式的应用呢你可以试着像我一样从源码的角度去剖析一下。
欢迎留言和我分享你的想法。如果有收获,也欢迎你把这篇文章分享给你的朋友。