如果大家觉得文章有错误内容,欢迎留言或者私信讨论~
在真实的项目开发中,对于设计模式要学会活学活用,切不可死记硬背,生搬硬套设计模式的设计与实现。需要了解到:
当你接手某一项任务,首先要分析这项任务的痛点在哪里,比如可读性不好、可拓展性不好,然后再针对性的使用某一个设计模式看看是否合适。而不是想着这次任务与书中的某个案例相同就套用上去,切记不可过度设计。
设计模式常用的应用场景就是对代码进行解耦,可以说设计模式就是用来解决复杂代码问题而产生的。对于复杂代码,比如项目代码量多、开发周期长、参与开发的人员多,我们前期要多花点时间在设计上,越是复杂代码,花在设计上的时间就要越多。相反,如果你参与的只是一个简单的项目,代码量不多,开发人员也不多,那简单的问题用简单的解决方案就好,不要引入过于复杂的设计模式,将简单问题复杂化,切记不可过度设计。
引入设计模式虽然会提高代码的可拓展性,实际上还会降低代码的可读性,一旦我们引入某个复杂的设计,之后即便在很长一段时间都没有扩展的需求,那么就需要背负这个复杂的设计一路走下去。为了避免这个错误,持续的重构非常重要——当对要不要应用某种设计模式感到模棱两可的时候,你可以思考一下,如果暂时不用这种设计模式,随着代码的演进,当某一天不得不去使用它的时候,重构的代码是否很大,切记不可过度设计。
所以为了加深自己对设计模式的应用场景与时机的了解,借用 JDK 源码去体会,在真实的项目开发中,要学会活学活用,切不可过于死板,生搬硬套设计模式的设计与实现,这点非常重要(开发前 2 年必须养成能够写一手适合易懂的能力)。
话不多说,让我们开始吧。
通常映像里的工厂模式都是以 Factory
作为后缀来命名的,并且主要负责创建对象这样一件事情。但在实际的项目开发中,工厂类的设计更加灵活。那我们就来看下,工厂模式在 Java JDK 中的一个应用:java.util.Calendar。看命名我们无法猜测它是一个工厂类。
Calendar 类提供了大量跟日期相关的功能代码,同时,又提供了一个 getInstance() 工厂方法,用来根据不同的 TimeZone 和 Locale 创建不同的 Calendar 子类对象。也就是说,功能代码和工厂方法代码耦合在了一个类中。
Calendar 类的相关代码如下所示,大部分代码都已经省略,从代码中,我们可以看出,getInstance() 方法可以根据不同TimeZone 和 Locale,创建不同的 Calendar 子类对象,比如 BuddhistCalendar、JapaneseImperialCalendar、GregorianCalendar,这些细节完全封装在工厂方法中,使用者只需要传递当前的时区和地址,就能够获得一个 Calendar 类对象来使用,而获得的对象具体是哪个 Calendar 子类的对象,使用者在使用的时候并不关心。
public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar> {
// .....
public static Calendar getInstance() {
return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
}
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 中还有建造者模式的应用,将 Builder 实现为原始类的内部类,我们先来看一下代码的实现:
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);
}
// Set the fields from the min stamp to the max stamp so that
// the fields resolution works in the Calendar.
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;
}
}
}
这里虽然工厂模式和建造者模式都是创建类型的设计模式——用来创建对象。但是工厂模式一般都是用来创建不同但类型相关的对象(比如继承同一父类或者接口的一组子类),而建造者模式则是用来“定制化”创建复杂对象的一种手段。
可能建造者模式前半段代码与工厂模式十分相近,后段才是标准的建造者模式的代码。但这里是想提醒大家,不要过于学院派,设计模式的核心在于设计原则这套心法
,甚至有时候都可以为了业务而违背设计原则。灵活运用,甚至适当的混用各种模式,根据具体的功能需求做灵活的调整。
除去之前文章讲过的 Java IO 类运用过装饰器模式,Collections 也是运用了这一设计模式。Collections 类是一个集合容器的工具类,提供了很多静态方法,用来创建各种集合容器,比如通过 unmodifiableColletion() 静态方法,来创建 UnmodifiableCollection 类对象。而这些容器类中的 UnmodifiableCollection 类、CheckedCollection 和 SynchronizedCollection 类,就是针对 Collection 类的装饰器类。
提到的这三类 Collection 代码结构上几乎一样,所以,只拿其中的UnmodifiableCollection 类来举例讲解一下,下面摘抄了部分代码:
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;
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) {
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
@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 类是典型的装饰器类。
尽管在之前的理论讲解中,都会了解到每个模式的经典代码实现,但是,在真实的项目开发中,这些模式的应用更加灵活,代码实现更加自由,可以根据具体的业务场景、功能需求,对代码实现做很大的调整,甚至还可能会对模式本身的设计思路做调整。