設計模式詳解(二)----------觀察者模式

来源:http://www.cnblogs.com/ZhangHaoShuaiGe/archive/2017/11/15/7840131.html
-Advertisement-
Play Games

有一個模式可以幫助你的對象知悉現況,不會錯過該對象感興趣的事,對象甚至在運行時可以決定是否要繼續被通知,如果一個對象狀態的改變需要通知很多對這個對象關註的一系列對象,就可以使用觀察者模式 。觀察者模式也是JDK中使用最多的一個設計模式,而我們本章討論的就是它。 那麼首先,我們先來看一看此模式的定義: ...


有一個模式可以幫助你的對象知悉現況,不會錯過該對象感興趣的事,對象甚至在運行時可以決定是否要繼續被通知,如果一個對象狀態的改變需要通知很多對這個對象關註的一系列對象,就可以使用觀察者模式 。觀察者模式也是JDK中使用最多的一個設計模式,而我們本章討論的就是它。

那麼首先,我們先來看一看此模式的定義:

                 定義:觀察者模式(有時又被稱為發佈-訂閱模式、模型-視圖模式、源-收聽者模式或從屬者模式)是軟體設計模式的一種。在此種模式中,一個目標物件管理所有相依於它的觀察者物件,並且在它本身的狀態改變時主動發出通知。這通常透過呼叫各觀察者所提供的方法來實現。此種模式通常被用來實作事件處理系統

接下來請LZ用一個例子展出今天的內容:

ex:有這樣一個需求,劉能訂閱了報刊的報紙,以後每周只要有新報紙發佈,劉能就能得到報紙,而劉能也可以選擇退訂,這時將不再得到報紙。報刊(Newspapers) ,訂閱者劉能(Subscriber)。

我們來分析一下,首先,訂閱者可以訂閱報刊,也可以退訂,所以我們需要寫兩個方法,分別為 registerObserver(訂閱) ,removeObserver(退訂),我們用一個list來存訂閱者,

public class Newspapers {//報刊
    private List<Subscriber> subscriber = new ArrayList<Subscriber>();
    private String paper;
    
    public void registerObserver(Subscriber subs){//訂閱
        subscriber.add(subs);
    }
    
    public void removeObserver(Subscriber subs){//退訂
            subscriber.remove(subs);

    }
   
    public void setPaper(String paper){//這裡控制新出報紙
        this.paper = paper;
        measurementsChanged();//一旦有新報紙出現調用此方法
    }
public void measurementsChanged(){ subscriber.get(0).update(paper); } }

 

 

在訂閱者中,我們設計成只訂閱不退訂,我們需要一個Newspapers對象,用來訂閱,

class Subscriber{//訂閱者劉能
    private String paper;
    private Newspapers newspapers;
    public Subscriber(Newspapers newspapers) {
        this.newspapers = newspapers;
        newspapers.registerObserver(this);
    }
    public void update(String paper){
        this.paper = paper;
        display();
    }
    public void display(){
        System.out.println("paper :"+paper);
    }
    
}

接著我們寫一個測試類

public class Test {
    public static void main(String[] args) {
        Newspapers newspapers = new Newspapers();
        Subscriber subscriber = new Subscriber(newspapers);
        newspapers.setPaper("今日說法");
    }
}

測試結果:報刊出現新的報紙,然後提醒它的所有訂閱者

 

這個功能到此就算是實現完了,但是,這時候我們接到了報社的電話:報社告訴我們謝大腳和王小蒙也訂閱了報紙,當報社有新的報紙時我們不僅要給劉能發,還要給謝大腳和王小蒙也發一份=。=

我們發現,這時我們需要再分別寫兩個類來代表謝大腳和王小蒙,但是Newspapers類中我們怎麼來寫?我們的訂閱和註冊已經寫死了只支持劉能!其實訂閱報紙本就是一個一對多的關係,而我們經過了上一章的學習,發現這種實現方式有很多地方是不對的,針對具體實現編程,會導致我們以後在訂閱或退訂時必須修改程式,所以我們應該想到使用介面。在這裡,我們把報刊稱為“主題”(Subject),而訂閱者稱為

“觀察者(Observer)”,讓我們來看的更仔細點:

