设计模式学习笔记 - 开源实战一(上):通过剖析JDK源码学习灵活应用设计模式

devtools/2024/9/25 23:18:22/

工厂模式在 Calendar 类中的应用

在前面讲到工厂模式的时候,大部分工厂类都是以 Factory 作为后缀来命名,并且工厂类主要负责创建对象这样一件事情。但在实际的项目开发中,工厂类的设计更加灵活。我们来看下,工厂模式在 Java JDK 中的一个应用: java.util.Calendar。从命名上,我们无法看出它是一个工厂类。

Calendar 类提供了大量跟日期相关的功能代码,同时,又提供了一个 genInstance() 工厂方法,迎来根据不同的 TimeZoneLocal 创建不同的 Calendar 子类对象。也就是说,功能代码和工厂方法代码耦合在了一个类中。所以,即便我们去查看它的源码,如果不细心的话,也很难发现它用到了工厂模式。同时,因为它不单单是一个工厂类,所以它并没有以 Factory 作为后缀来命名。

Calendar 类的相关代码如下所示,大部分代码都已经省略,只给出了 genInstance() 工厂方法的代码实现。从代码中,可以看出,genInstance() 方法可以根据不同的 TimeZoneLocal ,创建不同的 Calendar 子类对象,比如 BuddhistCalendarJapaneseImperialCalendarGregorianCalendar,这些细节完全封装在工厂方法中,使用者只需要传递时区和地址,就能够获得一个 Calendar 类对象来使用,而获得的对象具体是哪个 Calendar 子类的对象,使用者在使用的时候也并不关心。

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 no known calendar type is explicitly specified,// perform the traditional way to create a Calendar:// create a BuddhistCalendar for th_TH locale,// a JapaneseImperialCalendar for ja_JP_JP locale, or// a GregorianCalendar for any other locales.// NOTE: The language, country and variant strings are interned.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;}// ...
}

建造者模式在 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; // +1 for WEEK_YEARprivate static final int WEEK_YEAR = FIELD_COUNT;private long instant;// Calendar.stamp[] (lower half) and Calendar.fields[] (upper half) combinedprivate int[] fields;// Pseudo timestamp starting from MINIMUM_USER_STAMP.// (COMPUTED is used to indicate that the instant has been set.)private int nextStamp;// maxFieldIndex keeps the max index of fields which have been set.// (WEEK_YEAR is never included.)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;}// 省略多个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 Gregoriangcal.setGregorianChange(new Date(Long.MIN_VALUE));// and week definition to be compatible with ISO 8601setWeekDefinition(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;}// ...}// ...
}

看饿了上面的代码,有一个问题请你思考下:既然有了 genInstance() 工厂方法来创建 Calendar 类,为什么还要用 Builder 来创建 Calendar 类呢?这两者的区别在哪里呢?

实际上,前面讲到这两种模式时,我们对它们之间的区别做了详细的对比,现在我们再来一起回顾下。工厂模式是用来创建不同但相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。建造者模式用来创建一种类型的复杂对象,通过设置不同的可选参数,“定制化” 地创建不同的对象。

网上有一个经典的例子:

顾客走进一家餐馆,利用工厂模式,根据用户不同的选择,来制作不同的食物,比如披萨、汉堡、沙拉。对于披萨来说,用户又有各种配料可以定制,比如奶酪、西红柿、起司,我们通过建造者模式根据用户选择的不同配料来制作不同的披萨。

粗看,Calendar 类的 Builder 类的 build() 方法,你可能会觉得它有点像工厂模式。你的感觉没错,前面一般的代码其实跟 genInstance() 工厂方法类似,根据不同的 setXXX() 方法设置的参数,来定制化刚刚创建的 Calendar 子类对象。

你可能会说,这还能算式建造者模式吗?我用建造者模式章节中的一段话来回答你。

实际上,我们没必要非得把工厂模式、建造者模式分得那么清楚,我们需要知道的是,每个设计模式为什么这么设计,能解决什么问题。只有了解了这些最本质的东西,我们才能不生搬硬套,才能灵活应用,甚至可以混用各种模式创造出新的模式,来解决特定场景的问题。

