观察者设计模式
观察者设计模式
一、概念
定义对象之间的一种一对多的依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。
二、使用场景
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面
- 一个对象的改变将导致一个或多个其他对象也发生改变
- 需要在系统中创建一个触发链(A对象的行为会影响B对象,B对象的行为又会影响C对象,从而形成了一个链)
三、UML结构图
观察者设计模式UML.png
四、代码示例
案例一:
Subject:
/** * 抽象目标类 * @author HCX * */ public abstract class Subject { //保存注册的观察者对象 private List<Observer> observersList = new ArrayList<>(); //注册观察者对象 public void attach(Observer observer){ observersList.add(observer); System.out.println("Attached an observer"); } //注销观察者对象 public void detach(Observer observer){ observersList.remove(observer); } //通知所有注册的观察者对象 public void notifyObservers(String newState){ for (Observer observer : observersList) { observer.update(newState); } } }
Observer:
public interface Observer { void update(String state); }
ConcreateSubject:
public class ConcreateSubject extends Subject{ private String state; public String getState(){ return state; } public void Change(String newState){ state = newState; System.out.println("ConcreateSubject State:"+state); //状态发生改变,通知观察者 notifyObservers(state); } }
ConcreateObserver:
public class ConcreateObserver implements Observer{ //观察者状态 private String observerState; @Override public void update(String state) { //更新观察者状态,让它与目标状态一致 observerState = state; System.out.println("ConcreateObserver State:"+observerState); } }
MyTest:
public class MyTest { public static void main(String[] args) { //创建目标对象(被观察者) ConcreateSubject concreateSubject = new ConcreateSubject(); //创建观察者对象 Observer observer = new ConcreateObserver(); //将观察者对象注册到目标对象上 concreateSubject.attach(observer); //改变目标对象的状态 观察者就会进行相应的数据改变 concreateSubject.Change("I change"); } }
案例二:订阅天气
步骤:
- 定义目标对象
- 定义具体的目标对象
- 定义观察者的接口
- 定义观察者的具体实现
通用写法:
Subject:
/** * 目标对象:被观察的对象 * 观察它的观察者,并提供注册(添加)和删除观察者的接口 * @author HCX * */ public class Subject { //添加或删除观察者的方法 //集合:用来保存注册的观察者对象 private List<Observer> observers = new ArrayList<Observer>(); /** * 把观察者对象添加到注册的观察者对象集合中 * @param observer :观察者对象 */ public void attach(Observer observer){ observers.add(observer); } /** * 删除集合中的指定观察者 * @param observer :观察者 */ public void detach(Observer observer){ observers.remove(observer); } /** * 通知方法:需要通知观察者列表中已注册的所有观察者(只有子类可以调用) * 想所有注册的观察者发送消息 */ protected void notifyObservers(){ for (Observer observer : observers) { observer.update(this); } } }
ConcreteSubject:
/** * 具体的目标对象 * 负责把有关状态存入相应的观察者对象中 * @author HCX * */ public class ConcreteSubject extends Subject { //目标对象的状态 private String subjectState; public String getSubjectState() { return subjectState; } /** * 在set的同时,还需要在保存目标状态的同时,通知观察者 * 需要在set方法中添加通知的语句。 * @param subjectState */ public void setSubjectState(String subjectState) { this.subjectState = subjectState; this.notifyObservers(); } }
Observer:
/** * 观察者接口 * 定义一个更新的接口给那些在目标发生改变的时候被通知的对象 * @author HCX * */ public interface Observer { /** * 更新的接口 * @param subject:传入目标对象,方便获取相应的目标对象的状态 */ public void update(Subject subject); }
ConcreteObserver:
/** * 具体的观察者对象 * 实现更新的方法,使自身的状态和目标的状态保持一致 * @author HCX * */ public class ConcreteObserver implements Observer { //观察者的状态 private String observerState; /** * 获取目标类的状态同步到观察者状态中 */ @Override public void update(Subject subject) { //把目标状态获取的值赋给观察者状态变量observerState //状态在子类中,所以强转为ConcreteSubject,再通过get方法获取 observerState = ((ConcreteSubject)subject).getSubjectState(); } }
实际应用:
目标:天气
订阅者:小明的女朋友和小明的妈妈
两个订阅者都会收到最新的天气信息,只要在订阅者列表的人都会收到天气信息。
所需变量:
- 观察者的名字
- 天气情况的内容
- 提醒的内容:小明的女朋友提醒约会,小明的妈妈提醒购物。
Observer:
/** * 观察者接口 * 定义一个更新的接口给那些在目标发生改变的时候被通知的对象 * @author HCX * */ public interface Observer { /** * 更新的接口 * @param subject:传入目标对象,方便获取相应的目标对象的状态 */ public void update(WeatherSubject subject); }
ConcreteObserver:
/** * 具体的观察者对象 * 实现更新的方法,使自身的状态和目标的状态保持一致 * @author HCX * */ public class ConcreteObserver implements Observer { //观察者的名字,是谁收到了这个讯息,小明的女朋友还是他妈妈 private String observerName; //观察者自己设置的 //天气内容的情况,这个消息时从目标处获取 private String weatherContent;//从目标处获取 //提醒的内容:小明的女朋友提醒约会,他妈妈提醒购物。 private String remindThing;//观察者自己设置的 /** * 获取目标类的状态同步到观察者状态中 * 在该方法中,观察者收到了明天的天气情况,做出的事情 */ @Override public void update(WeatherSubject subject) { //把目标状态获取的值赋给观察者状态变量observerState //状态在子类中,所以强转为ConcreteSubject,再通过get方法获取 weatherContent = ((ConcreteWeatherSubject)subject).getWeatherContent(); System.out.println(observerName+"收到了:"+weatherContent+","+remindThing); } public String getObserverName() { return observerName; } public void setObserverName(String observerName) { this.observerName = observerName; } public String getWeatherContent() { return weatherContent; } public void setWeatherContent(String weatherContent) { this.weatherContent = weatherContent; } public String getRemindThing() { return remindThing; } public void setRemindThing(String remindThing) { this.remindThing = remindThing; } }
WeatherSubject:
/** * 目标对象:被观察的对象 * 观察它的观察者,并提供注册(添加)和删除观察者的接口 * @author HCX * */ public class WeatherSubject { //编写添加或删除观察者的方法 //集合:用来保存注册的观察者对象 private List<Observer> observers = new ArrayList<Observer>(); /** * 把观察者对象添加到注册的观察者对象集合中 * 把订阅天气的人添加到订阅者列表中 * @param observer :观察者对象 */ public void attach(Observer observer){ observers.add(observer); } /** * 删除集合中的指定观察者: * 删除集合中指定订阅天气的人 * @param observer :观察者 */ public void detach(Observer observer){ observers.remove(observer); } /** * 通知方法:需要通知观察者列表中已注册的所有观察者(只有子类可以调用) * 通知所有已经订阅天气的人 * 向所有注册的观察者发送消息 */ protected void notifyObservers(){ for (Observer observer : observers) { observer.update(this); } } }
ConcreteWeatherSubject:
/** * 具体的目标对象 * 负责把有关状态存入相应的观察者对象中 * @author HCX * */ public class ConcreteWeatherSubject extends WeatherSubject { //目标对象的状态 //获取天气的内容信息 private String weatherContent; public String getWeatherContent() { return weatherContent; } /** * 在set的同时,还需要在保存目标状态的同时,通知观察者 * 需要在set方法中添加通知的语句。 * 供外部调用,当有新的天气内容更新时,调用该方法。 * @param subjectState */ public void setWeatherContent(String weatherContent) { this.weatherContent = weatherContent; //内容有了,说明天气更新了,通知所有的订阅的人 this.notifyObservers(); } }
Client:
public class Client { public static void main(String[] args) { //1.创建目标 ConcreteWeatherSubject weather = new ConcreteWeatherSubject(); //2.创建观察者 ConcreteObserver observerGirl = new ConcreteObserver(); observerGirl.setObserverName("小明的女朋友"); observerGirl.setRemindThing("是我们的第一次约会,地点在欢乐海岸,不见不散哦"); ConcreteObserver observerMon = new ConcreteObserver(); observerMon.setObserverName("小明的妈妈"); observerMon.setRemindThing("是一个购物的好日子,明天去山姆买东西"); //3.注册观察者到观察者列表中 weather.attach(observerGirl); weather.attach(observerMon); //4.在目标处发布天气:会自动通知到已经注册的观察者中 weather.setWeatherContent("明天天气晴朗,蓝天白云,气温28度"); } }
五、观察者模式内部关系
1.目标与观察者之间的关系
①一个观察者和一个目标:一对一的关系
小明发布的天气—>小明的女朋友
②多个观察者对应一个目标:一对多的关系
小明发布的天气—->小明的女朋友、小明的妈妈
③一个观察者观察多个目标:多对一的关系
小明发布的天气、小明的报纸<—小明的女朋友
2.单向依赖
只有观察者依赖目标,目标没有依赖观察者
目标是主动,观察者是被动,被动的等待目标的通知
3.命名建议
①目标接口的定义,建议在名称后面跟Subject
②观察者接口的定义,建议在名称后面跟Observer
③观察者接口的更新方法,建议名称为update
4.触发通知的时机
一般是在完成了状态维护后触发,通知会传递数据,不能先通知,后改变数据,会导致观察者和目标对象状态不一致。
5.观察者模式的调用顺序示意图
观察者模式调用顺序示意图.png
6.通知的顺序
当目标对象的状态变化后,通知所有观察者的时候,顺序是不确定的。因此,观察者实现的功能不能依赖于通知的顺序,即多个观察者之间的顺序是平行的,相互之间不应该有先后依赖的关系。
六、观察者模式实现的两种方式
1.推模型
- 目标对象主动向观察者推送目标的详细信息
- 推送的信息通常是目标对象的全部或部分数据
修改部分内容,把需要传递的数据直接推送给观察者对象
观察者部分:按需取内容
目标部分:按需推送内容
Observer:
/** * 观察者接口 * 定义一个更新的接口给那些在目标发生改变的时候被通知的对象 * @author HCX * */ public interface Observer { /** * 更新的接口 * @param content:该参数按需分配,此处需要天气情况的内容 */ public void update(String content); }
ConcreteObserver:
/** * 具体的观察者对象 * 实现更新的方法,使自身的状态和目标的状态保持一致 * @author HCX * */ public class ConcreteObserver implements Observer { //观察者的名字,是谁收到了这个讯息,小明的女朋友还是他妈妈 private String observerName; //观察者自己设置的 //天气内容的情况,这个消息时从目标处获取 private String weatherContent;//从目标处获取 //提醒的内容:小明的女朋友提醒约会,他妈妈提醒购物。 private String remindThing;//观察者自己设置的 /** * 获取目标类的状态同步到观察者状态中 * 在该方法中,观察者收到了明天的天气情况,做出的事情 */ @Override public void update(String content) { //此处不再需要通过引用去获取内容 //weatherContent = ((ConcreteWeatherSubject)subject).getWeatherContent(); weatherContent = content; System.out.println(observerName+"收到了:"+weatherContent+","+remindThing); } public String getObserverName() { return observerName; } public void setObserverName(String observerName) { this.observerName = observerName; } public String getWeatherContent() { return weatherContent; } public void setWeatherContent(String weatherContent) { this.weatherContent = weatherContent; } public String getRemindThing() { return remindThing; } public void setRemindThing(String remindThing) { this.remindThing = remindThing; } }
WeatherSubject:
/** * 目标对象:被观察的对象 * 观察它的观察者,并提供注册(添加)和删除观察者的接口 * @author HCX * */ public class WeatherSubject { //编写添加或删除观察者的方法 //集合:用来保存注册的观察者对象 private List<Observer> observers = new ArrayList<Observer>(); /** * 把观察者对象添加到注册的观察者对象集合中 * 把订阅天气的人添加到订阅者列表中 * @param observer :观察者对象 */ public void attach(Observer observer){ observers.add(observer); } /** * 删除集合中的指定观察者: * 删除集合中指定订阅天气的人 * @param observer :观察者 */ public void detach(Observer observer){ observers.remove(observer); } /** * 通知方法:需要通知观察者列表中已注册的所有观察者(只有子类可以调用) * 通知所有已经订阅天气的人 * 向所有注册的观察者发送消息 */ protected void notifyObservers(String content){ for (Observer observer : observers) { //指定内容推送给观察者 observer.update(content); } } }
ConcreteWeatherSubject:
/** * 具体的目标对象 * 负责把有关状态存入相应的观察者对象中 * @author HCX * */ public class ConcreteWeatherSubject extends WeatherSubject { //目标对象的状态 //获取天气的内容信息 private String weatherContent; public String getWeatherContent() { return weatherContent; } /** * 在set的同时,还需要在保存目标状态的同时,通知观察者 * 需要在set方法中添加通知的语句。 * 供外部调用,当有新的天气内容更新时,调用该方法。 * @param subjectState */ public void setWeatherContent(String weatherContent) { this.weatherContent = weatherContent; //目标主动将天气情况推送给了观察者 this.notifyObservers(weatherContent); } }
Client不变
2.拉模型
- 目标对象在通知观察者的时候,只传递少量信息。
- 如果观察者需要更具体的信息,由观察者主动到目标对象中获取,相当于是观察者从目标对象中拉数据。
- 一般这种模型的实现中,会把目标对象自身通过update方法传递给观察者,这样,在观察者需要获取数据的时候,就可以通过引用来获取。
案例二即是拉模型
二者比较:
- 推模型是假定目标对象知道观察者需要的数据。
- 拉模型是目标对象不知道观察者具体需要什么数据,因此把自身传给观察者,由观察者来取值。
- 推模型会使观察者对象难以复用。
- 拉模型下,update方法的参数是目标对象本身,基本上可以适应各种情况的需要。
六、使用java自带的观察者模式
java实现与自己手动实现的对比:
- 不需要再定义观察者接口和目标的接口了
- 具体的目标实现里面不需要再维护观察者的注册信息了,在Java中的Observerable类里面已经实现好了
- 触发通知的方式有一点变化,要先调用setChanged方法,这个是java为了帮助实现更精确的触发控制而提供的功能。
- 具体的观察者实现里面,update方法能同时支持推模型和拉模型,这个是java在定义的时候就考虑进去了。
使用java自带的观察者和被观察者类
Java本身内置的类Observer:观察者对象
监听被观察者对象数据的变化,一旦被观察的数据发生变化,该观察者对象就会做出相应的响应。
源码:Observer
/** * A class can implement the <code>Observer</code> interface when it * wants to be informed of changes in observable objects. * * @author Chris Warth * @see java.util.Observable * @since JDK1.0 */ public interface Observer { /** * This method is called whenever the observed object is changed. An * application calls an <tt>Observable</tt> object's * <code>notifyObservers</code> method to have all the object's * observers notified of the change. * * @param o the observable object. * @param arg an argument passed to the <code>notifyObservers</code> * method. */ void update(Observable o, Object arg); }
源码:Observable:被观察者对象
public class Observable { private boolean changed = false; private Vector<Observer> obs; public Observable() { obs = new Vector<>(); } public synchronized void addObserver(Observer o) { if (o == null) throw new NullPointerException(); if (!obs.contains(o)) { obs.addElement(o); } } public synchronized void deleteObserver(Observer o) { obs.removeElement(o); } public void notifyObservers() { notifyObservers(null); } public void notifyObservers(Object arg) { Object[] arrLocal; synchronized (this) { if (!changed) return; arrLocal = obs.toArray(); clearChanged(); } for (int i = arrLocal.length-1; i>=0; i--) ((Observer)arrLocal[i]).update(this, arg); } public synchronized void deleteObservers() { obs.removeAllElements(); } protected synchronized void setChanged() { changed = true; } protected synchronized void clearChanged() { changed = false; } public synchronized boolean hasChanged() { return changed; } public synchronized int countObservers() { return obs.size(); } }
TargetObservable:
package com.hcx.observerable2; import java.util.Observable; /** * Observable是被观察者对象接口,实现该接口就是:目标(被观察者)的具体实现 * @author HCX * */ public class TargetObservable extends Observable{ //要观察的数据:消息发生改变时,所有被添加的观察者都能收到通知 private String message; public String getContent(){ return message; } public void setMessage(String message){ this.message = message; //被观察者数据发生变化时,通过以下两行代码通知所有的观察者 this.setChanged(); this.notifyObservers(message); } }
TargetObserver:
/** * Observer对象是观察者,实现Observer的对象就是具体的观察者对象 * @author HCX * */ public class TargetObserver implements Observer{ //定义观察者名称 private String name; public String getObserverName(){ return name; } public void setObserverName(String observerName){ this.name = observerName; } @Override public void update(Observable o, Object arg) { //更新消息数据 System.out.println(name+"收到了发生变化的数据内容是:"+((TargetObservable)o).getContent()); } }
套用案例二:
ConcreteWeatherSubject:
import java.util.Observable; /** * 天气目标的具体实现类 * @author HCX * */ public class ConcreteWeatherSubject extends Observable{ //天气情况的内容 private String content; public String getContent() { return content; } public void setContent(String content) { this.content = content; //通知所有的观察者 this.setChanged(); //主动通知,使用推模型 this.notifyObservers(content); //使用拉模型 //this.notifyObservers(); } }
ConcreteObserver:
import java.util.Observable; import java.util.Observer; /** * 具体的观察者对象 * @author HCX * */ public class ConcreteObserver implements Observer { //观察者名称变量 private String observerName; /** * Observable:目标的引用(拉模型) * Object:推送的内容(推模型) */ @Override public void update(Observable o, Object arg) { //推模型 System.out.println(observerName+"收到了消息,目标推送过来的是:"+arg); //拉模型 System.out.println(observerName+"收到了消息,主动到目标对象中去拉,拉的内容是:"+((ConcreteWeatherSubject)o).getContent()); } public String getObserverName() { return observerName; } public void setObserverName(String observerName) { this.observerName = observerName; } }
client:
public class Client { public static void main(String[] args) { // 创建天气作为目标(被观察者) ConcreteWeatherSubject subject = new ConcreteWeatherSubject(); // 创建小明的女朋友作为观察者 ConcreteObserver girl = new ConcreteObserver(); girl.setObserverName("小明的女朋友"); // 创建小明的妈妈作为观察者 ConcreteObserver mon = new ConcreteObserver(); mon.setObserverName("小明的妈妈"); //注册观察者 subject.addObserver(girl); subject.addObserver(mon); //目标更新天气情况 subject.setContent("天气晴,气温28度"); } }
两个观察者的比较:
- 为什么不用List?
在被观察者发出通知那一刻,同时添加新的观察者,或删除某个观察者的时候,导致被观察者不知道通知哪一个对象。因为同时删除和添加,如果没有线程安全,会造成混乱。所以jdk使用了vector。 - 是否有替代品?(替代Vector)
CopyOnWriteArrayList(线程安全的ArrayList)
七、观察者模式的优缺点
优点:
- 观察者模式实现了观察者和目标之间的抽象耦合
- 观察者模式实现了动态联动
- 观察者模式支持广播通信
缺点:
- 可能引起无畏的操作
八、何时使用观察者模式
观察者模式的本质:触发联动
当修改目标对象状态的时候,就会触发相应的通知,然后循环调用所有注册的观察者对象的相应方法,相当于联动调用观察者的方法。
1.当一个抽象模型有两个方面,其中一个方面的操作依赖于另一个方面的状态变化,此时选用观察者模式,将两者封装成观察者和目标对象,当目标对象变化时,依赖于他的观察者对象也发生相应的变化。
2.如果在更改一个对象的时候,需要同时连带改动其他的对象,而且不知道你究竟有多少对象需要被连带改变。
被更改的对象:目标对象
需要连带修改的其他对象:多个观察者对象
3.当一个对象必须通知其他的对象,但是又希望这个对象和其他被它通知的对象是松散耦合的。
这个对象:目标对象
被它通知的对象:观察者对象
九、观察者模式的衍生
1.需求:
小明的女朋友只想接收下雨的天气预报
小明的妈妈只想接收下雨或下雪的天气预报
解决:
目标:天气预报
观察者:小明的女朋友和小明的妈妈
之前是当目标天气更新时,是全部广播消息给已注册的观察者;此处需要根据不同的天气情况广播给不同的观察者。
在目标天气中进行判断,如果不符合观察者的条件,则不通知;
情况之一:
天气是晴天,按照小明的女朋友需要下雨的条件,小明的妈妈需要下雨或下雪的条件,他们俩就都不需要通知。
情况之二:
天气是下雨,小明的女朋友和小明的妈妈都需要通知。
情况之三:
天气是下雪:只需要通知小明的妈妈。
2.实现步骤:
- 定义目标的抽象类和观察者的接口
- 实现目标的类和观察者接口
- 进行测试
3.代码示例
observer:
/** * 定义一个更新的接口方法给那些在目标发生改变的时候被通知的观察者对象调用 * @author HCX * */ public interface Observer { /** * 更新的接口 * @param subject */ public void update(WeatherSubject subject); //设置观察者名称 public void setObserverName(String observerName); //取得观察者名称 public String getObserverName(); }
ConcreteObserver:
public class ConcreteObserver implements Observer { //观察者名称(观察者自己设置) private String observerName; //天气情况的内容(从目标出获取) private String weatherContent; //提醒的内容(观察者自己设置) private String remindThing; @Override public void update(WeatherSubject subject) { //从目标处获取天气情况 weatherContent = ((ConcreteWeatherSubject)subject).getWeatherContent(); System.out.println(observerName+"收到了"+weatherContent+","+remindThing); } @Override public void setObserverName(String observerName) { this.observerName = observerName; } @Override public String getObserverName() { return observerName; } public String getWeatherContent() { return weatherContent; } public void setWeatherContent(String weatherContent) { this.weatherContent = weatherContent; } public String getRemindThing() { return remindThing; } public void setRemindThing(String remindThing) { this.remindThing = remindThing; } }
WeatherSubject:
public abstract class WeatherSubject { public List<Observer> observers = new ArrayList<Observer>(); /** * 把订阅天气的观察者添加到订阅者列表中 * @param observer */ public void attach(Observer observer){ observers.add(observer); } /** * 删除集合中指定的订阅天气的人 * @param observer */ public void detach(Observer observer){ observers.remove(observer); } //此处需要把notifyobservers方法的实现放到子类中,因为需要根据不同的天气情况区别的对待观察者 protected abstract void notifyObservers(); }
ConcreteWeatherSubject:
public class ConcreteWeatherSubject extends WeatherSubject { //晴天、下雨、下雪 //目标对象的状态 private String weatherContent; @Override protected void notifyObservers() { //循环所有注册的观察者 for (Observer observer : observers) { /** * 规则: * 小明的女朋友: 下雨:通知,其他条件不通知 * 小明的妈妈: 下雨、下雪:通知,其他条件不通知 */ //晴天:do nothing //下雨 if("下雨".equals(this.getWeatherContent())){ if("小明的女朋友".equals(observer.getObserverName())){ observer.update(this); } if("小明的妈妈".equals(observer.getObserverName())){ observer.update(this); } } //下雪 if("下雪".equals(this.getWeatherContent())){ if("小明的妈妈".equals(observer.getObserverName())){ observer.update(this); } } } } public String getWeatherContent() { return weatherContent; } public void setWeatherContent(String weatherContent) { this.weatherContent = weatherContent; this.notifyObservers(); } }
Client:
public class Client { public static void main(String[] args) { // 1.创建目标 ConcreteWeatherSubject weatherSubject = new ConcreteWeatherSubject(); // 2.创建观察者 ConcreteObserver observerGirl = new ConcreteObserver(); observerGirl.setObserverName("小明的女朋友"); observerGirl.setRemindThing("下雨了,出门记得带伞"); ConcreteObserver observerMon = new ConcreteObserver(); observerMon.setObserverName("小明的妈妈"); observerMon.setRemindThing("不管下雨还是下雪,都不出门了"); // 3.注册观察者到观察者列表中 weatherSubject.attach(observerGirl); weatherSubject.attach(observerMon); // 4.在目标处发布天气:会自动通知到已经注册的观察者中 weatherSubject.setWeatherContent("下雨"); } }
注:本文部分内容来自慕课网
原文地址:https://www.jianshu.com/p/74f83d85084d
相关推荐
-
Java应用集群下的定时任务处理方案(mysql) Java基础
2019-9-4
-
Java 项目热部署,节省构建时间的正确姿势 Java基础
2020-6-13
-
Java基础中一些容易被忽视的语法小细节总结 Java基础
2019-5-14
-
JDK容器学习之List: CopyOnWriteArrayList,ArrayList,LinkedList对比 Java基础
2020-5-30
-
了解一波网页端微信是如何登录的 Java基础
2019-4-1
-
Java性能优化之String字符串优化 Java基础
2019-5-5
-
java并发编程的艺术-第四章之Java并发基础 Java基础
2020-5-30
-
java并发机制锁的类型和实现 Java基础
2019-6-8
-
UML图中类之间的关系 Java基础
2019-7-24
-
springboot+swagger接口文档企业实践(下) Java基础
2020-6-18