Java常用设计模式

单例模式

单例模式就是: 在程序运行期间, 某些类有且最多只有一个实例对象

饿汉模式(静态常量)

饥饿模式又称为饿汉模式, 指的是JVM在加载类的时候就完成类对象的创建

java 复制代码
//饿汉式(静态常量)
public class Singleton1 {
    //构造器私有化,外部不能new
    private Singleton1() {}
    //本类创建对象实例
    private final static Singleton1 instance = new Singleton1();
    //提供一个公有的静态方法,返回对象实例
    public static Singleton1 getInstance() {
        return instance;
    }
}
  • 优点:JVM层面的线程安全。JVM在加载这个类的时候就会对它进行初始化, 因此JVM层面包证了线程安全
  • 缺点:造成空间的浪费

饿汉模式(静态代码块)

java 复制代码
//饿汉式(静态代码块)
public class Singleton2 {
    //构造器私有化,外部不能new
    private Singleton2() {}
    //本类创建对象实例
    private static Singleton2 instance;
    static {
        instance = new Singleton2();
    }
    //提供一个公有的静态方法,返回对象实例
    public static Singleton2 getInstance() {
        return instance;
    }
}

这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。

饿汉模式(枚举)

java 复制代码
//枚举
public enum Singleton8 {
    INSTANCE
}

枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的(枚举类也是在JVM层面保证的线程安全),并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。

懒汉模式(线程不安全,不可用)

真正需要的时候再完成类对象的创建

java 复制代码
//懒汉式(线程不安全)
public class Singleton3 {
    private static Singleton3 instance;
    private Singleton3() {}
    //提供一个静态的公有方法,当使用到该方法时,才去创建instance 即懒汉式
    public static Singleton3 getInstance() {
        if (instance == null){
            instance = new Singleton3();
        }
        return instance;
    }
}
  • 优点:节省空间
  • 缺点:线程不安全

懒汉模式(线程安全,同步方法,不推荐用)

通过synchronized关键字对获取实例的方法进行同步限制, 实现了线程安全

java 复制代码
//懒汉式(线程安全)
public class Singleton4 {
    private static Singleton4 instance;
    private Singleton4() {}
    //提供一个静态的公有方法,当使用到该方法时,才去创建instance 即懒汉式
    public static synchronized Singleton4 getInstance() {
        if (instance == null){
            instance = new Singleton4();
        }
        return instance;
    }
}
  • 优点:线程安全
  • 缺点:对所有线程的访问都会进行同步操作, 有很严重的性能问题

懒汉模式(线程不安全,同步代码块,不可用)

java 复制代码
//懒汉式(线程安全, 同步代码块)
public class Singleton5 {
    private static Singleton5 instance;
    private Singleton5(){};
    //提供一个静态的公有方法,当使用到该方法时,才去创建instance 即懒汉式
    public static Singleton5 getInstance() {
        if (instance == null){
            synchronized(Singleton5.class){
                instance = new Singleton5();
            }
        }
        return instance;
    }
}

