行為型SEQ1 - 職責(zé)鏈模式 Chain of Responsibility Pattern

【學(xué)習(xí)難度:★★★☆☆,使用頻率:★★☆☆☆】
直接出處:職責(zé)鏈模式
梳理和學(xué)習(xí):https://github.com/BruceOuyang/boy-design-pattern
簡書日期: 2018/03/15
簡書首頁:http://www.itdecent.cn/p/0fb891a7c5ed

請求的鏈?zhǔn)教幚怼氊?zé)鏈模式(一)

“一對二”,“過”,“過”……這聲音熟悉嗎?你會想到什么?對!紙牌。在類似“斗地主”這樣的紙牌游戲中,某人出牌給他的下家,下家看看手中的牌,如果要不起上家的牌則將出牌請求再轉(zhuǎn)發(fā)給他的下家,其下家再進(jìn)行判斷。一個(gè)循環(huán)下來,如果其他人都要不起該牌,則最初的出牌者可以打出新的牌。在這個(gè)過程中,牌作為一個(gè)請求沿著一條鏈在傳遞,每一位紙牌的玩家都可以處理該請求。在設(shè)計(jì)模式中,我們也有一種專門用于處理這種請求鏈?zhǔn)絺鬟f的模式,它就是本章將要介紹的職責(zé)鏈模式。

16.1 采購單的分級審批

Sunny軟件公司承接了某企業(yè)SCM(Supply Chain Management,供應(yīng)鏈管理)系統(tǒng)的開發(fā)任務(wù),其中包含一個(gè)采購審批子系統(tǒng)。該企業(yè)的采購審批是分級進(jìn)行的,即根據(jù)采購金額的不同由不同層次的主管人員來審批,主任可以審批5萬元以下(不包括5萬元)的采購單,副董事長可以審批5萬元至10萬元(不包括10萬元)的采購單,董事長可以審批10萬元至50萬元(不包括50萬元)的采購單,50萬元及以上的采購單就需要開董事會討論決定。如圖16-1所示:

圖16-1 采購單分級審批示意圖

如何在軟件中實(shí)現(xiàn)采購單的分級審批?Sunny軟件公司開發(fā)人員提出了一個(gè)初始解決方案,在系統(tǒng)中提供一個(gè)采購單處理類PurchaseRequestHandler用于統(tǒng)一處理采購單,其框架代碼如下所示:

//采購單處理類  
class PurchaseRequestHandler {  
    //遞交采購單給主任  
    public void sendRequestToDirector(PurchaseRequest request) {  
        if (request.getAmount() < 50000) {  
            //主任可審批該采購單  
            this.handleByDirector(request);  
        }  
        else if (request.getAmount() < 100000) {  
            //副董事長可審批該采購單  
            this.handleByVicePresident(request);  
        }  
        else if (request.getAmount() < 500000) {  
            //董事長可審批該采購單  
            this.handleByPresident(request);  
        }  
        else {  
            //董事會可審批該采購單  
            this.handleByCongress(request);  
        }  
    }  

    //主任審批采購單  
    public void handleByDirector(PurchaseRequest request) {  
        //代碼省略  
    }  

    //副董事長審批采購單  
    public void handleByVicePresident(PurchaseRequest request) {  
        //代碼省略  
    }  

    //董事長審批采購單  
    public void handleByPresident(PurchaseRequest request) {  
        //代碼省略  
    }  

    //董事會審批采購單  
    public void handleByCongress(PurchaseRequest request) {  
        //代碼省略  
    }  
}

問題貌似很簡單,但仔細(xì)分析,發(fā)現(xiàn)上述方案存在如下幾個(gè)問題:

(1)PurchaseRequestHandler類較為龐大,各個(gè)級別的審批方法都集中在一個(gè)類中,違反了“單一職責(zé)原則”,測試和維護(hù)難度大。

(2)如果需要增加一個(gè)新的審批級別或調(diào)整任何一級的審批金額和審批細(xì)節(jié)(例如將董事長的審批額度改為60萬元)時(shí)都必須修改源代碼并進(jìn)行嚴(yán)格測試,此外,如果需要移除某一級別(例如金額為10萬元及以上的采購單直接由董事長審批,不再設(shè)副董事長一職)時(shí)也必須對源代碼進(jìn)行修改,違反了“開閉原則”。