实际上,从上面的例子,我们要能学到,不要过于死板地套用各种模式的原理和实现,不要不敢做丝毫的改动。模式是死的,人是活的。在实际的项目开发中,不仅各种模式可以混合在一起使用,而且具体的代码实现,也可以根据具体的功能需求来做灵活的调整。

装饰器模式在 Collections 类中的应用

在装饰器模式章节讲到,Java IO 类库是装饰器模式的非常经典的应用。实际上,Java 的 Collections 类也用到的装饰器模式。

Collections 是一个集合容器的工具类,提供了很多静态方法,用来创建各种集合容器,比如通过 unmodifiableCollection() 静态方法,来创建 UnmodifiableCollection 类对象。而这些容器类中的 UnmodifiableCollection 类、CheckedCollectionSynchronizedCollection 类,就是针对 Collection 类的装饰器类。

因为刚刚提到的这三个装饰器类,在代码结构上几乎一样,所以,我们这里只拿 UnmodifiableCollection 类来举例讲解下。UnmodifiableCollection 类是 Collections 类的一个内部类,相关代码如下所示。

public class Collections {// Suppresses default constructor, ensuring non-instantiability.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();}@Overridepublic void forEachRemaining(Consumer<? super E> action) {// Use backing collection versioni.forEachRemaining(action);}};}public boolean add(E e) {throw new UnsupportedOperationException();}public boolean remove(Object o) {throw 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@Overridepublic void forEach(Consumer<? super E> action) {c.forEach(action);}@Overridepublic boolean removeIf(Predicate<? super E> filter) {throw new UnsupportedOperationException();}@SuppressWarnings("unchecked")@Overridepublic Spliterator<E> spliterator() {return (Spliterator<E>)c.spliterator();}@SuppressWarnings("unchecked")@Overridepublic Stream<E> stream() {return (Stream<E>)c.stream();}@SuppressWarnings("unchecked")@Overridepublic Stream<E> parallelStream() {return (Stream<E>)c.parallelStream();}}// ...
}

看了上面的代码,请你思考下,为甚说 UnmodifiableCollection 类是 Collection 类的装饰器类呢? 这两者时间可以看做简单的接口实现关系或者继承关系吗?

前面讲过,装饰器模式中的装饰器类是对原始类功能的增强。尽管 UnmodifiableCollection 类可以算式对 Collection 类的一种增强,但这点还不具备足够的说服力来断定 UnmodifiableCollection 就是 Collection 类的装饰器类。

实际上,关键的一点是, UnmodifiableCollection 的构造函数结构一个 Collection 类对象,然后对其所有的函数进行了包裹(Wrap):重新实现(比如 add() 函数)或者简单封装(比如 stream() 函数)。而简单的接口实现或继承,并不会如此来实现 UnmodifiableCollection 类。所以,从代码实现的角度来说,UnmodifiableCollection 类是典型的装饰器类。

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

在适配器模式章节中我们讲到,适配器模式可以用来兼容老的版本接口。当时我们举了一个 JDK 的例子。

老版本的 JDK 提供了 Enumeration 类来遍历容器。新版本的 JDK 用 Iterator 类替代 Enumeration 。为了兼容老的客户端代码(使用老版本的 JDK 的代码),我们保留了 Enumeration 类,并且在 Collections 中仍然保留了 enumaration() 方法(因为我们一般是通过这个静态函数来创建一个容器的 Enumeration 类对象)。

不过,保留 Enumerationenumaration() 方法,都只是为了兼容,实际上,跟适配器没有一点关系。到底哪一部分才是适配器呢?

在新版本的 JDK 中,Enumeration 类是适配器类。它适配客户端代码(使用 Enumeration 类)和新版本 JDK 中新的迭代器 Iterator 类。不过,从代码实现的角度来说,这个适配器模式的代码实现,跟经典的适配器模式的代码实现,差别稍微有点大。enumaration() 静态函数的逻辑和 Enumeration 适配器类的代码耦合在一起,enumaration() 静态函数直接通过 new 的方式创建了 匿名类对象。具体代码如下所示:

public interface Enumeration<E> {boolean hasMoreElements();E nextElement();
}public class Collections {// ...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 中的应用,主要目的是给你展示真实项目中是如何灵活应用设计模式的。

在本章的讲解中,我们可以学习到,尽管在之前的理论讲解中,都讲到了每个设计模式的经典代码实现,但是,在真实项目中,这些模式的应用更加灵活,代码实现更加自由,可以根据具体的业务场景、功能需求,对代码实现做很大的调整,甚至还可能回对设计模式本身的设计思路做调整。

比如,JDK 中的 Calendar 类,就耦合了业务功能代码、工厂方法、建造者这三种类型的代码,并且,在建造者类的 build() 方法中,前半部分是工厂方法的代码实现,后半部分才是建造者模式的代码实现。这也高速我们,在项目中应用设计模式,切不可生搬硬套,过于学院派,要学会结合实际情况做灵活调整,做到心中无剑胜有剑。


http://www.ppmy.cn/devtools/6075.html

相关文章

web自动化系列-selenium 的鼠标操作(十)

对于鼠标操作 &#xff0c;我们可以通过click()方法进行点击操作 &#xff0c;但是有些特殊场景下的操作 &#xff0c;click()是无法完成的 &#xff0c;比如 &#xff1a;我想进行鼠标悬停 、想进行鼠标拖拽 &#xff0c;怎么办 &#xff1f; 这个时候你用click()是无法完成的…

经济学人早操练(1)

hello大家好&#xff0c;周末给大家奉献一波“大”的。2024年4月13日经济学人Leader板块的文章&#xff0c;题目是《The next housing disaster》&#xff0c;当时看到这个题目的时候也是吓得一蹦啊&#xff0c;啥&#xff1f;我们的房子还有灾难&#xff0c;球球啊&#xff0c…

K8s的亲和、反亲和、污点、容忍

1 亲和与反亲和 亲和性的原理其实很简单&#xff0c;主要利用label标签结合nodeSelector选择器来实现 1.1 Pod和Node 从pod出发&#xff0c;可以分成亲和性和反亲和性&#xff0c;分别对应podAffinity和podAntiAffinity。从node出发&#xff0c;也可以分成亲和性和反亲和性&…

06 MapStream递归

Collections(集合工具类) 可变参数 就是一种特殊形参&#xff0c;定义在方法、构造器的形参列表里&#xff0c;定义格式是&#xff1a;方法名(数据类型… 形参名称){ } 可变参数的特点和好处 **特点&#xff1a;**可以不传数据给它&#xff1b;可以传一个或者同时传多个数据给…

深度学习基础——卷积神经网络的基础模块

深度学习基础——卷积神经网络的基础模块 卷积神经网络&#xff08;Convolutional Neural Networks&#xff0c;CNN&#xff09;是深度学习中一种非常重要的神经网络结构&#xff0c;它在图像识别、图像分类、目标检测等领域取得了巨大成功。本文将介绍卷积神经网络的几个基础…

Java+springboot开发的医院智能导诊服务系统源码 自动兼容小程序与H5版本

智能导诊系统 一、什么是智慧导诊系统&#xff1f; 智慧导诊系统是一种医院使用的引导患者自助就诊挂号、精准推荐科室、引导患者挂号就诊的系统。该系统结合医院挂号及就诊的HIS系统&#xff0c;为患者带来全流程的信息指引提醒&#xff0c;可以在全院区构建一个精细化、移动…

微信小程序前端获取OpenID和session_key

微信小程序前端获取OpenID和session_key code2Session: https://api.weixin.qq.com/sns/jscode2session?appidAPPID&secretSECRET&js_codeJSCODE&grant_typeauthorization_code wx.login({success: (res) > {console.log(res.code) //拿到codeuni.request({u…

为什么Redis使用单线程 性能会优于多线程?

前言 在计算机领域&#xff0c;性能一直都是一个关键的话题。无论是应用开发还是系统优化&#xff0c;我们都需要关注如何在有限的资源下&#xff0c;实现最大程度的性能提升。Redis&#xff0c;作为一款高性能的开源内存数据库&#xff0c;因其出色的单线程性能而备受瞩目。那…