Detailed description of Java design patterns (1)

1. Observer Pattern 

Observer Pattern is a commonly used design pattern that defines a one-to-many dependency relationship that allows multiple observer objects to monitor a certain subject object at the same time. When a theme object changes, all its dependencies are notified and automatically updated.

In the observer pattern, there are two important roles, namely subject and observer. The topic maintains a list of observers and provides an interface for registering and deleting observers, as well as an interface for notifying observers. Observers define methods for receiving topic notifications so that the topic can notify observers when the state changes.

The advantage of using the observer pattern is that it can reduce the coupling between objects and make the interaction between objects more flexible. If we need to add a new observer, we only need to make it implement the Observer interface and register it in the topic. There is no need to modify the original code.

The observer pattern is widely used in actual development, such as the event listening mechanism in Java, the broadcast mechanism in Android, and so on.

Example

Below is an example of the Observer pattern with a news theme.

Suppose we are developing a news subscription system, and we need to subscribe to multiple news sources, which may come from different media, such as newspapers, television, the Internet, etc. When a news source publishes a new article, the system needs to automatically push the news content to subscribed users.

In order to achieve this function, we can use the observer pattern, in which the news source is the observer (the object being observed) and the subscriber is the observer (the object being observed).

In this system, when the news source publishes a new article, an event will be triggered. Subscribers will listen to this event and perform corresponding operations, that is, push the new article to subscribed users.

Here is a simple implementation:

Create the observer interface NewsPublisher and define the register, unregister and notifyObservers methods.

public interface NewsPublisher {
    void register(NewsSubscriber subscriber);
    void unregister(NewsSubscriber subscriber);
    void notifyObservers(String news);
}

Create the observer interface NewsSubscriber and define the update  method.

public interface NewsSubscriber {
    void update(String news);
}

Create a news source class Newspaper  that implements the NewsPublisher  interface for publishing news.

import java.util.ArrayList;
import java.util.List;

public class Newspaper implements NewsPublisher {
    private List<NewsSubscriber> subscribers = new ArrayList<>();

    public void addNews(String news) {
        System.out.println("Newspaper: " + news);
        notifyObservers(news);
    }

    @Override
    public void register(NewsSubscriber subscriber) {
        subscribers.add(subscriber);
    }

    @Override
    public void unregister(NewsSubscriber subscriber) {
        subscribers.remove(subscriber);
    }

    @Override
    public void notifyObservers(String news) {
        for (NewsSubscriber subscriber : subscribers) {
            subscriber.update(news);
        }
    }
}

Create multiple observer classes TVNewsSubscriber and WebNewsSubscriber  to implement the NewsSubscriber  interface for receiving news.

public class TVNewsSubscriber implements NewsSubscriber {
    private String name;

    public TVNewsSubscriber(String name) {
        this.name = name;
    }

    @Override
    public void update(String news) {
        System.out.println("TV News Subscriber " + name + ": " + news);
    }
}

public class WebNewsSubscriber implements NewsSubscriber {
    private String name;

    public WebNewsSubscriber(String name) {
        this.name = name;
    }

    @Override
    public void update(String news) {
        System.out.println("Web News Subscriber " + name + ": " + news);
    }
}

In the client program, instantiate Newspaper  and add subscribers to it, and then call the addNews  method to publish news.

package org.example.observer;

public class Test {
    public static void main(String[] args) {

        Newspaper newspaper = new Newspaper();

        newspaper.register( new WebNewsSubscriber("张三"));
        newspaper.register(new WebNewsSubscriber("李四"));
        newspaper.register(new WebNewsSubscriber("王五"));

        newspaper.addNews("1111");


    }
}

Application scenarios

Java's observer pattern is mainly used in the following scenarios:

when the state of an object changes, all objects that care about this object will receive notifications and automatically update.

An object will change many times, and the objects that need to care about it can be updated automatically.

An abstract model has two aspects, one of which depends on the other, which can be solved using the Observer pattern.

In Java, the observer pattern is often used in graphical user interface (GUI) systems. For example, when a button is clicked, the state of the interface needs to be updated. The observer pattern is also often used to implement event-driven systems, such as Swing's event-driven mechanism.

life examples

The Observer pattern is a design pattern whose purpose is to establish a one-to-many dependency relationship between objects so that when the state of an object changes, all objects that depend on it can be notified and automatically updated. Here are some examples of the Observer pattern in life:

Subscription services: When you subscribe to a service, such as a mailing list or social media notifications, you become an observer. You will receive notifications when new content is available on the service.

Observing the weather: When you are observing the weather, the weather itself is the one being observed, and you are the observer. When the weather changes, you'll receive notifications.

Stock trading: When you invest in a stock, you may use a stock ticker to watch its price. This software is an observer and you will be notified when the stock price changes.

In-game tasks: In games, tasks usually have a status, such as In Progress or Completed. When you complete a task, this status changes and the game interface updates accordingly.

These are some common examples of the Observer pattern in life.

2. Singleton mode

The singleton pattern is a common creational design pattern that restricts a class to create only one object instance. In the singleton pattern, only one object instance of a class exists, and this object can be accessed by all other objects in the system.

The singleton pattern is typically used when you need to control a resource, such as when creating a database connection, thread pool, or cache object. In these scenarios, the singleton pattern can ensure that only one object instance exists in the system, thus avoiding resource waste and conflicts.

The singleton pattern can be implemented in many different ways, including lazy style, hungry style, double-checked locking, static inner classes, enumerations, etc. Each implementation method has its advantages and disadvantages, and the appropriate implementation method needs to be selected based on specific demand scenarios.

1. Hungry-style singleton mode

The Hungry-style singleton mode is the simplest singleton mode, simple to implement and thread-safe. Instantiation is completed when the class is loaded, so it is very fast when called, but lazy loading cannot be achieved.