这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一致,假如一个线程进入了if (instance == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例

懒汉模式(线程安全,双重检查, 推荐用)

双重检查锁(Double Checked Locking, 简称DCL)模式

java 复制代码
//双重检查
public class Singleton6 {
    private Singleton6() {}
    private static volatile Singleton6 instance;
    public static Singleton6 getInstance() {
        //第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实际
        if (instance == null){
            synchronized(Singleton5.class){
                //抢到锁之后再次判断是否为空
                if (instance == null){
                    instance = new Singleton6();
                }
            }
        }
        return instance;
    }
}

在多处理器的共享内存、或者编译器的优化下, DCL模式并不一定线程 ------ 可能 (注意: 只是可能出现) 会发生指令的重排序, 出现半个对象的问题

Java中创建一个对象的过程并不是原子性操作,可能会发生指令的重排序(先把这个实例的引用指向地址,再对成员初始化), 出现半个对象的问题

因此要用volatile关键字修饰instance变量

半对象问题:当一个线程进来的时候,判断对象是否为空?肯定为空,因为还没创建呢,往下执行,拿到锁,继续往下执行,再次判断是否为空?为空,往下执行,在new对象的时候,对象有个半初始化的一个状态,在执行完new的时候,分配了一块空间,成员变量是引用类型那么它的值为null,就在此时,invokespecialastore 1发生了指令重排序,直接将instance指向了初始化一半还没有调用构造方法的内存空间,这时候第二个线程进来了,判断对象为空吗?不为空,为啥?因为它指向了一个半初始化的一个对象嘛!既然不为空,我就直接返回了这个初始化一半的对象

懒汉式(线程安全,静态内部类,推荐用)

java 复制代码
//静态内部类
public class Singleton7 {
    private Singleton7() {}
    //提供一个静态的公有方法,当使用到该方法时,才去创建instance 即懒汉式
    private static class SingletonInstance {
        private static final Singleton7 INSTANCE = new Singleton7();
    }
    public static Singleton7 getInstance() {
        return SingletonInstance.INSTANCE;
    }
}
  • JVM在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类(SingletonHolder)的属性/方法被调用时才会被加载, 并初始化其静态属性(instance)
  • 优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高

破坏单例模式

除枚举方式外, 其他方法都会通过反射的方式破坏单例

  1. 反射是通过调用构造方法生成新的对象, 可以在构造方法中进行判断 ------ 若已有实例, 则阻止生成新的实例,
java 复制代码
private Singleton() throws Exception {
    if (instance != null) {
      throw new Exception("Singleton already initialized, 此类是单例类, 不允许生成新对象, 请通过getInstance()获取本类对象");
    }
}
  1. 如果单例类实现了序列化接口Serializable, 就可以通过反序列化破坏单例。可以不实现序列化接口, 或者重写反序列化方法readResolve(), 反序列化时直接返回相关单例对象
java 复制代码
// 反序列化时直接返回当前实例
public Object readResolve() {
    return instance;
}
  1. Object#clone()方法也会破坏单例, 即使你没有实现Cloneable接口 ------ 因为clone()方法是Object类中的。可以重写clone()方法, 并在其中抛出异常信息"Can not create clone of Singleton class"

工厂模式

简单工厂模式

简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。

  • Factory: 工厂角色 负责根据不同的参数创建不同的实例。

  • IProduct: 抽象产品角色 所有产品实例的接口,负责描述所有产品实例的行为。

  • Product(A B ...): 具象产品角色,所有产品的实例,实现了抽象产品定义的代码

示例:

平台做一个机票代购业务,对接了两个供应商A、B,用户选择完机票后,平台拿着机票去供应商下单。下单时根据机票由那个供应商提供去相应的供应商去下单。

  1. 定义一个下单接口
java 复制代码
public interface IVender {
    /**
     * 供应商下单方法
     */
    void order();
}
  1. 分别实现A、B供应商的下单方法
java 复制代码
public class VendorA implements IVender {
    @Override
    public void order() {
        // 业务逻辑处理
        System.out.println("A供应商下单成功,下单时间" + new Date());
    }
}

public class VendorB implements IVender {
    @Override
    public void order() {
        // 业务逻辑处理
        System.out.println("B供应商下单成功,下单时间:" + new Date());
    }
}
  1. 接着定义一个工厂类,根据传入的不同参数请求,分别创建不同的供应商实例并返回,若碰到无效的参数,则抛出异常
java 复制代码
public class VendorFactory {

    public static IVender createVendor(String type) {
        switch (type) {
            case "A":
                return new VendorA();
            case "B":
                return new VendorB();
            default:
                throw new RuntimeException("供应商不存在");
        }
    }
}
  1. 最后,由我们客户端进行调用
java 复制代码
public class Client {
    public static void main(String[] args) {
        String type = "A";
        IVender iVender = VendorFactory.createVendor(type);
        iVender.order();
    }
}

缺点:缺点在于不符合开闭原则,每次添加新产品就需要修改工厂类。在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展维护,并且工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响。

工厂方法模式

  • 工厂方法模式将工厂抽象化,并定义一个创建对象的接口。每增加新产品,只需增加该产品以及对应的具体实现工厂类,由具体工厂类决定要实例化的产品是哪个,将对象的创建与实例化延迟到子类,这样工厂的设计就符合"开闭原则"了,扩展时不必去修改原来的代码。
  • 缺点:但缺点在于,每增加一个产品都需要增加一个具体产品类和实现工厂类,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。
  1. 抽象产品
java 复制代码
// 工厂方法的抽象产品
public interface Interviewer {
    void askQuestion();
}
  1. 具体产品
java 复制代码
// 具体产品
public class Developer implements Interviewer{
    @Override
    public void askQuestion() {
        System.out.println("询问设计模式相关的问题");
    }
}

public class CommunityExecutive implements Interviewer{
    @Override
    public void askQuestion() {
        System.out.println("询问社区建设相关的问题");
    }
}
  1. 抽象工厂
java 复制代码
//抽象工厂类
public abstract class HiringManager {
    // 抽象工厂方法
    protected abstract Interviewer makeInterviewer();
    public void takeInterviewer() {
        Interviewer interviewer = makeInterviewer(); //创建具体的
        interviewer.askQuestion();
    }
}
  1. 具体工厂(决定要实例化的产品是哪个)
java 复制代码
// 实现工厂类
public class DevelopmentManager extends HiringManager{

    @Override
    protected Interviewer makeInterviewer() {
        return new Developer();
    }
}

public class MarketingManager extends HiringManager{
    @Override
    protected Interviewer makeInterviewer() {
        return new CommunityExecutive();
    }
}

策略模式

策略模式定义了一系列的算法,并将每一个算法封装起来,使每个算法可以相互替代,使算法本身和使用算法的客户端分割开来,相互独立

  1. 策略接口角色IStrategy:用来约束一系列具体的策略算法,策略上下文角色ConcreteStrategy使用此策略接口来调用具体的策略所实现的算法
java 复制代码
//策略接口
public interface IStrategy {
    //定义的抽象算法方法 来约束具体的算法实现方法
    public void algorithmMethod();
}
  1. 具体策略实现角色ConcreteStrategy:具体的策略实现,即具体的算法实现
java 复制代码
 // 具体的策略实现2
public class ConcreteStrategy implements IStrategy {
     //具体的算法实现
    @Override
    public void algorithmMethod() {
        System.out.println("this is ConcreteStrategy method...");
    }
}
  1. 策略上下文角色StrategyContext:策略上下文,负责具体的策略实现交互,通常策略上下文对象会持有一个真正的策略实现对象,策略上下文还可以让具体的策略实现从其中获取相关数据,回调策略上下文对象的方法。
java 复制代码
/**
 * 策略上下文
 */
public class StrategyContext {
    //持有一个策略实现的引用
    private IStrategy strategy;
    //使用构造器注入具体的策略类
    public StrategyContext(IStrategy strategy) {
        this.strategy = strategy;
    }
 
    public void contextMethod(){
        //调用策略实现的方法
        strategy.algorithmMethod();
    }
}
  1. 外部客户端
java 复制代码
//外部客户端
public class Client {
    public static void main(String[] args) {
        //1.创建具体测策略实现
        IStrategy strategy = new ConcreteStrategy();
        //2.在创建策略上下文的同时,将具体的策略实现对象注入到策略上下文当中
        StrategyContext ctx = new StrategyContext(strategy);
        //3.调用上下文对象的方法来完成对具体策略实现的回调
        ctx.contextMethod();
    }
}
  • 缺点:
  1. 客户端必须了解所有的策略,清楚它们的不同:
    如果由客户端来决定使用何种算法,那客户端必须知道所有的策略,清楚各个策略的功能和不同,这样才能做出正确的选择,但是这暴露了策略的具体实现
  2. 增加了对象的数量:
    由于策略模式将每个具体的算法都单独封装为一个策略类,如果可选的策略有很多的话,那对象的数量也会很多
  3. 只适合偏平的算法结构:
    由于策略模式的各个策略实现是平等的关系(可相互替换),实际上就构成了一个扁平的算法结构。即一个策略接口下面有多个平等的策略实现(多个策略实现是兄弟关系),并且运行时只能有一个算法被使用。这就限制了算法的使用层级,且不能被嵌套
  • 本质:
    分离算法,选择实现。如果没有上下文,策略模式就回到了最基本的接口和实现了,只要是面向接口编程,就能够享受到面向接口编程带来的好处,通过一个统一的策略接口来封装和分离各个具体的策略实现,无需关系具体的策略实现。貌似没有上下文什么事,但是如果没有上下文的话,客户端就必须直接和具体的策略实现进行交互了,尤其是需要提供一些公共功能或者是存储一些状态的时候,会大大增加客户端使用的难度;引入上下文之后,这部分工作可以由上下文来完成,客户端只需要和上下文进行交互就可以了。这样可以让策略模式更具有整体性,客户端也更加的简单

代理模式

  • 前言:代理(Proxy)模式是一种结构型设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象。

代理模式大致有三种角色:

  • Real Subject:真实类,也就是被代理类、委托类。用来真正完成业务服务功能;
  • Proxy:代理类,将自身的请求用 Real Subject 对应的功能来实现,代理类对象并不真正的去实现其业务功能;
  • Subject:定义 RealSubject 和 Proxy 角色都应该实现的接口。

静态代理

静态代理需要先定义接口,被代理对象与代理对象一起实现相同的接口,然后通过调用相同的方法来调用目标对象的方法。

![外

  • 优点:静态代理模式在不改变目标对象的前提下,实现了对目标对象的功能扩展。
  • 缺点:静态代理实现了目标对象的所有方法,一旦目标接口增加方法,代理对象和目标对象都要进行相应的修改,增加维护成本。

动态代理

JDK代理

  • 原理:JDK动态代理对象不需要实现接口,但是目标对象必须实现接口。代理对象会实现与目标类一样的方法,并将方法调用转发给目标对象

  • 样例:有一天公司增加了业务,出售的商品越来越多,售后也需要更上。但是公司发现原来的代理商,还要再培训才能完成全部的业务,于是就找了另外的动态代理商B 。 代理商B 承诺无缝对接公司所有的业务,不管新增什么业务,均不需要额外的培训即可完成

  1. 公司增加了维修业务
java 复制代码
//接口添加方法
public interface TVCompany {

    /**
     * 生产电视机
     * @return 电视机
     */
    public TV produceTV();

    /**
     * 维修电视机
     * @param tv 电视机
     * @return 电视机
     */
    public TV repair(TV tv);
}
  1. 工厂也得把维修业务搞起来
java 复制代码
//新增目标类
public class TVFactory implements TVCompany {
    @Override
    public TV produceTV() {
        System.out.println("TV factory produce TV...");
        return new TV("小米电视机","北京");
    }

    @Override
    public TV repair(TV tv) {
        System.out.println("tv is repair finished...");
        return new TV("小米电视机","北京");
    }
}
  1. B代理商 全面代理公司所有的业务。使用Proxy.newProxyInstance方法生成代理对象,实现InvocationHandler中的 invoke方法,在invoke方法中通过反射调用代理类的方法,并提供增强方法
java 复制代码
//新增代理类
public class TVProxyFactory {

    private Object target;

    public TVProxyFactory(Object o){
        this.target = o;
    }

    /*
        ClassLoader loader:指定当前目标对象使用类加载器,获取加载器的方法是固定的。
        Class<?>[] interfaces:目标对象实现的接口的类型,使用泛型方式确认类型。
        InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入。
    */
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),
                new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("TV proxy find factory for tv.... ");
                Object invoke = method.invoke(target, args);
                return invoke;
            }
        });
    }
}
  1. 客户端调用