(3)審批流程的設(shè)置缺乏靈活性,現(xiàn)在的審批流程是“主任-->副董事長-->董事長-->董事會”,如果需要改為“主任-->董事長-->董事會”,在此方案中只能通過修改源代碼來實(shí)現(xiàn),客戶端無法定制審批流程。

如何針對上述問題對系統(tǒng)進(jìn)行改進(jìn)?Sunny公司開發(fā)人員迫切需要一種新的設(shè)計(jì)方案,還好有職責(zé)鏈模式,通過使用職責(zé)鏈模式我們可以最大程度地解決這些問題,下面讓我們正式進(jìn)入職責(zé)鏈模式的學(xué)習(xí)。

請求的鏈?zhǔn)教幚怼氊?zé)鏈模式(二)

16.2 職責(zé)鏈模式概述

很多情況下,在一個(gè)軟件系統(tǒng)中可以處理某個(gè)請求的對象不止一個(gè),例如SCM系統(tǒng)中的采購單審批,主任、副董事長、董事長和董事會都可以處理采購單,他們可以構(gòu)成一條處理采購單的鏈?zhǔn)浇Y(jié)構(gòu),采購單沿著這條鏈進(jìn)行傳遞,這條鏈就稱為職責(zé)鏈。職責(zé)鏈可以是一條直線、一個(gè)環(huán)或者一個(gè)樹形結(jié)構(gòu),最常見的職責(zé)鏈?zhǔn)侵本€型,即沿著一條單向的鏈來傳遞請求。鏈上的每一個(gè)對象都是請求處理者,職責(zé)鏈模式可以將請求的處理者組織成一條鏈,并讓請求沿著鏈傳遞,由鏈上的處理者對請求進(jìn)行相應(yīng)的處理,客戶端無須關(guān)心請求的處理細(xì)節(jié)以及請求的傳遞,只需將請求發(fā)送到鏈上即可,實(shí)現(xiàn)請求發(fā)送者和請求處理者解耦。

職責(zé)鏈模式定義如下: 職責(zé)鏈模式(Chain of Responsibility Pattern):避免請求發(fā)送者與接收者耦合在一起,讓多個(gè)對象都有可能接收請求,將這些對象連接成一條鏈,并且沿著這條鏈傳遞請求,直到有對象處理它為止。職責(zé)鏈模式是一種對象行為型模式。

職責(zé)鏈模式結(jié)構(gòu)的核心在于引入了一個(gè)抽象處理者。職責(zé)鏈模式結(jié)構(gòu)如圖16-2所示:

圖16-2 職責(zé)鏈模式結(jié)構(gòu)圖

在職責(zé)鏈模式結(jié)構(gòu)圖中包含如下幾個(gè)角色:

  • Handler(抽象處理者):它定義了一個(gè)處理請求的接口,一般設(shè)計(jì)為抽象類,由于不同的具體處理者處理請求的方式不同,因此在其中定義了抽象請求處理方法。因?yàn)槊恳粋€(gè)處理者的下家還是一個(gè)處理者,因此在抽象處理者中定義了一個(gè)抽象處理者類型的對象(如結(jié)構(gòu)圖中的successor),作為其對下家的引用。通過該引用,處理者可以連成一條鏈。

  • ConcreteHandler(具體處理者):它是抽象處理者的子類,可以處理用戶請求,在具體處理者類中實(shí)現(xiàn)了抽象處理者中定義的抽象請求處理方法,在處理請求之前需要進(jìn)行判斷,看是否有相應(yīng)的處理權(quán)限,如果可以處理請求就處理它,否則將請求轉(zhuǎn)發(fā)給后繼者;在具體處理者中可以訪問鏈中下一個(gè)對象,以便請求的轉(zhuǎn)發(fā)。

在職責(zé)鏈模式里,很多對象由每一個(gè)對象對其下家的引用而連接起來形成一條鏈。請求在這個(gè)鏈上傳遞,直到鏈上的某一個(gè)對象決定處理此請求。發(fā)出這個(gè)請求的客戶端并不知道鏈上的哪一個(gè)對象最終處理這個(gè)請求,這使得系統(tǒng)可以在不影響客戶端的情況下動(dòng)態(tài)地重新組織鏈和分配責(zé)任。