主題和觀察者定義了一對多的關係,觀察者依賴於此主題,只要主題狀態一有改變,觀察者就會被通知。根據通知的風格,觀察者可能因此而更新。

① 我們定義一個主題介面Subject,它除了註冊和撤銷方法之外,還擁有notifyObservers()方法,此方法用於在狀態改變時更新所有當前觀察者。對象只有使用此介面註冊為觀察者,或者把自己從觀察者中刪除。

② 接下來我們定義一個觀察者介面Observer,所有潛在的觀察者必須實現此介面,它只擁有一個公共方法update,當主題狀態改變時,它被調用。具體的觀察者可以是實現此介面的任意類,觀察者必須註冊具體主題,以便接受更新。

③此外,我們還需要一個介面DisplayElement用來顯示

下麵我們來實現一下:

public interface Subject{
    public void registerObserver(Observer observer);//註冊觀察者
    public void removeObserver(Observer observer);//移除觀察者
    public void notifyObservers();//當主題狀態改變,調用這個方法通知觀察者
}
public interface Observer {
    public void update(String paper);//此方法當產生改變的時候由主題調用,
}
public interface DisplayElement{
     public void display();//顯示
}

下麵是主題實現類:

//主題(報社)
public class Newspapers implements Subject{
    private ArrayList<Observer> observers ;
    private String paper;
    
    public Newspapers() {
        observers = new ArrayList<Observer>();
    }
    
    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
        
    }

    @Override
    public void removeObserver(Observer observer) {
        int index = observers.indexOf(observer);
        if(index >= 0){
            observers.remove(index);
        }
        
    }
    
    public void setMeasurements(String paper){
        this.paper = paper;
        measurementsChanged();//當有新報紙時調用此方法
    }
    
    public void measurementsChanged(){
        notifyObservers();
    }
    
    @Override
    public void notifyObservers() {//調用此方法通知所有觀察者
        for(int i=0;i<observers.size();i++){
            Observer observer = observers.get(i);
            observer.update(paper);//更新報紙
        }
        
    }

}

觀察者:

//觀察者劉能
public class SubscriberLiuNeng implements Observer,DisplayElement{
    private String paper;
    private Subject subject;
    
    public SubscriberLiuNeng(Subject subject) {
        this.subject = subject;
        subject.registerObserver(this);
    }
    
    @Override
    public void update(String paper) {
        this.paper = paper;
        display();
    }
    
    public void display(){
        System.out.println("劉能,"+paper+"新報紙出來了");
    }
    
}
//觀察者謝大腳
class SubscriberXieDaJiao implements Observer,DisplayElement{
    private String paper;
    private Subject subject;
    
    public SubscriberXieDaJiao(Subject subject) {
        this.subject = subject;
        subject.registerObserver(this);
    }
    
    @Override
    public void update(String paper) {
        this.paper = paper;
        display();
    }
    
    public void display(){
        System.out.println("謝大腳,"+paper+"新報紙出來了");
    }
    
}
//觀察者王小蒙
class SubscriberWangXiaoMeng implements Observer,DisplayElement{
    private String paper;
    private Subject subject;
    
    public SubscriberWangXiaoMeng(Subject subject) {
        this.subject = subject;
        subject.registerObserver(this);
    }
    
    @Override
    public void update(String paper) {
        this.paper = paper;
        display();
    }
    
    public void display(){
        System.out.println("王小蒙,"+paper+"新報紙出來了");
    }
    
}

這裡我們保存subject引用是為瞭如果想要取消註冊會非常方便。接著我們寫個測試類:

    public static void main(String[] args) {
        Newspapers newspapers = new Newspapers();
        SubscriberLiuNeng subscriberLiuNeng = new SubscriberLiuNeng(newspapers);
        SubscriberXieDaJiao subscriberXieDaJiao = new SubscriberXieDaJiao(newspapers);
        SubscriberWangXiaoMeng subscriberWangXiaoMeng = new SubscriberWangXiaoMeng(newspapers);
        newspapers.setMeasurements("今日說法");
    }

結果:

 