java 复制代码
public class TVConsumer {
    public static void main(String[] args) {
        TVCompany target = new TVFactory();
        TVCompany tvCompany = (TVCompany) new TVProxyFactory(target).getProxy();
        TV tv = tvCompany.produceTV();
        tvCompany.repair(tv);
    }
}

缺点:JDK 动态代理有一个最致命的问题是它只能代理实现了某个接口的实现类(因为java是单继承,生成的新的代理类继承Proxy),并且代理类也只能代理接口中实现的方法,要是实现类中有自己私有的方法,而接口中没有的话,该方法不能进行代理调用

事务失效场景:内部调用,当类内部的方法调用另一个带有 @Transactional 注解的方法时,这个调用不会通过 Spring 的代理对象进行,而是直接通过 this 引用,因此 Spring 无法拦截并应用事务。Spring AOP 代理机制只能拦截通过代理对象进行的方法调用,而不能拦截类内部的直接方法调用

解决:

其中一种解决方法是在类内部通过 Spring 容器获取当前对象的代理实例,然后通过代理对象调用目标方法,从而让事务生效

java 复制代码
@Service
public class TransactionService {

    @Autowired
    private ApplicationContext context;

    @Transactional
    public void publicMethod() {
        // 从 Spring 容器中获取代理对象
        TransactionService proxy = context.getBean(TransactionService.class);
        proxy.internalMethod(); // 通过代理对象调用方法,事务生效
    }

