Location>code7788 >text

Interviewer: what design patterns are used in JDK?

Popularity:640 ℃/2024-08-15 15:57:20

Design patterns are solutions summarized by previous generations after practical verification, helping us to build more maintainable, extensible and readable code. Of course, in the interview process, will be asked more or less. So today, let's look at a design pattern in the common interview questions: JDK are used in which design patterns?

I have summarized the design patterns used in the JDK in a way that is familiar and understandable, as shown below:

So, let's look at them one by one next.

1. The singleton model

The singleton pattern ensures that there is only one instance of a class and provides a global access point.

The Runtime class uses the singleton pattern, as can be seen in the source code below:

public class Runtime {
    private static final Runtime currentRuntime = new Runtime();
    private static Version version;
    /**
     * Returns the runtime object associated with the current Java application.
     * Most of the methods of class {@code Runtime} are instance
     * methods and must be invoked with respect to the current runtime object.
     *
     * @return the {@code Runtime} object associated with the current
     * Java application.
     */
    public static Runtime getRuntime() {
        return currentRuntime;
    }
    /** Don't let anyone else instantiate this class */
    private Runtime() {}
    // Omit other source code
}

As you can see from the source code above, the starvation approach used by Runtime implements the singleton pattern.

2. Factory model

The factory pattern provides a way to encapsulate the process of object creation in a single class, which is the factory class.

All threads in the thread pool are created through the factory, using the factory pattern, the specific source code is as follows:

3. Proxy model

The proxy pattern is a design pattern that provides a proxy for other objects to control access to that object. The proxy object acts as an intermediary between the client and the target object and can remove content and services that are not visible to the client or add additional services that the client needs.

The JDK has built-in functionality for dynamic proxies, which are an implementation of the proxy model that is provided by classes.

The Proxy usage demo is as follows:

import ;
import ;
import ;

// 1.connector
interface Subject {
    void doSomething();
}

// 2.target class(surrogate class (math.))
class RealSubject implements Subject {
    @Override
    public void doSomething() {
        ("RealSubject is doing something");
    }
}

// 3.dynamic agent class (computing)
class DynamicProxyHandler implements InvocationHandler {
    private Object target;
    DynamicProxyHandler(Object target) {
         = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        ("Before calling method");
        Object result = (target, args);
        ("After calling method");
        return result;
    }
}

public class JDKProxyDemo {
    public static void main(String[] args) {
        // Creating Real Objects
        Subject realSubject = new RealSubject();
        // Creating a Dynamic Proxy Processor
        InvocationHandler handler = new DynamicProxyHandler(realSubject);
        // Creating Proxy Objects
        Subject proxySubject = (Subject) (
            ().getClassLoader(),
            ().getInterfaces(),
            handler);
        // Calling Methods on Proxy Objects
        ();
    }
}

4. Iterator pattern

The iterator pattern canProvides a simple way to iterate through each element in a container. With iterators, users can easily access all the elements in the container, simplifying the programming process.

Iterable is the standard iterator pattern, Collection is a subclass of Iterator, which is used in the following code:

import ;
import ;

public class IteratorDemo {
    public static void main(String[] args) {
        // Create a ArrayList and add the element
        ArrayList<String> list = new ArrayList<>();
        ("Apple");
        ("Banana");
        ("Orange");

        // Get Iterator
        Iterator<String> iterator = ();

        // Iterating over collections using iterators
        while (()) {
            String fruit = ();
            ("Fruit: " + fruit);
        }
    }
}

5. Template approach model

The Template Method Pattern (TMP) defines the skeleton of an algorithm in an operation, deferring some steps to subclasses for implementation. Template methods enable subclasses to redefine certain steps in an algorithm without changing the structure of the algorithm.

In AQS (AbstractQueuedSynchronizer), the require and release methods use the template method pattern.

These methods are considered template method patterns because they define the basic framework or flow of an operation, but some of the key steps in them are designed as abstract methods, left to subclasses to implement concretely.

Take the acquire method as an example, its general flow includes trying to acquire resources, adding the current thread to the waiting queue if the acquisition fails, blocking the thread and other steps. But how to determine whether the resource can be acquired (by calling the tryAcquire method), as well as some details of the handling of the acquisition failure, is to be realized by the subclasses, the specific source code is as follows:

protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}

For example, the AQS-based implementation of ReentrantLock overrides the tryAcquire method with the following source code:

6. Decorator pattern

The decorator pattern is a design pattern that dynamically adds additional functionality to an object without modifying the original object.

BufferedInputStream is a typical decorator pattern, when using ordinary InputStream to read data, each time may be the actual I/O operations, while BufferedInputStream will first read a part of the data into the buffer, subsequent read operations can be directly from the buffer to get, reducing the actual I/O times.

For example, the following code:

InputStream inputStream = new FileInputStream("");
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);

BufferedInputStream doesn't change the basic structure and interface of FileInputStream, it just provides a new interface for it.Added buffering features

7. Strategy Mode

The strategy pattern defines a set of interchangeable algorithms and encapsulates each of them so that they are interchangeable.

Comparator is a typical example of the strategy pattern. The Comparator interface defines a method compare(T o1, T o2) that compares two objects. This interface allows the user to define different comparison strategies, giving us the flexibility to change the sorting or comparison logic.

For example, the following sample code:

import ;
import ;
import ;

public class StrategyPatternExample {
    static class Person {
        private String name;
        private int age;
        // neglect Setter、Getter and other methods
    }
    // Listed in ascending order of age
    static class AgeComparator implements Comparator<Person> {
        @Override
        public int compare(Person p1, Person p2) {
            return ((), ());
        }
    }
    // Sort by name in descending order
    static class NameDescendingComparator implements Comparator<Person> {
        @Override
        public int compare(Person p1, Person p2) {
            return ().compareTo(());
        }
    }
    public static void main(String[] args) {
        ArrayList<Person> people = new ArrayList<>();
        (new Person("Alice", 30));
        (new Person("Bob", 25));
        (new Person("Charlie", 35));

        // Strategies for Using Age Ascending
        (people, new AgeComparator());

        // Strategies for using name descending
        (people, new NameDescendingComparator());
    }
}

8. Builder model

The builder pattern is a creation-based design pattern for creating complex objects in a series of steps. It separates the process of building an object from its representation, allowing the same building process to create different representations.

Common examples of using the builder pattern in the JDK are the StringBuilder and StringBuffer classes.

While the two classes themselves are not implementations of the builder pattern in the traditional sense (since the builder pattern is often used to build different representations or different parts of the same object), they provide a way of chaining calls to build and modify strings, which in a way embodies the idea of the builder pattern.

For example, the following code:

public class StringBuilderDemo {
    public static void main(String[] args) {
        // utilization StringBuilder Constructing and modifying strings
        StringBuilder builder = new StringBuilder();
        ("Hello")
        .append(", ")
        .append("world")
        .append("!")
        .insert(7, "beautiful ")
        .deleteCharAt(13);

        // Output constructed and modified strings
        (());
        // exports: Hello, beautiful world!  
    }  
}

StringBuilder builds and modifies strings incrementally by chaining calls to the append, insert and deleteCharAt methods. This approach makes the process of building and modifying strings more fluid and easier to read.

Post-lesson Reflections

What design patterns are used in Spring?

This article has been included in my interview mini-site, which contains modules such as Redis, JVM, Concurrency, Concurrency, MySQL, Spring, Spring MVC, Spring Boot, Spring Cloud, MyBatis, Design Patterns, Message Queuing and more.