我們可以發現,通過這種實現方式,主題和觀察者之間依然可以互相交互,但是並不清除彼此的細節。關於觀察者的一切,主題只知道觀察者實現了某個介面(也就是Observer介面)。主題不需要知道觀察者的具體類是誰,做了些神馬或其他任何細節。任何時候我們都可以增加新的觀察者,因為主題唯一依賴的東西是一個實習Observer介面的對象列表,所以我們可以隨時增加觀察者。事實上,在運行時我們可以用新的觀察者取代現有的觀察者,主題不會受到任何影響,同樣的,也可以在任何時候刪除某些觀察者。

當有新類型的觀察者出現時,主題的代碼不需要修改,假如我們有個新的具體類需要

 當觀察者,我們不需要為了相容新類型而修改主題的代碼,所有要做的就是在新的類里實現此觀察者介面,然後註冊為觀察者即可。主題不在乎別的,它只會發送通知給所有實現了觀察者藉口的對象。

我們可以獨立地復用主題或觀察者,如果我們在其他地方需要使用主題或觀察者,可以輕易地復用,因為二者並非緊耦合。下麵我們來看一下觀察者模式的類圖,是不是很熟悉?

回到上面的例子,其實這是一個“推”的例子,數據是由主題推給觀察者的,而不是由觀察者自己獲取,其實Java有自己內置的Observer模式不僅支持“推”,還支持“拉”。

java.util包內包含最基本的Observer介面(相當於上面的Subject介面)與Observable類(相當於上面寫的Observer介面)

而java內置的觀察者模式運作方式,和我們的實現類似,但有一些小差異,其中最明顯的差異是Newspapers(也就是我們的主題)現在擴展自Observable類,並繼承到一些增加,刪除,通知觀察者的方法(以及其他的方法),廢話不多說,直接上Observable類源碼:

public class Observable {
    private boolean changed = false;
    private Vector 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) {
        /*
         * a temporary array buffer, used as a snapshot of the state of
         * current Observers.
         */
        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();
    }
}

註意setChanged()方法用來標記狀態已經改變的事實,好讓notifyObservers()知道當它被調用時應該更新觀察者。如果在notifyObservers()之前沒有先調用setChanged()方法,那麼觀察者就不會被通知。這樣做有其必要性,setChanged()方法可以讓你在更新觀察者時,有更多的彈性,你可以更適當的通知觀察者。比方說,我們的報社是如此的敏銳,以致於報紙剛寫了十分之一就會更新,這會導致Newspaper對象持續不斷的通知觀察者,我們顯然不願意這種情況發生,我們希望做到的是當報紙剛剛印刷出來,我們才更新,就可以在報紙剛印刷出的時候調用setChanged()方法,進而有效的更新。

下麵是我們使用java內置的觀察者模式實現:

import java.util.ArrayList;
import java.util.Observable;
//主題
public class Newspapers extends Observable{
    private String paper;
    
    public void setMeasurements(String paper){
        this.paper = paper;
        measurementsChanged();//當有新報紙時調用此方法
    }
    
    public void measurementsChanged(){
        setChanged();
        notifyObservers();
    }

    public String getPaper() {
        return paper;
    }
}

這裡我們不需要創建list數據結構來存儲觀察者了,而在measurementsChanged()方法中我們不需要調用notifyObservers()方法來傳送數據對象,因為這次我們採用的是“拉”的做法,由觀察者自己獲取。所以我們為此也提供了getPaper方法。

接下來看觀察者:

//觀察者劉能
public class SubscriberLiuNeng implements Observer,DisplayElement{
    private String paper;
    private Observable observable;
    
    public SubscriberLiuNeng(Observable observable) {
        this.observable = observable;
        observable.addObserver(this);
    }
    
    @Override
    public void update(Observable obs, Object arg) {
        if(obs instanceof Newspapers){
            Newspapers newspapers =(Newspapers) obs;
            this.paper = newspapers.getPaper();
            display();
        }
        
    }
    
    public void display(){
        System.out.println("劉能,"+paper+"新報紙出來了");
    }
    
}

//觀察者劉能
 class SubscriberXieDaJiao implements Observer,DisplayElement{
    private String paper;
    private Observable observable;
    
    public SubscriberXieDaJiao(Observable observable) {
        this.observable = observable;
        observable.addObserver(this);
    }
    
    @Override
    public void update(Observable obs, Object arg) {
        if(obs instanceof Newspapers){
            Newspapers newspapers =(Newspapers) obs;
            this.paper = newspapers.getPaper();
            display();
        }
        
    }
    
    public void display(){
        System.out.println("謝大腳,"+paper+"新報紙出來了");
    }
    
}

