[Open Source and Project Combat: Open Source Combat] 76 | Open Source Combat 1 (Part 1): Learning Flexible Application Design Patterns by Analyzing Java JDK Source Code

Starting today, we will officially enter the actual combat stage. The actual combat session includes two parts, one is open source project actual combat, and the other is project actual combat.

In the actual part of open source projects, I will take you to analyze the design principles, ideas and patterns used in several classic open source projects, including five open source projects such as Java JDK, Unix, Google Guava, Spring, and MyBatis. analyze. In the actual combat part of the project, we have carefully selected several actual combat projects, and will take you through the design principles, ideas, and patterns you have learned before to analyze, design, and code them, including authentication, flow, and idempotent Three items such as retry and gray release.

In the next two lessons, we will focus on analyzing several common design patterns used in the Java JDK. The purpose of learning is to let you realize that in real project development, you must learn to learn and use it flexibly, and you must not be too rigid and mechanically copy the design and implementation of design patterns. In addition, for each mode, it is impossible for us to analyze in detail like the previous study of theoretical knowledge, and many of them are point-to-point. On the premise that you already have previous theoretical knowledge, I think you can follow my guidance to study on your own. If you don’t understand something, you can also go back and read the previous theoretical explanations.

Without further ado, let's officially start today's study!

Application of factory pattern in Calendar class

When we talked about the factory pattern earlier, most of the factory classes are named with Factory as the suffix, and the factory class is mainly responsible for creating objects. But in actual project development, the design of the factory class is more flexible. Then let's take a look at an application of the factory pattern in Java JDK: java.util.Calendar. From the name, we cannot see that it is a factory class.
The Calendar class provides a large number of date-related function codes, and at the same time, provides a getInstance() factory method to create different Calendar subclass objects according to different TimeZone and Locale. That is, the function code and the factory method code are coupled in one class. Therefore, even if we check its source code, if we are not careful, it is difficult to find that it uses the factory mode. At the same time, because it is not just a factory class, it is not named with Factory as a suffix.

The relevant code of the Calendar class is shown below, most of the code has been omitted, and I only give the code implementation of the getInstance() factory method. From the code, we can see that the getInstance() method can create different Calendar subclass objects according to different TimeZone and Locale, such as BuddhistCalendar, JapaneseImperialCalendar, GregorianCalendar, these details are completely encapsulated in the factory method, the user only needs to pass the current The time zone and address, you can get a Calendar class object to use, and the user doesn't care which Calendar subclass object the obtained object is.

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;
  }
  //...
}

Application of Builder Pattern in Calendar Class

Still just the Calendar class, it not only uses the factory pattern, but also uses the builder pattern. We know that there are two ways to implement the builder pattern, one is to define a Builder class separately, and the other is to implement the Builder as an inner class of the original class. Calendar adopts the second implementation idea. Let's look at the code first and then explain it. I posted the relevant code below.

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;
    }
  }
}

After reading the above code, I have a question for you to think about: Since there is already a getInstance() factory method to create Calendar class objects, why use Builder to create Calendar class objects? Where is the difference between the two?

In fact, when we talked about these two modes earlier, we made a detailed comparison of the differences between them. Now, let's review them together. The factory pattern is used to create different but related types of objects (a group of subclasses that inherit the same parent class or interface), and the given parameters determine which type of object to create. The builder pattern is used to create a type of complex object, and create different objects "customized" by setting different optional parameters.

There is a classic example on the Internet that explains the difference between the two very well.

A customer walks into a restaurant to order food. We use the factory model to make different foods, such as pizza, burgers, and salads, according to different choices of users. For pizza, users can customize various toppings, such as cheese, tomato, and cheese. We use the builder mode to make different pizzas according to different toppings selected by users.

Looking at the build() method of Calendar's Builder class, you might think it's a bit like a factory pattern. You feel right, the first half of the code is indeed similar to the getInstance() factory method, creating different Calendar subclasses according to different types. In fact, the latter half of the code belongs to the standard builder mode, which customizes the just-created Calendar subclass object according to the parameters set by the setXXX() method.

You might say, can this be considered a builder pattern? Let me answer you with a passage from Chapter 46:

We don't want to be too academic, we have to distinguish the factory mode and the builder mode so clearly. What we need to know is why each mode is designed in this way and what problems it can solve. Only by understanding these most essential things can we not apply mechanically, but can apply them flexibly, and even mix various modes to create new modes to solve problems in specific scenarios.