    @Transactional
    public void internalMethod() {
        // 事务在这里生效
    }
}

Cglib代理

Cglib代理可以称为子类代理,是在内存中构建一个子类对象,从而实现对目标对象功能的扩展。它不要求目标类实现接口中的方法,而是基于字节码生成技术,生成目标类的子类作为代理类,并重写父类的方法和增强逻辑

Cglib通过Enhancer 来生成代理类,通过实现MethodInterceptor接口,并实现其中的intercept方法,在此方法中可以添加增强方法,并可以利用反射Method或者MethodProxy继承类 来调用原方法

java 复制代码
public class TVProxyCglib implements MethodInterceptor {

    //给目标对象创建一个代理对象
    public Object getProxyInstance(Class c){
        //1.工具类
        Enhancer enhancer = new Enhancer();
        //2.设置父类
        enhancer.setSuperclass(c);
        //3.设置回调函数,调用方法的时候先调用intercept(拦截器)方法,执行我们定义的方法的增强链(也就是设置)
        enhancer.setCallback(this);
        //4.创建子类(代理对象)
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("TVProxyFactory enhancement.....");
        Object object = methodProxy.invokeSuper(o, objects);
        return object;
    }
}

新代理的B工厂

java 复制代码
public class TVFactoryB {
    public TV produceTVB() {
        System.out.println("tv factory B producing tv.... ");
        return new TV("华为电视机", "南京");
    }