//觀察者劉能
 class SubscriberWangXiaoMeng implements Observer,DisplayElement{
    private String paper;
    private Observable observable;
    
    public SubscriberWangXiaoMeng(Observable observable) {
        this.observable = observable;
        observable.addObserver(this);
    }
    
    @Override
    public void update(Observable obs, Object arg) {
        if(obs instanceof Newspapers){
            Newspapers newspapers =(Newspapers) obs;
            this.paper = newspapers.getPaper();
            display();
        }
        
    }
    
    public void display(){
        System.out.println("王小蒙,"+paper+"新報紙出來了");
    }
    
}

 

可以看到,在update方法中,我們先確定可觀察者屬於Newspapers類型,然後調用get方法獲取paper接下來利用display()方法顯示出來。測試類與之前一樣,但是為了能讓大家看的明白點,這裡我再寫一遍:

    public static void main(String[] args) {
        Newspapers newspapers = new Newspapers();
        SubscriberLiuNeng subscriberLiuNeng = new SubscriberLiuNeng(newspapers);
        SubscriberXieDaJiao subscriberXieDaJiao = new SubscriberXieDaJiao(newspapers);
        SubscriberWangXiaoMeng subscriberWangXiaoMeng = new SubscriberWangXiaoMeng(newspapers);
        newspapers.setMeasurements("今日說法");
    }

註意看結果:

 這是怎麼回事?文字輸出順序居然不一樣了。這其實是因為Observable實現了它的notifyObservers()方法,這導致了通知觀察者的次序不同於我們先前的次序,其實誰都沒有錯,只是雙方選擇不同的方式實現罷了。

但是,如果我們的代碼依賴這樣的次序,就是錯的,為什麼呢?因為一旦觀察者/可觀察者的實現有所改變,通知次序就會改變,很可能產生錯誤的結果,這絕對不是我們所認為的松耦合。

java.util.Observable的黑暗面

想必你早已註意到了,可觀察者是一個“類”而不是一個“介面”,更糟的是,它甚至沒有實現一個介面。而且,它的實現有許多問題,限制了它的使用和復用,雖然它提供了有用的功能,但是LZ依然想提醒大家註意一個事實:Observable是一個類

這違背了我們的原則,會造成什麼問題呢?你必須設計一個類去繼承它,如果某類想同時具有Observable類和另一個基類的行為,就會陷入兩難,因為java不支持多繼承。再者,因為沒有Observable介面,你無法建立自己的實現,和java內置的ObserverAPI搭配使用,也無法將java.util的實現換成另一套做法的實現(比方說,Observable將關鍵的方法保護起來,通過上面LZ放出的源碼,你會發現setChanged()方法是protected類型的)。這意味著:除非你繼承自Observable,否則你無法創建Observable示例並組合到自己的對象中,這違反了我們的設計原則:多用組合,少用繼承

所以,如果你能夠擴展Observable,那麼它“可能”可以符合你的要求,否則,你就需要像LZ一開始那樣自己手動實現這一整套觀察者模式,不過都無所謂,不管使用哪一種方法,我們都已經熟悉了觀察者模式了。

通過LZ的講解,想必各位都已經明瞭了觀察者模式的具體實現方式,也清楚了它是如何做到解耦的。觀察者模式定義了對象之間的一對多依賴,這樣依賴,當一個對象改變狀態時,它的所有依賴者都會收到通知並自動更新,另外,觀察者模式分離了觀察者和被觀察者二者的責任,這樣讓類之間各自維護自己的功能,專註於自己的功能,會提高系統的可維護性和可重用性.

下麵我們來升華一下觀察者模式。在JDK中有這樣一個類PropertyChangeSupport,

它用來監聽bean的屬性是否發生改變,當bean的屬性發生變化時,使用PropertyChangeSupport對象的firePropertyChange方法,它會將一個事件發送給所有已經註冊的監聽器。該方法有三個參數:屬性的名字、舊的值以及新的值。屬性的值必須是對象,如果是簡單數據類型,則必須進行包裝。我們來看一下PropertyChangeSupport的源代碼(由於源代碼過長,LZ只裁取了一部分這裡有用的)