A private, immutable Singleton  instance is created when the class is loaded. Only one instance will exist at any time, and there will be no concurrency, so there will be no thread safety issues. At the same time, since the instance  is declared final ,  there will be no reordering problem when obtaining the instance , ensuring thread safety.

public class Singleton {
    private static final Singleton instance = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() {
        return instance;
    }
}

2. Lazy singleton mode (thread unsafe)

The lazy singleton mode  is instantiated only when the getInstance() method is called, achieving lazy loading, but it is thread unsafe in a multi-threaded environment.

This singleton mode is unsafe in a multi-threaded environment because it does not consider thread safety. When multiple threads call the getInstance()  method at the same time, multiple instances may be created.

For example, thread A and thread B call the getInstance()  method at the same time. At this time, instance  is null . When A executes instance = new Singleton();,  since the initialization has not been completed, thread B will also enter  the judgment of instance == null. , and a new instance will also be created. This will result in the creation of multiple instances.

public class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

In order to solve the thread insecurity problem of lazy singleton mode,  thread safety can be achieved by adding the synchronized keyword to the getInstance()  method . But this approach is inefficient because you need to acquire the lock every time you get an instance.

This code is thread-safe because the synchronized keyword is used on the getInstance() method, which ensures that only one thread can access this method at the same time. If multiple threads call the getInstance() method at the same time in a multi-threaded environment, only one thread can execute the line of code instance = new Singleton() due to the synchronized keyword, ensuring that only one instance is created.

public class Singleton {    
private static Singleton instance = null;    
private Singleton() {}    
public static synchronized Singleton getInstance() {       
if (instance == null) {            
instance = new Singleton();        
}       
 return instance;    
}
}

3. Double check lock singleton mode

The double-checked locking singleton mode implements lazy loading and thread safety through locking and double judgment.

This code uses the "double-check lock" mechanism, that is, the first time it checks whether the object instance exists, it will only obtain the lock and create the instance if it does not exist. Because the synchronized block uses the class object Singleton.class as the lock object, it is guaranteed that only one thread can enter and create an instance in the synchronized block. At the same time, the volatile keyword is used to ensure visibility in a multi-threaded environment. That is, if one thread modifies the value of instance, other threads can immediately see the change in this value, thereby avoiding dirty reads. Therefore, this code is thread-safe.

public class Singleton {
    private static volatile Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

4. Static inner class singleton pattern

This method uses the static inner class of the class to implement the singleton mode, which has the advantage of thread safety. The static inner class of the class will only be loaded once when the class is loaded. At the same time, the static inner class will not be initialized when the singleton class is loaded. It will only be initialized when the method in the static inner class is called, and static initialization will only Executed once, thus ensuring thread safety. In addition, since SingletonHolder is a private static inner class, it cannot be accessed from the outside, which also ensures the uniqueness of the singleton.

public class Singleton {
    private Singleton() {}
    private static class SingletonHolder {
        private static final Singleton instance = new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
}

5. Enumeration singleton mode

The enumeration singleton mode is a more concise implementation method. The enumeration constant will only be initialized once, so the singleton mode can be implemented.

这个实现方式是使用了 Java 的枚举类型来实现单例模式,而 Java 的枚举类型保证了在任何情况下都只会有一个实例。因此这种实现方式是线程安全的。枚举类型的实现方式是由 JVM 内部保证线程安全的。在 Java 中,枚举类型是天然的单例模式,且枚举类型在多线程环境下也是线程安全的。

public enum Singleton {
    INSTANCE;
    public Singleton2 getInstance() {
        return Singleton2.getInstance();
    }
}

说明

1. 私有构造函数`Singleton()`:防止外部直接实例化该类,仅允许通过`getInstance()`方法获取该类的唯一实例。
2. 静态变量`instance`:该类的唯一实例。
3. 静态方法`getInstance()`:获取该类的唯一实例。如果`instance`为空,则使用双重检查锁定确保实例化时的线程安全。
4. 关键字`volatile`:确保多线程下的变量同步,保证内存可见性。
5. 同步锁`synchronized (Singleton.class)`:保证实例化时的线程安全,确保在多线程下只有一个线程能够进行实例化。

应用场景

Java单例模式通常在以下场景中使用:

1. 全局配置管理:比如说,整个应用程序只需要一个全局配置管理类,那么可以使用单例模式。
2. 缓存管理:如果应用程序需要维护一个全局的缓存,那么也可以使用单例模式。
3. 日志管理:如果一个应用程序需要持久化存储日志信息,那么也可以使用单例模式。
4. 全局对象的创建:如果整个应用程序只需要一个对象,那么可以使用单例模式。比如说,一个数据库连接池对象。
5. 资源共享:如果多个线程需要共享一个资源,那么可以使用单例模式。

总的来说,单例模式适用于需要维护唯一实例的场景,比如说,全局配置管理、缓存管理、日志管理、全局对象的创建、资源共享等。

生活实例

Plaintext
比如说,你现在正在烤肉,烤肉需要一个烤炉。在这里,烤炉就可以看作是单例模式中的单例对象,因为在整个烤肉过程中,你只需要使用一个烤炉。如果你每次烤肉都建立一个新的烤炉,那么很明显是不必要的,这样只会浪费时间和空间,而且也不利于烤肉效果的统一。因此,我们只需要使用一个烤炉即可。

再比如说,你在做一份报告,需要用到一个打印机。同样地,打印机也可以看作是单例模式中的单例对象。因为在整个报告处理过程中,你只需要使用一个打印机,如果每次都建立一个新的打印机,这样显然是不必要的。因此,我们只需要使用一个打印机即可。

Guess you like

Origin blog.csdn.net/qq_18235445/article/details/129313330