    public TV repairB(TV tv) {
        System.out.println("tv B is repair finished.... ");
        return tv;
    }
}

C代理可以直接和公司合作,也可以和工厂打交道。并且可以代理任何工厂的产品。

java 复制代码
public class TVConsumer {
    public static void main(String[] args) {
        TVCompany tvCompany = (TVCompany) new TVProxyCglib().getProxyInstance(TVFactory.class);
        TV tv = tvCompany.produceTV();
        tvCompany.repair(tv);
        System.out.println("==============================");

        TVFactoryB tvFactoryB = (TVFactoryB) new TVProxyCglib().getProxyInstance(TVFactoryB.class);
        TV tv = tvFactoryB.produceTVB();
        tvFactoryB.repairB(tv);
    }
}

打印结果

java 复制代码
TVProxyFactory enhancement.....
TV factory produce TV...
TVProxyFactory enhancement.....
tv is repair finished...
==============================
TVProxyFactory enhancement.....
tv factory B producing tv.... 
TVProxyFactory enhancement.....
tv B is repair finished.... 

Process finished with exit code 0

Spring AOP使用代理

Spring中AOP的实现有JDK和Cglib两种,如下图:

  • 如果目标对象需要实现接口,则使用JDK代理
  • 如果目标对象不需要实现接口,则使用Cglib代理