職責(zé)鏈模式的核心在于抽象處理者類的設(shè)計(jì),抽象處理者的典型代碼如下所示:

abstract class Handler {
    
    /**
     * 維持對下家的引用
     */  
    protected Handler successor;  

    public void setSuccessor(Handler successor) {  
        this.successor=successor;  
    }  

    public abstract void handleRequest(String request);  
}

上述代碼中,抽象處理者類定義了對下家的引用對象,以便將請求轉(zhuǎn)發(fā)給下家,該對象的訪問符可設(shè)為protected,在其子類中可以使用。在抽象處理者類中聲明了抽象的請求處理方法,具體實(shí)現(xiàn)交由子類完成。

具體處理者是抽象處理者的子類,它具有兩大作用:第一是處理請求,不同的具體處理者以不同的形式實(shí)現(xiàn)抽象請求處理方法handleRequest();第二是轉(zhuǎn)發(fā)請求,如果該請求超出了當(dāng)前處理者類的權(quán)限,可以將該請求轉(zhuǎn)發(fā)給下家。具體處理者類的典型代碼如下:

class ConcreteHandler extends Handler {  
    public void handleRequest(String request) {  
        if (請求滿足條件) {  
            //處理請求  
        }  
        else {  
            this.successor.handleRequest(request);  //轉(zhuǎn)發(fā)請求  
        }  
    }  
}

在具體處理類中通過對請求進(jìn)行判斷可以做出相應(yīng)的處理。

需要注意的是,職責(zé)鏈模式并不創(chuàng)建職責(zé)鏈,職責(zé)鏈的創(chuàng)建工作必須由系統(tǒng)的其他部分來完成,一般是在使用該職責(zé)鏈的客戶端中創(chuàng)建職責(zé)鏈。職責(zé)鏈模式降低了請求的發(fā)送端和接收端之間的耦合,使多個(gè)對象都有機(jī)會處理這個(gè)請求。

思考

如何在客戶端創(chuàng)建一條職責(zé)鏈?

請求的鏈?zhǔn)教幚怼氊?zé)鏈模式(三)

16.3 完整解決方案

為了讓采購單的審批流程更加靈活,并實(shí)現(xiàn)采購單的鏈?zhǔn)絺鬟f和處理,Sunny公司開發(fā)人員使用職責(zé)鏈模式來實(shí)現(xiàn)采購單的分級審批,其基本結(jié)構(gòu)如圖16-3所示:

圖16-3 采購單分級審批結(jié)構(gòu)圖

在圖16-3中,抽象類Approver充當(dāng)抽象處理者(抽象傳遞者),Director、VicePresident、President和Congress充當(dāng)具體處理者(具體傳遞者),PurchaseRequest充當(dāng)請求類。完整代碼如下所示:

//采購單:請求類  
class PurchaseRequest {  
    private double amount;  //采購金額  
    private int number;  //采購單編號  
    private String purpose;  //采購目的  

    public PurchaseRequest(double amount, int number, String purpose) {  
        this.amount = amount;  
        this.number = number;  
        this.purpose = purpose;  
    }  

    public void setAmount(double amount) {  
        this.amount = amount;  
    }  

    public double getAmount() {  
        return this.amount;  
    }  

    public void setNumber(int number) {  
        this.number = number;  
    }  

    public int getNumber() {  
        return this.number;  
    }  

    public void setPurpose(String purpose) {  
        this.purpose = purpose;  
    }  

    public String getPurpose() {  
        return this.purpose;  
    }  
}  

//審批者類:抽象處理者  
abstract class Approver {  
    protected Approver successor; //定義后繼對象  
    protected String name; //審批者姓名  

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

    //設(shè)置后繼者  
    public void setSuccessor(Approver successor) {   
        this.successor = successor;  
    }  

    //抽象請求處理方法  
    public abstract void processRequest(PurchaseRequest request);  
}  

//主任類:具體處理者  
class Director extends Approver {  
    public Director(String name) {  
        super(name);  
    }  

    //具體請求處理方法  
    public void processRequest(PurchaseRequest request) {  
        if (request.getAmount() < 50000) {  
            System.out.println("主任" + this.name + "審批采購單:" + request.getNumber() + ",金額:" + request.getAmount() + "元,采購目的:" + request.getPurpose() + "。");  //處理請求  
        }  
        else {  
            this.successor.processRequest(request);  //轉(zhuǎn)發(fā)請求  
        }     
    }  
}  