public class PropertyChangeSupport implements Serializable {
    private PropertyChangeListenerMap map = new PropertyChangeListenerMap();

    public PropertyChangeSupport(Object sourceBean) {
        if (sourceBean == null) {
            throw new NullPointerException();
        }
        source = sourceBean;
    }

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        if (listener == null) {
            return;
        }
        if (listener instanceof PropertyChangeListenerProxy) {
            PropertyChangeListenerProxy proxy =
                   (PropertyChangeListenerProxy)listener;
            // Call two argument add method.
            addPropertyChangeListener(proxy.getPropertyName(),
                                      proxy.getListener());
        } else {
            this.map.add(null, listener);
        }
    }

    public void removePropertyChangeListener(PropertyChangeListener listener) {
        if (listener == null) {
            return;
        }
        if (listener instanceof PropertyChangeListenerProxy) {
            PropertyChangeListenerProxy proxy =
                    (PropertyChangeListenerProxy)listener;
            // Call two argument remove method.
            removePropertyChangeListener(proxy.getPropertyName(),
                                         proxy.getListener());
        } else {
            this.map.remove(null, listener);
        }
    }

    public PropertyChangeListener[] getPropertyChangeListeners() {
        return this.map.getListeners();
    }

    public void addPropertyChangeListener(
                String propertyName,
                PropertyChangeListener listener) {
        if (listener == null || propertyName == null) {
            return;
        }
        listener = this.map.extract(listener);
        if (listener != null) {
            this.map.add(propertyName, listener);
        }
    }

    public void removePropertyChangeListener(
                String propertyName,
                PropertyChangeListener listener) {
        if (listener == null || propertyName == null) {
            return;
        }
        listener = this.map.extract(listener);
        if (listener != null) {
            this.map.remove(propertyName, listener);
        }
    }

    public PropertyChangeListener[] getPropertyChangeListeners(String propertyName) {
        return this.map.getListeners(propertyName);
    }

    public void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
        if (oldValue == null || newValue == null || !oldValue.equals(newValue)) {
            firePropertyChange(new PropertyChangeEvent(this.source, propertyName, oldValue, newValue));
        }
    }
    
    public void firePropertyChange(PropertyChangeEvent event) {
        Object oldValue = event.getOldValue();
        Object newValue = event.getNewValue();
        if (oldValue == null || newValue == null || !oldValue.equals(newValue)) {
            String name = event.getPropertyName();

            PropertyChangeListener[] common = this.map.get(null);
            PropertyChangeListener[] named = (name != null)
                        ? this.map.get(name)
                        : null;

            fire(common, event);
            fire(named, event);
        }
    }
    
    private static void fire(PropertyChangeListener[] listeners, PropertyChangeEvent event) {
        if (listeners != null) {
            for (PropertyChangeListener listener : listeners) {
                listener.propertyChange(event);
            }
        }
    }
}

 

LZ帶著大家來一步步分析源碼,從源碼中不難看出map的類型是<String,PropertyChangeListener>,而這個PropertyChangeListener我們稍後說。首先構造器不用多說了,獲取一個bean。而addPropertyChangeListener方法大家有沒有覺得眼熟?這個就相當於我們的註冊,PropertyChangeListenerProxy是PropertyChangeListener的實現類

而PropertyChangeListener相當於觀察者介面,我們另觀察者實現此介面或者繼承PropertyChangeListenerProxy類都可以,removePropertyChangeListener相當於撤銷,下麵我們看最重要的方法firePropertyChange(),這裡的oldSource是改變之前的屬性值,newValue是改變後的屬性值,而propertyName相當於屬性名,也就是key。這裡將參數封裝到了PropertyChangeEvent類中調用firePropertyChange(PropertyChangeEvent event)方法,我們發現,最後遍歷所有的觀察者,調用觀察者的propertyChange()方法,而這個方法是PropertyChangeListener 介面中的,不管我們採用實現介面還是繼承PropertyChangeListenerProxy的方式,都需要我們親自實現這個方法。

public interface PropertyChangeListener extends java.util.EventListener {