总结

  • 静态代理:需要代理类和目标类都实现接口的方法,从而达到代理增强其功能
  • JDK动态代理:需要代理类实现某个接口,使用Proxy.newProxyInstance方法生成代理类,并实现InvocationHandler中的invoke方法,实现增强功能
  • Cglib动态代理:无需代理类实现接口,使用Cblib中的Enhancer来生成代理对象子类,并实现MethodInterceptor中的intercept方法,在此方法中可以实现增强功能

模板方法模式

核心思想是:父类定义骨架,子类实现某些细节

为了防止子类重写父类的骨架方法,可以在父类中对骨架方法使用final。对于需要子类实现的抽象方法,一般声明为protected,使得这些方法对外部客户端不可见

  1. 父类定义骨架
java 复制代码
public abstract class AbstractSetting {
    public final String getSetting(String key) {
        //从缓存读取
        String value = lookupCache(key);
        if (value == null) {
            // 在缓存中未找到,从数据库读取
            value = readFromDatabase(key);
            // 放入缓存
            putIntoCache(key, value);
        }
        return value;
    }

    protected abstract String lookupCache(String key);

    protected abstract void putIntoCache(String key, String value);
}
  1. 子类实现某些细节
java 复制代码
public class RedisSetting extends AbstractSetting {
    private RedisClient client = RedisClient.create("redis://localhost:6379");

    protected String lookupCache(String key) {
        try (StatefulRedisConnection<String, String> connection = client.connect()) {
            RedisCommands<String, String> commands = connection.sync();
            return commands.get(key);
        }
    }

    protected void putIntoCache(String key, String value) {
        try (StatefulRedisConnection<String, String> connection = client.connect()) {
            RedisCommands<String, String> commands = connection.sync();
            commands.set(key, value);
        }
    }
}
  1. 客户端调用
java 复制代码
AbstractSetting setting = new RedisSetting();
System.out.println("autosave = " + setting.getSetting("autosave"));
System.out.println("autosave = " + setting.getSetting("autosave"));
ng> connection = client.connect()) {
            RedisCommands<String, String> commands = connection.sync();
            return commands.get(key);
        }
    }

    protected void putIntoCache(String key, String value) {
        try (StatefulRedisConnection<String, String> connection = client.connect()) {
            RedisCommands<String, String> commands = connection.sync();
            commands.set(key, value);
        }
    }
}
  1. 客户端调用
java 复制代码
AbstractSetting setting = new RedisSetting();
System.out.println("autosave = " + setting.getSetting("autosave"));
System.out.println("autosave = " + setting.getSetting("autosave"));

观察者模式

基本理解