//副董事長類:具體處理者  
class VicePresident extends Approver {  
    public VicePresident(String name) {  
        super(name);  
    }  

    //具體請求處理方法  
    public void processRequest(PurchaseRequest request) {  
        if (request.getAmount() < 100000) {  
            System.out.println("副董事長" + this.name + "審批采購單:" + request.getNumber() + ",金額:" + request.getAmount() + "元,采購目的:" + request.getPurpose() + "。");  //處理請求  
        }  
        else {  
            this.successor.processRequest(request);  //轉(zhuǎn)發(fā)請求  
        }     
    }  
}  

//董事長類:具體處理者  
class President extends Approver {  
    public President(String name) {  
        super(name);  
    }  

    //具體請求處理方法  
    public void processRequest(PurchaseRequest request) {  
        if (request.getAmount() < 500000) {  
            System.out.println("董事長" + this.name + "審批采購單:" + request.getNumber() + ",金額:" + request.getAmount() + "元,采購目的:" + request.getPurpose() + "。");  //處理請求  
        }  
        else {  
            this.successor.processRequest(request);  //轉(zhuǎn)發(fā)請求  
        }  
    }  
}  

//董事會類:具體處理者  
class Congress extends Approver {  
    public Congress(String name) {  
        super(name);  
    }  

    //具體請求處理方法  
    public void processRequest(PurchaseRequest request) {  
        System.out.println("召開董事會審批采購單:" + request.getNumber() + ",金額:" + request.getAmount() + "元,采購目的:" + request.getPurpose() + "。");        //處理請求  
    }      
}

編寫如下客戶端測試代碼:

class Client {  
    public static void main(String[] args) {  
        Approver wjzhang,gyang,jguo,meeting;  
        wjzhang = new Director("張無忌");  
        gyang = new VicePresident("楊過");  
        jguo = new President("郭靖");  
        meeting = new Congress("董事會");  

        //創(chuàng)建職責(zé)鏈  
        wjzhang.setSuccessor(gyang);  
        gyang.setSuccessor(jguo);  
        jguo.setSuccessor(meeting);  

        //創(chuàng)建采購單  
        PurchaseRequest pr1 = new PurchaseRequest(45000,10001,"購買倚天劍");  
        wjzhang.processRequest(pr1);  

        PurchaseRequest pr2 = new PurchaseRequest(60000,10002,"購買《葵花寶典》");  
        wjzhang.processRequest(pr2);  

        PurchaseRequest pr3 = new PurchaseRequest(160000,10003,"購買《金剛經(jīng)》");  
        wjzhang.processRequest(pr3);  

        PurchaseRequest pr4 = new PurchaseRequest(800000,10004,"購買桃花島");  
        wjzhang.processRequest(pr4);  
    }  
}

編譯并運(yùn)行程序,輸出結(jié)果如下:

主任張無忌審批采購單:10001,金額:45000.0元,采購目的:購買倚天劍。
副董事長楊過審批采購單:10002,金額:60000.0元,采購目的:購買《葵花寶典》。
董事長郭靖審批采購單:10003,金額:160000.0元,采購目的:購買《金剛經(jīng)》。
召開董事會審批采購單:10004,金額:800000.0元,采購目的:購買桃花島。

如果需要在系統(tǒng)增加一個(gè)新的具體處理者,如增加一個(gè)經(jīng)理(Manager)角色可以審批5萬元至8萬元(不包括8萬元)的采購單,需要編寫一個(gè)新的具體處理者類Manager,作為抽象處理者類Approver的子類,實(shí)現(xiàn)在Approver類中定義的抽象處理方法,如果采購金額大于等于8萬元,則將請求轉(zhuǎn)發(fā)給下家,代碼如下所示:

//經(jīng)理類:具體處理者  
class Manager extends Approver {  
    public Manager(String name) {  
        super(name);  
    }  

    //具體請求處理方法  
    public void processRequest(PurchaseRequest request) {  
        if (request.getAmount() < 80000) {  
            System.out.println("經(jīng)理" + this.name + "審批采購單:" + request.getNumber() + ",金額:" + request.getAmount() + "元,采購目的:" + request.getPurpose() + "。");  //處理請求  
        }  
        else {  
            this.successor.processRequest(request);  //轉(zhuǎn)發(fā)請求  
        }     
    }  
}