    /**
     * This method gets called when a bound property is changed.
     * @param evt A PropertyChangeEvent object describing the event source
     *          and the property that has changed.
     */

    void propertyChange(PropertyChangeEvent evt);

}

下麵我們來寫一個demo:

建立一個MyBean,相當於我們說的主題,我們利用PropertyChangeSupport構造器將bean對象傳入。

public class MyBean{
    private String source = "hello";
    private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);

    public void setSource(String newSource) {
        String oldSource = source;
        source = newSource;
        propertyChangeSupport.firePropertyChange("source", oldSource, newSource);
    }
    
    public String getSource() {
        return source;
    }

    public void addPropertyChangeListener(PropertyChangeListener listener){
        propertyChangeSupport.addPropertyChangeListener(listener);
    }
    
    public void removePropertyChangeListener(PropertyChangeListener listener){
        propertyChangeSupport.removePropertyChangeListener(listener);
    }
}

這裡需要我們手動實現addPropertyChangeListener和removePropertyChangeListener方法,因為我們的主題並沒有通過繼承其他類而獲得這兩個方法。

下麵我們寫出測試類來監聽主題的Source屬性是否改變:

public class ChangeListener implements PropertyChangeListener{
    public static void main(String[] args) {
        MyBean mybean = new MyBean();
        mybean.addPropertyChangeListener(new ChangeListener());
        mybean.setSource("WOW");
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        MyBean mybean = (MyBean) evt.getSource();
        if(evt.getPropertyName().equals(mybean.getSource()));
                System.out.println("BeanTest 的 name 屬性變化!");
        }
    
}

結果:

 

下期預告:裝飾者模式

 

 

 

 

 

 

 

 

 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Spring MVC框架是一個開源的Java平臺,為開發強大的基於Java的Web應用程式提供全面的基礎架構支持非常容易和非常快速。 Spring web MVC框架提供了MVC(模型 - 視圖 - 控制器)架構和用於開發靈活和鬆散耦合的Web應用程式的組件。 MVC模式導致應用程式的不同方面(輸入 ...
  • 這是我在一個面試初級工程師的時候遇到的一個問題,覺得在項目中都沒怎麼遇到. 一.編寫一個Student對象 二.編寫測試類主要用到得時Collections這個輔助工具 1.Collections.sort方法支持對對象進組排序 2.使用方法:只要實現Comparator方法. 3.實現目的:主要是 ...
  • 要生成對象並通過名稱空間註入屬性的類 代碼如下: XML配置文件寫法如下: 生成對象及屬性值調用方法,代碼如下: ...
  • 作者:NiceCui 本文謝絕轉載,如需轉載需徵得作者本人同意,謝謝。 本文鏈接:http://www.cnblogs.com/NiceCui/p/7835122.html 郵箱:[email protected] 日期:2017-11-14 22:26 將枚舉作為參數傳遞在複雜的服務調用中也是很常 ...
  • 鍵盤操作 功能 Alt + / 語句或變數名自動補全 Ctrl + Shift + F 語句格式化 Ctrl + / 單行註釋(或取消單行註釋) Ctrl + Shift + / 多行註釋 Ctrl + Shift + \ 取消多行註釋 Ctrl + Shift + o 快捷導包 Alt + 上下箭 ...
  • Given a positive integer N, your task is to calculate the sum of the positive integers less than N which are not coprime to N. A is said to be coprime ...
  • 記錄下麵試裡面遇到的一些java盲區,一方面掃描自己的知識盲區,一方面也可以給後面面試的朋友一些警示,以免面試的時候出現不知道的尷尬情況。 提出問題:父類靜態屬性,父類屬性,父類構造方法,子類靜態屬性, 子類屬性,子類構造方法的初始化順序? 提出猜想:父類靜態屬性=> 父類屬性=> 父類構造方法= ...
  • 靜態方法和實例方法的區別主要體現在兩個方面: 在外部調用靜態方法時,可以使用"類名.方法名"的方式,也可以使用"對象名.方法名"的方式。而實例方法只有後面這種方式。也就是說,調用靜態方法可以無需創建對象。 靜態方法在訪問本類的成員時,只允許訪問靜態成員(即靜態成員變數和靜態方法),而不允許訪問實例成 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...