观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式

  • 优点:
  1. 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则
  2. 目标与观察者之间建立了一套触发机制
  • 缺点:
  1. 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用
  2. 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率
  • 观察者模式的结构:
  1. 抽象主题(subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的方法
  2. 具体主题(Concrete subject)角色:也叫具体目标类,实现了抽象目标类的方法,当具体主题的内部状态发生变化的时候,通知所有注册过的观察者对象
  3. 抽象观察者(Observer)角色:它是一个抽象类或者接口,它包含了一个更新自己的抽象方法,当接受到具体主题的更改通知时被调用
  4. 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态

示例:

Observer

java 复制代码
// 抽象观察者
public interface Observer {
    //更新的方法
    void update(String messages);
}

WexinUser

java 复制代码
//具体观察者类 实现更新的方法
public class WexinUser implements Observer {
    //用户名
    private String name;

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

    public WexinUser() {

    }

    @Override
    public void update(String messages) {
        System.out.println(name + "-->" + messages);
    }
}

Subject

java 复制代码
//抽象主题类
public interface Subject {
    //增加订阅者
    public void attach(Observer observer);

    //删除订阅者
    public void remove(Observer observer);

    //通知订阅者更新消息
    public void notify(String messages);
}

SubscriptionSubject

java 复制代码
//具体主题(具体被观察者)
public class SubscriptionSubject implements Subject {
    //存储订阅公众号的微信用户
    private List<Observer> weixinUserList = new ArrayList<Observer>();

    @Override
    public void attach(Observer observer) {
        weixinUserList.add(observer);
    }

    @Override
    public void remove(Observer observer) {
        weixinUserList.remove(observer);
    }
}

Client

java 复制代码
public class Client {
    public static void main(String[] args) {
        SubscriptionSubject subject = new SubscriptionSubject();

        //创建微信用户
        WexinUser user1 = new WexinUser("张三");
        WexinUser user2 = new WexinUser("李四");
        WexinUser user3 = new WexinUser("王五");

        //订阅公众号
        subject.attach(user1);
        subject.attach(user2);
        subject.attach(user3);

        //通过订阅用户
        subject.notify("您关注的公众号更新啦~~~");
    }
}

JDK源码解析

在 Java 中,通过 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例

  1. Observable类(抽象被观察者)
    Observable 类是抽象目标类(被观察者),它有一个 Vector 集合成员变量,用于保存所有要通知的观察者对象,下面来介绍它最重要的3个方法
  • void addObserver(Observer o) 方法:用于将新的观察者对象添加到集合中
  • void notifyObservers(Object arg) 方法:调用集合中的所有观察者对象的 update方法,通知它们数据发生改变。通常越晚加入集合的观察者越先得到通知
  • void setChange() 方法:用来设置一个 boolean 类型的内部标志,注明目标对象发生了变化。当它为true时,notifyObservers() 才会通知观察者
  1. Observer 接口(抽象观察者)
    Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 update 方法,进行相应的工作

示例:

警擦(观察者)抓小偷(被观察者),当小偷偷东西的时警擦会被通知

Thief(被观察者)

java 复制代码
//小偷类 继承Observable接口
import java.util.Observable;

public class Thief extends Observable {
    private String name;

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

    public Thief() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void steal() {
        System.out.println("emmm我正在悄悄偷东西");
        super.setChanged();//默认为true
        super.notifyObservers();
    }
}

Policemen(观察者)

java 复制代码
import java.util.Observable;
import java.util.Observer;
public class Policeman implements Observer {

    private String name;

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Policeman() {
    }

    @Override
    public void update(Observable o, Object arg) {
        System.out.println("警察:" + ((Thief) o).getName() + "我抓住你了!!!");
    }
}

Client

java 复制代码
public class Client {
    public static void main(String[] args) {
        //小偷(被观察者)
        Thief thief = new Thief("法外狂徒格雷福斯");
        //警察(观察者)
        Policeman policeman = new Policeman("凯瑟琳女警");
        //警察观察小偷
        thief.addObserver(policeman);
        //小偷行窃
        thief.steal();
    }
}

/*  打印结果:
    emmm我正在悄悄偷东西
    警察:法外狂徒格雷福斯我抓住你了!!!
*/
相关推荐
hcgeng2 分钟前
Kotlin学习(一)
java·开发语言·前端·kotlin
飞的肖7 分钟前
vue实现淘宝web端,装饰淘宝店铺APP,以及后端设计成能快速响应前端APP
java·前端·vue.js·店铺装修
Mr_sun.13 分钟前
Day04-后端Web基础(Maven基础)
java·前端·maven
谛凌16 分钟前
【物流管理系统 - IDEA&Java&Swing&MySQL】基于Java实现的物流管理系统导入IDEA教程
java·mysql·intellij-idea·swing·物流管理系统
IDRSolutions_CN18 分钟前
(基础)理解PDF表单文件
java·经验分享·pdf·软件工程·团队开发
magic 24521 分钟前
idea快捷键
java·ide·intellij-idea
雪芽蓝域zzs25 分钟前
IDEA中创建maven项目
java·maven·intellij-idea
TroubleMaker1 小时前
OkHttp源码学习之CertificatePinner
android·java·okhttp
swoole~1 小时前
Docker Compose 教程
java·docker·eureka
坑里技术员2 小时前
Python标准库之SQLite3
java·开发语言·jvm