由于鏈的創(chuàng)建過程由客戶端負(fù)責(zé),因此增加新的具體處理者類對原有類庫無任何影響,無須修改已有類的源代碼,符合“開閉原則”。

在客戶端代碼中,如果要將新的具體請求處理者應(yīng)用在系統(tǒng)中,需要?jiǎng)?chuàng)建新的具體處理者對象,然后將該對象加入職責(zé)鏈中。如在客戶端測試代碼中增加如下代碼:

Approver rhuang;  
rhuang = new Manager("黃蓉");

將建鏈代碼改為:

//創(chuàng)建職責(zé)鏈

//將“黃蓉”作為“張無忌”的下家
wjzhang.setSuccessor(rhuang);

//將“楊過”作為“黃蓉”的下家
rhuang.setSuccessor(gyang); 
gyang.setSuccessor(jguo);
jguo.setSuccessor(meeting);

重新編譯并運(yùn)行程序,輸出結(jié)果如下:

主任張無忌審批采購單:10001,金額:45000.0元,采購目的:購買倚天劍。 
經(jīng)理黃蓉審批采購單:10002,金額:60000.0元,采購目的:購買《葵花寶典》。
董事長郭靖審批采購單:10003,金額:160000.0元,采購目的:購買《金剛經(jīng)》。 
召開董事會審批采購單:10004,金額:800000.0元,采購目的:購買桃花島。

思考

如果將審批流程由“主任-->副董事長-->董事長-->董事會”調(diào)整為“主任-->董事長-->董事會”,系統(tǒng)將做出哪些改動(dòng)?預(yù)測修改之后客戶端代碼的輸出結(jié)果。

請求的鏈?zhǔn)教幚怼氊?zé)鏈模式(四)

16.4 純與不純的職責(zé)鏈模式

職責(zé)鏈模式可分為純的職責(zé)鏈模式和不純的職責(zé)鏈模式兩種:

(1) 純的職責(zé)鏈模式

一個(gè)純的職責(zé)鏈模式要求一個(gè)具體處理者對象只能在兩個(gè)行為中選擇一個(gè):要么承擔(dān)全部責(zé)任,要么將責(zé)任推給下家,不允許出現(xiàn)某一個(gè)具體處理者對象在承擔(dān)了一部分或全部責(zé)任后又將責(zé)任向下傳遞的情況。而且在純的職責(zé)鏈模式中,要求一個(gè)請求必須被某一個(gè)處理者對象所接收,不能出現(xiàn)某個(gè)請求未被任何一個(gè)處理者對象處理的情況。在前面的采購單審批實(shí)例中應(yīng)用的是純的職責(zé)鏈模式。

(2)不純的職責(zé)鏈模式

在一個(gè)不純的職責(zé)鏈模式中允許某個(gè)請求被一個(gè)具體處理者部分處理后再向下傳遞,或者一個(gè)具體處理者處理完某請求后其后繼處理者可以繼續(xù)處理該請求,而且一個(gè)請求可以最終不被任何處理者對象所接收。Java AWT 1.0中的事件處理模型應(yīng)用的是不純的職責(zé)鏈模式,其基本原理如下:由于窗口組件(如按鈕、文本框等)一般都位于容器組件中,因此當(dāng)事件發(fā)生在某一個(gè)組件上時(shí),先通過組件對象的handleEvent()方法將事件傳遞給相應(yīng)的事件處理方法,該事件處理方法將處理此事件,然后決定是否將該事件向上一級容器組件傳播;上級容器組件在接到事件之后可以繼續(xù)處理此事件并決定是否繼續(xù)向上級容器組件傳播,如此反復(fù),直到事件到達(dá)頂層容器組件為止;如果一直傳到最頂層容器仍沒有處理方法,則該事件不予處理。每一級組件在接收到事件時(shí),都可以處理此事件,而不論此事件是否在上一級已得到處理,還存在事件未被處理的情況。顯然,這就是不純的職責(zé)鏈模式,早期的Java AWT事件模型(JDK 1.0及更早)中的這種事件處理機(jī)制又叫事件浮升(Event Bubbling)機(jī)制。從Java.1.1以后,JDK使用觀察者模式代替職責(zé)鏈模式來處理事件。目前,在JavaScript中仍然可以使用這種事件浮升機(jī)制來進(jìn)行事件處理。

