• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            面對(duì)現(xiàn)實(shí),超越自己
            逆水行舟,不進(jìn)則退
            posts - 269,comments - 32,trackbacks - 0

            引言

            提起Command模式,我想沒(méi)有什么比遙控器的例子更能說(shuō)明問(wèn)題了,本文將通過(guò)它來(lái)一步步實(shí)現(xiàn)GOF的Command模式。

            我們先看下這個(gè)遙控器程序的需求:假如我們需要為家里的電器設(shè)計(jì)一個(gè)遠(yuǎn)程遙控器,通過(guò)這個(gè)控制器,我們可以控制電器(諸如燈、風(fēng)扇、空調(diào)等)的開關(guān)。我們的控制器上有一系列的按鈕,分別對(duì)應(yīng)家中的某個(gè)電器,當(dāng)我們?cè)谶b控器上按下“On”時(shí),電器打開;當(dāng)我們按下“Off”時(shí),電器關(guān)閉。

            好了,讓我們開始Command 模式之旅吧。

            HardCoding的實(shí)現(xiàn)方式

            控制器的實(shí)現(xiàn)

            一般來(lái)說(shuō),考慮問(wèn)題通常有兩種方式:從最復(fù)雜的情況考慮,也就是盡可能的深謀遠(yuǎn)慮,設(shè)計(jì)之初就考慮到程序的可維護(hù)性、擴(kuò)展性;還有就是從最簡(jiǎn)單的情況考慮,不考慮擴(kuò)展,客戶要求什么,我們就做個(gè)什么,至于以后有什么新的需求,等以后再說(shuō)。當(dāng)然這兩種方式各有優(yōu)劣,本文我們從最簡(jiǎn)單的情況開始考慮。

            我們假設(shè)控制器只能控制 三個(gè)電器,分別是:燈、電扇、門(你就當(dāng)是電子門好了^^)。那么我們的控制器應(yīng)該有三組,共六個(gè)按鈕,每一組按鈕分別有“On”,“Off”按鈕。同時(shí),我們規(guī)定,第一組按鈕對(duì)應(yīng)燈,第二組按鈕對(duì)應(yīng)電扇,第三組則對(duì)應(yīng)門,那么控制器應(yīng)該就像這樣:

             

            類的設(shè)計(jì)

            好了,控制器大致是這么個(gè)樣子了,那么 燈、電扇、門又是什么樣子呢?如果你看過(guò)前面幾節(jié)的模式,你可能會(huì)以為此時(shí)又要為它們創(chuàng)建一個(gè)基類或者接口,然后供它們繼承或?qū)崿F(xiàn)?,F(xiàn)在讓我們先看看我們想要控制的電器是什么樣子的:

            很抱歉,你遺憾地發(fā)現(xiàn),它們的接口完全不同,我們沒(méi)有辦法對(duì)它們進(jìn)行抽象,但是因?yàn)槲覀兇丝虄H考慮客戶最原始的需求(最簡(jiǎn)單的情況),那么我們大可以直接將它們復(fù)合到 遙控器(ControlPanel) 中

            NOTE:關(guān)于接口,有狹義的含義:就是一個(gè)聲明為interface的類型。還有一個(gè)廣義的含義:就是對(duì)象暴露給外界的方法、屬性,所以一個(gè)抽象類也可以稱作一個(gè)接口。這里,說(shuō)它們的接口不同,意思是說(shuō):這三個(gè)電器暴露給外界的方法完全不同。


            注意到,PressOn方法,它代表著某一個(gè)按鍵被按下,并接受一個(gè)int類型的參數(shù):SlotNo,它代表是第幾個(gè)鍵被按下。顯然,SlotNo的取值為0到2。對(duì)于PressOff則是完全相同的設(shè)計(jì)。

            代碼實(shí)現(xiàn)

             1 namespace Command {
             2 
             3     // 定義燈
             4     public class Light{
             5        public void TurnOn(){
             6            Console.WriteLine("The light is turned on.");
             7        }
             8        public void TurnOff() {
             9            Console.WriteLine("The light is turned off.");
            10        }
            11     }
            12 
            13     // 定義風(fēng)扇
            14     public class Fan {
            15        public void Start() {
            16            Console.WriteLine("The fan is starting.");
            17        }
            18        public void Stop() {
            19            Console.WriteLine("The fan is stopping.");
            20        }
            21     }
            22 
            23     // 定義門
            24     public class Door {
            25        public void Open() {
            26            Console.WriteLine("The door is open for you.");
            27        }
            28        public void Shut() {
            29            Console.WriteLine("The door is closed for safety");
            30        }
            31     }
            32 
            33     // 定義遙控器
            34     public class ControlPanel {
            35        private Light light;
            36        private Fan fan;
            37        private Door door;
            38 
            39        public ControlPanel(Light light, Fan fan, Door door) {
            40            this.light = light;
            41            this.fan = fan;
            42            this.door = door;
            43        }
            44 
            45        // 點(diǎn)擊On按鈕時(shí)的操作。slotNo,第幾個(gè)按鈕被按
            46        public void PressOn(int slotNo){
            47            switch (slotNo) {
            48               case 0:
            49                   light.TurnOn();
            50                   break;
            51               case 1:
            52                   fan.Start();
            53                   break;
            54               case 2:
            55                   door.Open();
            56                   break;
            57            }
            58        }
            59 
            60        // 點(diǎn)擊Off按鈕時(shí)的操作。
            61        public void PressOff(int slotNo) {
            62            switch (slotNo) {
            63               case 0:
            64                   light.TurnOff();
            65                   break;
            66               case 1:
            67                   fan.Stop();
            68                   break;
            69               case 2:
            70                   door.Shut();
            71                   break;
            72            }
            73        }
            74     }
            75 
            76     class Program {
            77        static void Main(string[] args) {
            78            Light light = new Light();
            79            Fan fan = new Fan();
            80            Door door = new Door();
            81 
            82            ControlPanel panel = new ControlPanel(light, fan, door);
            83                       
            84            panel.PressOn(0);     // 按第一個(gè)On按鈕,燈被打開了
            85            panel.PressOn(2);     // 按第二個(gè)On按鈕,門被打開了
            86            panel.PressOff(2);        // 按第二個(gè)Off按鈕,門被關(guān)閉了                        
            87        }
            88     }
            89 }
            90 
            91 輸出為:
            92 
            93 The light is turned on.
            94 The door is open for you.
            95 The door is closed for safety

            存在問(wèn)題

            這個(gè)解決方案雖然能解決當(dāng)前的問(wèn)題,但是幾乎沒(méi)有任何擴(kuò)展性可言?;蛘哒f(shuō),被調(diào)用者(Receiver:燈、電扇、門)與它們的調(diào)用者(Invoker:遙控器)是緊耦合的。遙控器不僅需要確切地知道它能控制哪些電器,并且需要知道這些電器由哪些方法可供調(diào)用。

            • 如果我們需要調(diào)換一下按鈕所控制的電器的次序,比如說(shuō)我們需要讓按鈕1不再控制燈,而是控制門,那么我們需要修改 PressOn 和 PressOff 方法中的Switch語(yǔ)句。
            • 如果我們需要給遙控器多添一個(gè)按鈕,以使它多控制一個(gè)電器,那么遙控器的字段、構(gòu)造函數(shù)、PressOn、PressOff方法都要修改。
            • 如果我們不給遙控器多添按鈕,但是要求它可以控制10個(gè)或者電器,換言之,就是我們可以動(dòng)態(tài)分配某個(gè)按鈕控制哪個(gè)電器,這樣的設(shè)計(jì)看上去簡(jiǎn)直無(wú)法完成。

            HardCoding 的另一實(shí)現(xiàn)

            新設(shè)計(jì)方案

            在考慮新的方案以前,我們先回顧前面的設(shè)計(jì),第三個(gè)問(wèn)題似乎暗示著我們的遙控器不夠好,思考一下,我們發(fā)現(xiàn)可以這樣設(shè)計(jì)遙控器:


            對(duì)比一下,我們看到可以通過(guò)左側(cè)可以上下活動(dòng)的閥門來(lái)控制當(dāng)前遙控器控制的是哪個(gè)電器(按照?qǐng)D中當(dāng)前顯示,控制的是燈),在選定了閥門后,我們可以再通過(guò)On,Off按鈕來(lái)對(duì)電器進(jìn)行控制。此時(shí),我們需要多添一個(gè)方法,通過(guò)它來(lái)控制閥門(進(jìn)而選擇想要控制的電器)。我們管這個(gè)方法叫做SetDevice()。那么我們的設(shè)計(jì)變成下圖所示:

            NOTE:在圖中,以及現(xiàn)實(shí)世界中,閥門所能控制的電器數(shù)總是有限的,但在程序中,可以是無(wú)限的,就看你有多少個(gè)諸如light的電器類了


            注意到幾點(diǎn)變化:

            • 因?yàn)槲覀兗僭O(shè)遙控器可以控制的電器是無(wú)限多的,所以這里不能指定具體電器類型,因?yàn)樵贑#中所有類型均繼承自O(shè)bject,我們將SetDevice()方法接受的參數(shù)設(shè)置成為Object。
            • ControlPanel不知道它將控制哪個(gè)類,所以圖中ControlPanel和Light、Door、Fan沒(méi)有聯(lián)系。
            • PressOn()和PressOff()方法不再需要參數(shù),因?yàn)楹苊黠@,只有一組On和Off按鈕。

            代碼實(shí)現(xiàn)

             1 namespace Command {
             2 
             3     public class Light {  // 略   }
             4     public class Fan {    // 略 }
             5     public class Door {   // 略 }
             6 
             7     // 定義遙控器
             8     public class ControlPanel {
             9        private Object device;
            10 
            11        // 點(diǎn)擊On按鈕時(shí)的操作。
            12        public void PressOn() {
            13            Light light = device as Light;
            14            if (light != null) light.TurnOn();
            15 
            16            Fan fan = device as Fan;
            17            if (fan != null) fan.Start();
            18 
            19            Door door = device as Door;
            20            if (door != null) door.Open();
            21        }
            22 
            23        // 點(diǎn)擊Of按鈕時(shí)的操作。
            24        public void PressOff() {
            25            Light light = device as Light;
            26            if (light != null) light.TurnOff();
            27 
            28            Fan fan = device as Fan;
            29            if (fan != null) fan.Stop();
            30 
            31            Door door = device as Door;
            32            if (door != null) door.Shut();
            33        }
            34        
            35        // 設(shè)置閥門控制哪個(gè)電器
            36        public void SetDevice(Object device) {
            37            this.device = device;
            38        }
            39     }
            40 
            41     class Program {
            42        static void Main(string[] args) {
            43 
            44            Light light = new Light();
            45            Fan fan = new Fan();  
            46 
            47            ControlPanel panel = new ControlPanel();
            48            
            49            panel.SetDevice(light);      // 設(shè)置閥門控制燈
            50            panel.PressOn();             // 打開燈
            51            panel.PressOff();            // 關(guān)閉燈
            52 
            53            panel.SetDevice(fan);    // 設(shè)置閥門控制電扇
            54            panel.PressOn();             // 打開門
            55        }
            56     }
            57 }

            存在問(wèn)題

            我們首先可以看到,這個(gè)方案似乎解決了第一種設(shè)計(jì)的大多數(shù)問(wèn)題,除了一點(diǎn)點(diǎn)瑕疵:

            • 盡管我們可以控制任意多的設(shè)備,但是我們每添加一個(gè)可以控制的設(shè)備,仍需要修改PressOn()和PressOff()方法。
            • 在PressOn()和PressOff()方法中,需要對(duì)所有可能控制的電器進(jìn)行類型轉(zhuǎn)換,無(wú)疑效率低下。

            封裝調(diào)用

            問(wèn)題分析

            我們的處境似乎一籌莫展,想不到更好的辦法來(lái)解決。這時(shí)候,讓我們先回頭再觀察一下ControlPanel的PressOn()和PressOff()代碼。

             1 // 點(diǎn)擊On按鈕時(shí)的操作。
             2 public void PressOn() {
             3     Light light = device as Light;
             4     if (light != null) light.TurnOn();
             5 
             6     Fan fan = device as Fan;
             7     if (fan != null) fan.Start();
             8 
             9     Door door = device as Door;
            10     if (door != null) door.Open();
            11 }

            我們發(fā)現(xiàn)PressOn()和PressOff()方法在每次添加新設(shè)備時(shí)需要作修改,而實(shí)際上改變的是對(duì)對(duì)象方法的調(diào)用,因?yàn)椴还苡卸嗌賯€(gè)if語(yǔ)句,只會(huì)調(diào)用其中某個(gè)不為null的對(duì)象的一個(gè)方法。然后我們?cè)倩仡櫼幌翺O的思想,Encapsulate what varies(封裝變化)。我們想是不是應(yīng)該有辦法將這變化的這部分(方法的調(diào)用)封裝起來(lái)呢?

            在考慮如何封裝之前,我們假設(shè)已經(jīng)有一個(gè)類,把它封裝起來(lái)了,我們管這個(gè)類叫做Command,那么這個(gè)類該如何使用呢?

            我們先考慮一下它的構(gòu)成,因?yàn)樗庋b各個(gè)對(duì)象的方法,所以,它應(yīng)該暴露出一個(gè)方法,這個(gè)方法既可以代表 light.TurnOn(),也可以代表fan.Start(),還可以代表door.Open(),讓我們給這個(gè)方法起個(gè)名字,叫做Execute()。

            好了,現(xiàn)在我們有了Command類,還有了一個(gè)萬(wàn)金油的Execute()方法,現(xiàn)在,我們修改PressOn()方法,讓它通過(guò)這個(gè)Command類來(lái)控制電器(調(diào)用各個(gè)類的方法)。

            1 // 點(diǎn)擊On按鈕時(shí)的操作。
            2 public void PressOn() {
            3     command.Execute();
            4 }

            哇,是不是有點(diǎn)簡(jiǎn)單的過(guò)分了???但就是這么簡(jiǎn)單,可我們還是發(fā)現(xiàn)了兩個(gè)問(wèn)題:

            1. Command應(yīng)該能知道它調(diào)用的是哪個(gè)電器類的哪個(gè)方法,這暗示我們Command類應(yīng)該保存對(duì)于具體電器類的一個(gè)引用。
            2. 我們的ControlPanel應(yīng)該有兩個(gè)Command,一個(gè)Command對(duì)應(yīng)于所有開啟的操作(我們管它叫onCommand),一個(gè)Command對(duì)應(yīng)所有關(guān)閉的操作(我們管它叫offCommand)。

            同時(shí),我們的SetDevice(object)方法,也應(yīng)該改成SetCommand(onCommand,offCommand)。好了,現(xiàn)在讓我們看看新版ControlPanel 的全景圖吧。

            Command類型的實(shí)現(xiàn)

            顯然,我們應(yīng)該能看出:onCommand實(shí)體變量(instance variable)和offCommand變量屬于Command類型,同時(shí),上面我們已經(jīng)討論過(guò)Command類應(yīng)該具有一個(gè)Execute()方法,除此以外,它還需要可以保存對(duì)各個(gè)對(duì)象的引用,通過(guò)Execute()方法可以調(diào)用其引用的對(duì)象的方法。

            那么我們按照這個(gè)思路,來(lái)看下開燈這一操作(調(diào)用light對(duì)象的TurnOn()方法)的Command對(duì)象應(yīng)該是什么樣的:

             1 public class LightOnCommand{
             2     Light light;
             3     public Command(Light light){
             4        this.light = light;
             5     }
             6     
             7     public void Execute(){
             8        light.TurnOn();
             9     }
            10 }

            再看下開電扇(調(diào)用fan對(duì)象的Start()方法)的Command對(duì)象應(yīng)該是什么樣的:

             1 public class FanStartCommand{
             2     Fan fan;
             3     public Command(Fan fan){
             4        this.fan = fan;
             5     }
             6     
             7     public void Execute(){
             8        fan.Start();
             9     }
            10 }

            這樣顯然是不行的,它沒(méi)有解決任何的問(wèn)題,因?yàn)镕anStartCommand和LightOnCommand是不同的類型,而我們的ControlPanel要求對(duì)于所有打開的操作應(yīng)該只接受一個(gè)類型的Command的。但是經(jīng)過(guò)我們上面的討論,我們已經(jīng)知道所有的Command都有一個(gè)Execute()方法,我們何不定義一個(gè)接口來(lái)解決這個(gè)問(wèn)題呢?


            OK,現(xiàn)在我們已經(jīng)完成了全部的設(shè)計(jì),讓我們先看一下最終的UML圖,再進(jìn)行代碼實(shí)現(xiàn)吧(簡(jiǎn)單起見(jiàn),只加入了燈和電扇)。


            我們先看下這張圖說(shuō)明了什么,以及發(fā)生的順序:

            1. ConsoleApplication,也就是我們的應(yīng)用程序,它創(chuàng)建電器Fan、Light對(duì)象,以及LightOnCommand和FanStartCommand。
            2. LightOnCommand、FanStartCommand實(shí)現(xiàn)了ICommand接口,它保存著對(duì)于Fan和Light的引用,并通過(guò)Execute()調(diào)用Fan和Light的方法。
            3. ControlPanel復(fù)合了Command對(duì)象,通過(guò)調(diào)用Command的Execute()方法,間接調(diào)用了Light的TurnOn()方法或者是Fan的Stop()方法。

            它們之間的時(shí)序圖是這樣的:


            可以看出:通過(guò)引入Command對(duì)象,ControlPanel對(duì)于它實(shí)際調(diào)用的對(duì)象Fan或者Light是一無(wú)所知的,它只知道當(dāng)On按下的時(shí)候就調(diào)用onCommand的Execute()方法;當(dāng)Off按下的時(shí)候就調(diào)用offCommand的Execute()方法。Light和Fan當(dāng)然更不知道誰(shuí)在調(diào)用它。通過(guò)這種方式,我們實(shí)現(xiàn)了調(diào)用者(Invoker,遙控器ControlPanel) 和 被調(diào)用者(Receiver,電扇Fan等)的解耦。如果將來(lái)我們需要對(duì)這個(gè)ControlPanel進(jìn)行擴(kuò)展,只需要再添加一個(gè)實(shí)現(xiàn)了ICommand接口的對(duì)象就可以了,對(duì)于ControlPanel無(wú)需做任何修改。

            代碼實(shí)現(xiàn)

             1 namespace Command {
             2 
             3     // 定義空調(diào),用于測(cè)試給遙控器添新控制類型
             4     public class AirCondition {
             5        public void Start() {
             6            Console.WriteLine("The AirCondition is turned on.");
             7        }
             8        public void SetTemperature(int i) {
             9            Console.WriteLine("The temperature is set to " + i);
            10        }
            11        public void Stop() {
            12            Console.WriteLine("The AirCondition is turned off.");
            13        }
            14     }
            15     
            16     // 定義Command接口
            17     public interface ICommand {
            18        void Execute();
            19     }
            20 
            21     // 定義開空調(diào)命令
            22     public class AirOnCommand : ICommand {
            23        AirCondition airCondition;
            24        public AirOnCommand(AirCondition airCondition) {
            25            this.airCondition = airCondition;
            26        }
            27        public void Execute() {  //注意,你可以在Execute()中添加多個(gè)方法
            28            airCondition.Start();
            29            airCondition.SetTemperature(16);
            30        }
            31     }
            32 
            33     // 定義關(guān)空調(diào)命令
            34     public class AirOffCommand : ICommand {
            35        AirCondition airCondition;
            36        public AirOffCommand(AirCondition airCondition) {
            37            this.airCondition = airCondition;
            38        }
            39        public void Execute() {
            40            airCondition.Stop();
            41        }
            42     }
            43 
            44 
            45     // 定義遙控器
            46     public class ControlPanel {
            47        private ICommand onCommand;
            48        private ICommand offCommand;
            49 
            50        public void PressOn() {
            51            onCommand.Execute();
            52        }
            53 
            54        public void PressOff() {
            55            offCommand.Execute();
            56        }
            57 
            58        public void SetCommand(ICommand onCommand,ICommand offCommand) {
            59            this.onCommand = onCommand;
            60            this.offCommand = offCommand;
            61        }
            62     }
            63 
            64     class Program {
            65        static void Main(string[] args) {
            66 
            67            // 創(chuàng)建遙控器對(duì)象
            68            ControlPanel panel = new ControlPanel();
            69 
            70            AirCondition airCondition = new AirCondition();       //創(chuàng)建空調(diào)對(duì)象
            71 
            72            // 創(chuàng)建Command對(duì)象,傳遞空調(diào)對(duì)象
            73            ICommand onCommand = new AirOnCommand(airCondition);
            74            ICommand offCommand = new AirOffCommand(airCondition);
            75 
            76            // 設(shè)置遙控器的Command
            77            panel.SetCommand(onCommand, offCommand);
            78 
            79            panel.PressOn();      //按下On按鈕,開空調(diào),溫度調(diào)到16度
            80            panel.PressOff();     //按下Off按鈕,關(guān)空調(diào)
            81 
            82        }
            83     }
            84 }

             

            Command 模式

            實(shí)際上,我們上面做的這一切,實(shí)現(xiàn)了另一個(gè)設(shè)計(jì)模式:Command模式?,F(xiàn)在又到了給出官方定義的時(shí)候了。每次到了這部分我就不知道該怎么寫了,寫的人太多了,資料也太多了,我相信你看到這里對(duì)Command模式已經(jīng)比較清楚了,所以我還是一如既往地從簡(jiǎn)吧。

            Command模式的正式定義:將一個(gè)請(qǐng)求封裝為一個(gè)對(duì)象,從而使你可用不同的請(qǐng)求對(duì)客戶進(jìn)行參數(shù)化;對(duì)請(qǐng)求排隊(duì)或記錄請(qǐng)求日志,以及支持可撤消的操作。

            它的 靜態(tài)圖 是這樣的:


            它的 時(shí)序圖 是這樣的:


            可以和我們前面的圖對(duì)比一下,對(duì)于這兩個(gè)圖,除了改了個(gè)名字外基本沒(méi)變,我就不再說(shuō)明了,也留給你一點(diǎn)思考的空間。

            總結(jié)

            本文簡(jiǎn)單地介紹了GOF的Commmand模式,我們通過(guò)一個(gè)簡(jiǎn)單的范例家電遙控器 實(shí)現(xiàn)了這一模式。

            我們首先了解了不使用此模式的HardCoding方式的實(shí)現(xiàn)方法,討論了它的缺點(diǎn);然后又換了另一種改進(jìn)了的實(shí)現(xiàn)方法,再次討論了它的不足。  然后,我們通過(guò)將對(duì)象的調(diào)用封裝到一個(gè)Command對(duì)象中的方式,巧妙地完成了設(shè)計(jì)。最后,我們給出了Command模式的正式定義。

            本文僅僅簡(jiǎn)要介紹了Command模式,它的高級(jí)應(yīng)用:取消操作(UnDo)、事務(wù)支持(Transaction)、隊(duì)列請(qǐng)求(Queuing Request) 以后有時(shí)間了會(huì)再寫文章。

            希望這篇文章能對(duì)你有所幫助!

            本文轉(zhuǎn)自:http://www.tracefact.net/Design-Pattern/Command.aspx
            其他鏈接:http://blog.csdn.net/tianxiaoqi2008/article/details/7276846

            posted on 2012-07-10 13:40 王海光 閱讀(523) 評(píng)論(0)  編輯 收藏 引用 所屬分類: Design Pattern
            亚洲一区中文字幕久久| 欧美国产精品久久高清| 久久成人18免费网站| 亚洲精品国产美女久久久| 成人a毛片久久免费播放| 99久久精品国产一区二区| 久久久久一本毛久久久| 99久久777色| 无码人妻久久一区二区三区免费| 国产亚洲美女精品久久久| 69久久精品无码一区二区| 久久人人添人人爽添人人片牛牛 | 亚洲人成伊人成综合网久久久 | 久久一本综合| 久久99中文字幕久久| 久久永久免费人妻精品下载| 麻豆av久久av盛宴av| 老司机午夜网站国内精品久久久久久久久 | 亚洲欧美日韩精品久久| 国内精品九九久久久精品| 久久久久久久波多野结衣高潮 | 尹人香蕉久久99天天拍| 久久国产免费| 久久er国产精品免费观看8| 99久久婷婷国产综合亚洲| 久久久婷婷五月亚洲97号色| 97精品国产97久久久久久免费| 四虎国产精品成人免费久久| 久久久99精品成人片中文字幕 | 久久香综合精品久久伊人| 久久亚洲熟女cc98cm| 7777精品伊人久久久大香线蕉| 热综合一本伊人久久精品| 久久久久久噜噜精品免费直播 | 精品无码久久久久久久动漫| 久久精品国产一区| 久久精品这里热有精品| 热久久国产精品| 久久av高潮av无码av喷吹| 久久精品国产亚洲5555| 理论片午午伦夜理片久久|