In fact, from the example of Calendar, we can also learn not to apply the principles and implementations of various modes too rigidly, and not dare to make slight changes. The model is dead, but the people who use it are alive. In actual project development, not only various modes can be mixed together, but also the specific code implementation can be flexibly adjusted according to specific functional requirements.

Application of Decorator Pattern in Collections Class

As we mentioned earlier, the Java IO class library is a very classic application of the decorator pattern. In fact, Java's Collections class also uses the decorator pattern.

The Collections class is a tool class for collection containers, which provides many static methods to create various collection containers, such as creating UnmodifiableCollection class objects through the unmodifiableCollection() static method. The UnmodifiableCollection, CheckedCollection and SynchronizedCollection classes in these container classes are the decorator classes for the Collection class.

Because the three decorator classes just mentioned are almost the same in code structure, so here we only take the UnmodifiableCollection class as an example to explain. The UnmodifiableCollection class is an internal class of the Collections class. I have copied the relevant code below, you can read it first.

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();
    }
  }
}

After reading the above code, please think about it, why is the UnmodifiableCollection class a decorator class of the Collection class? Can the two be regarded as a simple interface implementation relationship or a class inheritance relationship?

As we said earlier, the decorator class in the decorator pattern is an enhancement to the functionality of the original class. Although the UnmodifiableCollection class can be regarded as a functional enhancement of the Collection class, this is not convincing enough to conclude that the UnmodifiableCollection is a decorator class for the Collection class.

In fact, the most critical point is that the constructor of UnmodifiableCollection receives a Collection class object, and then wraps all its functions: reimplementation (such as add() function) or simple encapsulation (such as stream() function ). Simple interface implementation or inheritance does not implement the UnmodifiableCollection class in this way. Therefore, from the perspective of code implementation, the UnmodifiableCollection class is a typical decorator class.

Application of Adapter Pattern in Collections Class

In Lecture 51 we mentioned that the Adapter pattern can be used to be compatible with older versions of the interface. At that time, we gave an example of JDK, and here we will take a closer look.

Older versions of the JDK provided the Enumeration class to traverse containers. New versions of the JDK use the Iterator class instead of the Enumeration class to traverse containers. In order to be compatible with the old client code (using the code of the old version of JDK), we retain the Enumeration class, and in the Collections class, we still retain the enumaration() static method (because we usually use this static function to create a container Enumeration class object).

However, keeping the Enumeration class and the enumeration() function is just for compatibility, in fact, it has nothing to do with the adapter. So which part is the adapter?

In newer versions of the JDK, the Enumeration class is an adapter class. It adapts the client code (using the Enumeration class) and the new iterator Iterator class in the new version of JDK. However, from the perspective of code implementation, the code implementation of this adapter mode is slightly different from the code implementation of the classic adapter mode. The logic of the enumeration() static function is coupled with the code of the Enumeration adapter class. The enumeration() static function directly creates an anonymous class object through new. The specific code is as follows:

/**
 * 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();
    }
  };
}

key review

Well, that's all for today's content. Let's summarize and review together, what you need to focus on.

Today, I will focus on the factory pattern, builder pattern, decorator pattern, and adapter pattern. The application of these four patterns in Java JDK is mainly to show you how to flexibly apply design patterns in real projects.

From today's explanation, we can learn that although in the previous theoretical explanation, we have mentioned the classic code implementation of each mode, but in real project development, the application of these modes is more flexible, and the code implementation More freedom, you can make great adjustments to the code implementation according to specific business scenarios and functional requirements, and may even adjust the design ideas of the pattern itself.

For example, the Calendar class in Java JDK couples three types of codes: business function code, factory method, and builder class. Moreover, in the build method of the builder class, the first half is the code implementation of the factory method, and the second half is the code implementation of the factory method. Part is the real code implementation of the builder pattern. This also tells us that when applying design patterns in projects, we must not apply them mechanically, which is too academic, and we must learn to make flexible adjustments based on the actual situation, so that there is no sword in mind to win with a sword.

class disscussion

In Java, is the frequently used StringBuilder class an application of the builder pattern? You can try to analyze it from the perspective of source code like me.

おすすめ

転載: blog.csdn.net/qq_32907491/article/details/131352492
おすすめ