16.5 職責(zé)鏈模式總結(jié)

職責(zé)鏈模式通過建立一條鏈來組織請求的處理者,請求將沿著鏈進(jìn)行傳遞,請求發(fā)送者無須知道請求在何時(shí)、何處以及如何被處理,實(shí)現(xiàn)了請求發(fā)送者與處理者的解耦。在軟件開發(fā)中,如果遇到有多個(gè)對象可以處理同一請求時(shí)可以應(yīng)用職責(zé)鏈模式,例如在Web應(yīng)用開發(fā)中創(chuàng)建一個(gè)過濾器(Filter)鏈來對請求數(shù)據(jù)進(jìn)行過濾,在工作流系統(tǒng)中實(shí)現(xiàn)公文的分級審批等等,使用職責(zé)鏈模式可以較好地解決此類問題。

1.主要優(yōu)點(diǎn)

職責(zé)鏈模式的主要優(yōu)點(diǎn)如下:

(1) 職責(zé)鏈模式使得一個(gè)對象無須知道是其他哪一個(gè)對象處理其請求,對象僅需知道該請求會被處理即可,接收者和發(fā)送者都沒有對方的明確信息,且鏈中的對象不需要知道鏈的結(jié)構(gòu),由客戶端負(fù)責(zé)鏈的創(chuàng)建,降低了系統(tǒng)的耦合度。

(2) 請求處理對象僅需維持一個(gè)指向其后繼者的引用,而不需要維持它對所有的候選處理者的引用,可簡化對象的相互連接。

(3) 在給對象分派職責(zé)時(shí),職責(zé)鏈可以給我們更多的靈活性,可以通過在運(yùn)行時(shí)對該鏈進(jìn)行動(dòng)態(tài)的增加或修改來增加或改變處理一個(gè)請求的職責(zé)。

(4) 在系統(tǒng)中增加一個(gè)新的具體請求處理者時(shí)無須修改原有系統(tǒng)的代碼,只需要在客戶端重新建鏈即可,從這一點(diǎn)來看是符合“開閉原則”的。

2.主要缺點(diǎn)

職責(zé)鏈模式的主要缺點(diǎn)如下:

(1) 由于一個(gè)請求沒有明確的接收者,那么就不能保證它一定會被處理,該請求可能一直到鏈的末端都得不到處理;一個(gè)請求也可能因職責(zé)鏈沒有被正確配置而得不到處理。

(2) 對于比較長的職責(zé)鏈,請求的處理可能涉及到多個(gè)處理對象,系統(tǒng)性能將受到一定影響,而且在進(jìn)行代碼調(diào)試時(shí)不太方便。

(3) 如果建鏈不當(dāng),可能會造成循環(huán)調(diào)用,將導(dǎo)致系統(tǒng)陷入死循環(huán)。

3.適用場景

在以下情況下可以考慮使用職責(zé)鏈模式:

(1) 有多個(gè)對象可以處理同一個(gè)請求,具體哪個(gè)對象處理該請求待運(yùn)行時(shí)刻再確定,客戶端只需將請求提交到鏈上,而無須關(guān)心請求的處理對象是誰以及它是如何處理的。

(2) 在不明確指定接收者的情況下,向多個(gè)對象中的一個(gè)提交一個(gè)請求。

(3) 可動(dòng)態(tài)指定一組對象處理請求,客戶端可以動(dòng)態(tài)創(chuàng)建職責(zé)鏈來處理請求,還可以改變鏈中處理者之間的先后次序。

練習(xí)

Sunny軟件公司的OA系統(tǒng)需要提供一個(gè)假條審批模塊:如果員工請假天數(shù)小于3天,主任可以審批該假條;如果員工請假天數(shù)大于等于3天,小于10天,經(jīng)理可以審批;如果員工請假天數(shù)大于等于10天,小于30天,總經(jīng)理可以審批;如果超過30天,總經(jīng)理也不能審批,提示相應(yīng)的拒絕信息。試用職責(zé)鏈模式設(shè)計(jì)該假條審批模塊。

練習(xí)會在我的github上做掉

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容