亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

在線捉鬼游戲開發之三-業務對象核心代碼編寫與單元測試(游戲開始前:玩家入座與退出)

2019-11-14 15:52:07
字體:
來源:轉載
供稿:網友

-----------回顧分割線-----------

系列之一講述了游戲規則,系列之二講述了舊版的前臺效果、代碼中不好的地方、以及新版的改進核心,此篇開始就是新版代碼編寫全過程。此系列旨在開發類似“誰是臥底+殺人游戲”的捉鬼游戲在線版,記錄從分析游戲開始的開發全過程,通過此項目讓自己熟悉面向對象的SOLID原則,提高對設計模式、重構的理解。

索引目錄

0. 索引(持續更新中)

1. 游戲流程介紹與技術選用

2. 設計業務對象與對象職責劃分(1)(圖解舊版本)

3. 設計業務對象與對象職責劃分(2)(舊版本代碼剖析)

4. 設計業務對象與對象職責劃分(3)(新版本業務對象設計)

5. 業務對象核心代碼編寫與單元測試(游戲開始前:玩家入座與退出)

……(未完待續)

-----------回顧結束分割線-----------

 

先放上svn代碼,地址:https://115.29.246.25/svn/CatGhost/

賬號:guest 密碼:guest(支持源代碼下載,已設只讀權限,待我基本做出初始版本后再放到git)

 

-----------本篇開始分割線-----------

從本篇開始,后續都是代碼的編寫記錄了,看的會有些枯燥,但如果是開發人員,應該會看的津津有味。建議看了前面四篇說明之后(個人覺得本系列亮點還是在前四篇,有助于面向對象開發的思考方式整理),再下個svn代碼。我寫的這些就不用再看代碼了,大致感受一下流程和思路即可。(個人對于寫完整個項目后直接貼代碼上來毫無講解的那種文章比較那個……反正我是看不下去的)

代碼是為文中的闡述服務的,即:主要看中文,必要理解時才看代碼。

一、按類圖搭建基礎類模型

上一篇中的這張關鍵類圖,想必大家還有印象,下面就先從這副類圖入手,先把類名、屬性名、方法名搭建起來,方法都是空的(即便再簡單的方法都先不寫,若要求返回值,也大致應付一下別出紅線),訪問修飾符也暫不過多考慮,如下兩類:

    public abstract class Player    {        public string NickName { get; set; }        public void Speak()        {        }        public void Vote()        {        }    }
Player
    public class SpeakManager    {        PRivate StringBuilder _record;        public void PlayerSpeak()        { }        public void SystemSpeak()        { }        public string ShowRecord()        {            if (this._record != null)            {                return this._record.ToString();            }            return string.Empty;        }        public void ClearRecord()        { }        public void SetSpeaker()        { }    }
SpeakManager

 

二、從單元測試開始,從類創建、初始化類內屬性、維護類屬性的角度,逐步完善類

以下是Table類單例模式的測試,很簡單

[TestMethod]public void CreateTableUnitTest(){    Table table = Table.GetInstance();    Assert.IsNotNull(table);}
CreateTableUnitTest
private static Table _table = new Table();private Table() { }public static Table GetInstance(){    return _table;}
Table Singleton

以下是Setting類初始化的同時,獲取配置文件游戲人數的測試,附上目前的Setting類,也比較好理解

[TestMethod]public void GetSettingCountUnitTest(){    Setting setting = Setting.GetInstance();    int iTotalCount = setting.GetTotalCount();    int iExpectCount = 9, iExpectCivilianCount = 4, iExpectIdiotCount = 2, iExpectGhostCount = 3;    Assert.AreEqual(iExpectCount, iTotalCount);    Assert.AreEqual(iExpectCivilianCount, setting.CivilianCount);    Assert.AreEqual(iExpectIdiotCount, setting.IdiotCount);    Assert.AreEqual(iExpectGhostCount, setting.GhostCount);}
GetSettingCountUnitTest
[TestMethod]public void IsFullFromSettingUnitTest(){    Setting setting = Setting.GetInstance();    bool iExpectFalse = setting.IsFull(8);    bool iExpectTrue = setting.IsFull(9);    Assert.AreEqual(false, iExpectFalse);    Assert.AreEqual(true, iExpectTrue);}
IsFullFromSettingUnitTest
    public class Setting    {        // field        private int _civilianCount;        private int _idiotCount;        private int _ghostCount;        // property        public int CivilianCount        {            get { return _civilianCount; }        }        public int IdiotCount        {            get { return _idiotCount; }        }        public int GhostCount        {            get { return _ghostCount; }        }        #region singleton        private static Setting _setting = new Setting();        private Setting()        {            InitialCount();        }        /// <summary>        /// 初始化配置文件人數        /// </summary>        private void InitialCount()        {            string strCivilianCount = System.Configuration.ConfigurationManager.AppSettings["CivilianCount"];            string strIdiotCount = System.Configuration.ConfigurationManager.AppSettings["IdiotCount"];            string strGhostCount = System.Configuration.ConfigurationManager.AppSettings["GhostCount"];            this._civilianCount = StringConvertToInt32(strCivilianCount);            this._idiotCount = StringConvertToInt32(strIdiotCount);            this._ghostCount = StringConvertToInt32(strGhostCount);        }        /// <summary>        /// 轉換字符串為數字        /// </summary>        /// <param name="strNumber">字符串</param>        /// <returns>數字</returns>        private int StringConvertToInt32(string strNumber)        {            int result = 0;            int.TryParse(strNumber, out result);            return result;        }        public static Setting GetInstance()        { return _setting; }        #endregion        // method        /// <summary>        /// 返回游戲總人數        /// </summary>        /// <returns>總人數</returns>        public int GetTotalCount()        {            return CivilianCount + IdiotCount + GhostCount;        }        /// <summary>        /// 是否玩家已滿        /// </summary>        /// <param name="iCurrentCount">當前玩家數</param>        /// <returns>是否已滿</returns>        public bool IsFull(int iCurrentCount)        {            return GetTotalCount() == iCurrentCount;        }    }
Setting

以下是PlayerManager類增減玩家名單的測試

[TestMethod]public void GetPlayerNameArrayUnitTest(){    PlayerManager manager = new PlayerManager();    string[] names = new string[] { "jack", "peter", "lily", "puppy", "coco", "kimi", "angela", "cindy","vivian" };    for (int order = 0; order < names.Length; order++)    {        string name = names[order];        manager.SetPlayer(order, name);    }    string[] array = manager.GetNameArray();    Assert.AreEqual(names.Length, array.Length);    manager.DeletePlayer(5);    Assert.AreEqual(names.Length, array.Length);    foreach (string name in array)    {        Console.WriteLine(name);    }}
GetPlayerNameArrayUnitTest
    public class PlayerManager    {        // field        private string[] _nameArray;        private Player[] _playerArray;        // method        public PlayerManager()        {            InitialArray();        }        /// <summary>        /// 初始化數組個數        /// </summary>        private void InitialArray()        {            Setting setting = Setting.GetInstance();            int totalCount = setting.GetTotalCount();            this._nameArray = new string[totalCount];            this._playerArray = new Player[totalCount];        }        /// <summary>        /// 設置指定座位號的玩家名        /// </summary>        /// <param name="order">座位號</param>        /// <param name="nickName">玩家名</param>        public void SetPlayer(int order, string nickName)        {            this._nameArray[order] = nickName;        }        public void SetPlayer(Player player)        { }        /// <summary>        /// 刪除指定座位號的玩家名        /// </summary>        /// <param name="order">座位號</param>        public void DeletePlayer(int order)        {            this._nameArray[order] = string.Empty;        }        /// <summary>        /// 返回玩家名單數組        /// </summary>        /// <returns>玩家名單數組</returns>        public string[] GetNameArray()        {            return this._nameArray;        }        /// <summary>        /// 返回指定角色的玩家數組        /// </summary>        /// <param name="type">玩家角色</param>        /// <returns>玩家數組</returns>        public Player[] GetPlayerArray(Type type)        {            List<Player> returnList = new List<Player>();            foreach (Player player in this._playerArray)            {                if (player.GetType().Equals(type))                {                    returnList.Add(player);                }            }            return returnList.ToArray();        }    }
PlayerManager

在逐漸完善類的過程中,需要注意幾點:

(1)命名問題:方法名別怕長,一定要寫清楚,英文不好就找翻譯,再不行就用拼音,再不行就寫中文。

(2)新方法的加入:如Setting中獲取游戲總人數的方法GetTotalCount(),以后在PlayerManager類需要以此為據初始化數組大?。ㄗ粩盗浚筮€有許多這種在設計階段沒考慮到的方法,也反襯出:設計階段不要過于糾結如何做到最perfect的類設計之后再去寫代碼,而是做個大概,主要是劃分職責(單一職責原則),以后再寫代碼的時候發現需要這么一個方法了再去添加(也要考慮這個方法/職責應該劃分給誰的問題)。

(3)訪問修飾符問題:多考慮迪米特法則(知道最少原則)——如果外界沒必要知道,就private,然后再慢慢升級訪問修飾符。好處是:在編寫單元測試的時候就能夠最早看出此類哪些是有必要開放的、哪些是要保護起來的,因為單元測試是最先覆蓋所有類內路徑的測試,比整個項目做得差不多了再考慮、或者邊做邊考慮,前者更完整(程序員都有代碼潔癖,別跟我說你沒有)。

(4)注釋寫得好,代碼改的少

相信上述幾點已經耳濡目染很久了,這里寫出來也是為和我一樣深有感悟的新手們做再次提醒,自己的感悟+別人的提醒=記憶更深刻。

 

三、自定義異常

我指的自定義是類似這樣的:

namespace Catghost.Common.Exceptions{    public sealed class NickNameIsNullOrEmptyException : Exception    {        public NickNameIsNullOrEmptyException()        {            throw new Exception("昵稱不能為空。");        }    }}
NickNameIsNullOrEmptyException
            // 檢查昵稱是否為空            if (string.IsNullOrEmpty(nickName))            {                throw new NickNameIsNullOrEmptyException();            }            // 檢查昵稱是否重復            foreach (string name in this._nameArray)            {                // 跳過空座位的檢測                if (string.IsNullOrEmpty(name))                {                    continue;                }                if (name.Equals(nickName))                {                    throw new NickNameRepeatException();                }            }
調用自定義異常

 單元測試情況:

系統不是會自己拋異常嗎,如果需要寫文字,不是還有new Exception(string)重載嗎,干嘛還要自定義手寫一堆?理由很簡單:復用。

假設有五個方法都要判斷昵稱是否為空,你可能會想到提取出一個private方法或者在common文件夾下新建類并將CheckNickName()公開,這是對的,復用嘛,但如果將異常也放在此類,甚至更糟糕的放在各處消費代碼中時,那提示的中文語句可能是“用戶名不能為空”、“您的用戶名為空了”、“對不起,請保證不為空的用戶名”——各種不一致,各種不復用。而且自定義異常也許在項目中后期會增加很多,甚至是你的隊友們在增加,那怎么溝通呢?

團隊約定這時候出現作用了:約定所有異常放在Common/Exceptions文件夾下,且命名有規則……巴拉巴拉,其實團隊約定真的很重要,不然你寫了半天的日期格式化,結果是團隊大哥早寫好放那的(以前我就遇到過這種蠢事),所以從頭開始項目團隊的時候,拿出一份Word,一條條寫著團隊約定(在哪個文件夾放什么東西、命名規則如何、注釋如何寫等等)。同樣,進入新的團隊或者有新人加入的時候,一定要自己主動去問或者告訴新人:這個項目的開發約定有哪些。

 

四、分析代碼

如此高大上的章節標題,當然要依賴高級點的功能,其實我不是很能參透這一塊,看了《重構》就稍微能理解多一些了:

 

右鍵解決方案管理器中的根目錄->分析
(1)運行代碼分析

意思是要加個可序列化標簽(沒明白深層原因,實現了ISerializble接口就要標記?回頭再明白吧,目前先走完項目,寫這個開發系列的好處也在這里,不明白的先記下,回頭再看,以免影響主要事件),行,那就加上。果斷ok。

(2)計算代碼度量值

這里是本篇亮點(利用重構提高可維護性、減低耦合與復雜度

簡單介紹一下,代碼度量值是檢測代碼可維護性較好的指標。

可維護性指數(越大越好):表示指定代碼容易修改、利于應對需求變換的程度。

類耦合度(越小越好):高內聚低耦合,說的就是這里,表示這里的代碼與其他類之間的直接關系有多大,關系越小就越不容易一改都改——越容易維護。

圈復雜度(越小越好):表示代碼中的分支情況有多少的問題,當然是越少越利于開發人員理解,越容易維護。

繼承深度、代碼行數(都是越小越好):簡單不贅述。

來吧開始:看到圖中鼠標點亮的那一行——PlayerManager類的SetPlayer()方法,此方法職責是在玩家點擊“入座”按鈕后,將玩家名字添加到列表中(很簡單吧,這有什么難的?看下去,有戲)??梢钥吹娇删S護性非常之低(56,還沒及格呢),耦合(7)、復雜度(10)看似比較低,但放眼看去其他方法(不要看成上面的其他類或者目錄總和)的這些指標,都很?。?以下),說明:這個方法的代碼沒寫好、要大改!

先來看看這個方法都寫了啥

        /// <summary>        /// 設置指定座位號的玩家名        /// </summary>        /// <param name="order">座位號</param>        /// <param name="nickName">玩家名</param>        public void SetPlayer(int order, string nickName)        {            // 檢查座位序號正確性            if (order < 0 || order >= this._nameArray.Length)            {                throw new IndexOutOfRangeException("入座位置不正確。");            }            // 檢查昵稱是否為空            if (string.IsNullOrEmpty(nickName))            {                throw new NickNameIsNullOrEmptyException();            }            // 檢查昵稱是否重復            foreach (string name in this._nameArray)            {                // 跳過空座位的檢測                if (string.IsNullOrEmpty(name))                {                    continue;                }                if (name.Equals(nickName))                {                    throw new NickNameRepeatException();                }            }            this._nameArray[order] = nickName;            if (Setting.GetInstance().IsFull(this._nameArray.Where(n => !string.IsNullOrEmpty(n)).Count()))            {                this._table.StartGame();            }        }
SetPlayer

 感覺還是比較清晰的,通俗易懂,還有注釋——沒錯,就是注釋出的問題:在《重構》中,注釋就是最大的需要改的信號——你會對string myName="jack"; 添加一段這樣的注釋嗎? // 我的名字是jack

肯定不會。為什么?你可能會回答:因為我一看就懂,不用注釋,只有難懂的、太長的代碼,才需要注釋。

沒錯,那既然知道這是一段難懂、太長的代碼,為何不去優化、拆分,使他變得又容易、又簡短呢?下面就隨筆者來做這個事兒:

(1)先測試,保證目前的代碼是ok的。好了,測了,ok。

(2)把“檢查座位序號正確性”的代碼提出去,起個好名字,消掉注釋。

 

 代碼改為:

        /// <summary>        /// 設置指定座位號的玩家名        /// </summary>        /// <param name="order">座位號</param>        /// <param name="nickName">玩家名</param>        public void SetPlayer(int order, string nickName)        {            CheckSeatOrder(order);            // 檢查昵稱是否為空            if (string.IsNullOrEmpty(nickName))            {                throw new NickNameIsNullOrEmptyException();            }            // 檢查昵稱是否重復            foreach (string name in this._nameArray)            {                // 跳過空座位的檢測                if (string.IsNullOrEmpty(name))                {                    continue;                }                if (name.Equals(nickName))                {                    throw new NickNameRepeatException();                }            }            this._nameArray[order] = nickName;            if (Setting.GetInstance().IsFull(this._nameArray.Where(n => !string.IsNullOrEmpty(n)).Count()))            {                this._table.StartGame();            }        }        /// <summary>        /// 檢查座位號正確性        /// </summary>        /// <param name="order">座位號</param>        private void CheckSeatOrder(int order)        {            if (order < 0 || order >= this._nameArray.Length)            {                throw new IndexOutOfRangeException("座位號不正確。");            }        }
CheckSeatOrder

(3)回到(1)——測試。全部通過。

(4)繼續(2)——找到其他注釋的地方,提取方法,起個好名字,消掉注釋。

(5)測試。全部通過。

直到SetPlayer()代碼成為下面這樣(提取后的我就不貼了,都一樣的):

        public void SetPlayer(int order, string nickName)        {            CheckSeatOrder(order);            CheckNickNameIsNullOrEmpty(nickName);            CheckNickNameIsRepeat(nickName);            this._nameArray[order] = nickName;            if (Setting.GetInstance().IsFull(this._nameArray.Where(n => !string.IsNullOrEmpty(n)).Count()))            {                this._table.StartGame();            }        }
SetPlayer

此時我們再來測一下代碼度量值:

哎喲不錯喔!(80后的偶像周杰倫?。┛删S護性提高了9個點,耦合與復雜度都減小了一大半,嘿嘿~說明路子對了,那么咱繼續優化,看能做到多好。

此時的SetPlayer方法中,最主要的就是一句 this._nameArray[order] = nickName; 這一句已經不能再簡單了。除此之外,都是附加的內容(前三行的檢測、之后的也算是檢測——檢測夠不夠人開始游戲)。那么要改的肯定是附加內容的。代碼先貼一下:

if (Setting.GetInstance().IsFull(this._nameArray.Where(n => !string.IsNullOrEmpty(n)).Count())){            this._table.StartGame();}
檢測是否夠人開始游戲

怎么看有問題的地方?其實很簡單,計算機的腦子轉的很快(只要內存夠),我們看10秒,計算機10毫秒不到,所以代碼的問題,主要不是為了給計算機方便,而是給人方便,給程序員方便,作為程序員,你覺得看哪里不順眼、不爽,那就是有問題

這段代碼中我看if的判斷里面就很長,不爽!

改為這個樣子(也是用的提取方法):

if (GetSetting().IsFull(GetCurrentPlayerCount())){            this._table.StartGame();}
修改if判斷

哈哈,爽多了,簡直跟看中文一樣,只不過換成英文罷了。

還有問題的地方很明顯,if判斷中,需要看完整個代碼才明白是做什么的,此時再做一次提取方法,成為這樣:

public void SetPlayer(int order, string nickName){    CheckSeatOrder(order);    CheckNickNameIsNullOrEmpty(nickName);    CheckNickNameIsRepeat(nickName);    this._nameArray[order] = nickName;    CheckStartGame();}
SetPlayer

感覺嗨了吧,現在回想一下,從一開始的一長段代碼,成為這個樣子,有點寫純粹寫英文方法名就能完成方法一樣,看起來簡直不要太爽。

在最后這個if的判斷這里,也許有的朋友會問,為什么不直接把整個if換掉,而要先換if判斷里面的問題:

首先我想你認可的是:if判斷里面肯定有問題。那么,如果直接換掉整個if,你還會記得要繼續去解決if里面本來就像毒瘤一樣存在的問題嗎?一段代碼的臭味道并沒有什么,但如果養成想一步登天的壞習慣來執行重構,這是最糟糕的了。所以咱還是鼓勵當傻逼,看見了也說沒看見,每次只做一點點。

當然,別忘了每改動一處,就測試一次

現在我們再來測一下代碼度量值:(也許你會擔心那一堆多出來的方法會不會使得整個類太臃腫,答案是no,還是那句話,計算機根本不怕麻煩,最怕麻煩也因為繁瑣而容易出錯的是人,只要那些提取出來的方法都是private,外界就不會知道,也就是程序員外界調用此類時也根本不會知道這個類里面到底有多少私有方法)

哎呀媽呀!(甜馨威武~)各指標數據也是好看的不要不要的。(還能不能更優化?此處拋磚引玉,大家可下載源碼自行嘗試)

 

五、總結

1. 搭建基礎類模型【從無到有的質變,總好過對著一個空的class就開始無頭緒的寫】

2. 從單元測試開始,逐步完善類【保障正確率,而不是建了前臺才測試】

3. 自定義異?!纠谥赜谩?/p>

4. 代碼分析【通過重構,優化代碼,利于那一堆好處——易維護、易復用、易擴展、靈活性好——四個好處都有對應的意思和做法,詳細請參照程杰版《大話設計模式》】

上述四步是我在做這個項目(其他項目,也許會有其他的步驟)到目前為止,不斷在迭代(循環重復)操作的過程,使得代碼不斷向更正確更好的方向前進。建議大家把對自己有用的開發流程(也許我的只是小項目可用,大家可以多看大牛們的文章)當做內功一樣來修為,而不是外在的尚方寶劍(拿到了才NB,沒拿到就是菜B),要把思路練到不怕丟不怕忘,甚至總結自己的方法步驟。

不說了,得覓食去了,回來繼續照著上面步驟改代碼去,爭取早點兒提交svn給大家下載最新源碼(本篇的代碼已提交)。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
成人免费直播live| 性色av一区二区三区免费| 亚洲天堂男人的天堂| 国内精品小视频在线观看| 正在播放欧美视频| 青青草一区二区| 国产精品日韩av| 国产精品国产三级国产专播精品人| 国产成+人+综合+亚洲欧美丁香花| 成人精品视频久久久久| 日产精品99久久久久久| 国产精品成人国产乱一区| 国产欧美在线视频| 欧美激情videos| 日韩视频免费看| 深夜福利日韩在线看| 久久久av网站| 国产精品无码专区在线观看| 青青草国产精品一区二区| 亚洲第一视频网| 亚洲一区美女视频在线观看免费| 中文字幕在线看视频国产欧美| 欧美性猛交xxxx乱大交3| 欧美日韩高清区| 亚洲一品av免费观看| 午夜精品一区二区三区av| 欧美久久精品午夜青青大伊人| 国产成人综合亚洲| 国产一区二区精品丝袜| 亚洲欧美国产另类| 国产精品入口免费视频一| 成人网址在线观看| 日韩免费在线电影| 国产精品免费视频久久久| 色悠悠国产精品| 久久久免费精品视频| 青青精品视频播放| 欧美夫妻性视频| 亚洲欧美在线播放| 亚洲aⅴ男人的天堂在线观看| 国产精品国产三级国产专播精品人| 亚洲精品成人久久| 奇门遁甲1982国语版免费观看高清| 1769国内精品视频在线播放| 成人av色在线观看| 欧美一级在线亚洲天堂| 欧美成人在线免费| 久久久久久av| 国产一区二区三区四区福利| 国产在线播放不卡| 亚洲男人第一av网站| 欧美成人自拍视频| 亚洲一区国产精品| 日韩在线视频网| www.美女亚洲精品| 亚洲人成伊人成综合网久久久| 亚洲天堂2020| 成人午夜一级二级三级| 在线亚洲国产精品网| 欧美激情啊啊啊| 欧美性高潮在线| 日本欧美一二三区| 欧美高清电影在线看| 成人国产精品一区二区| 欧美性xxxxxxxxx| 国产精品久久在线观看| 精品久久久久人成| 九九热在线精品视频| 尤物九九久久国产精品的分类| 日韩精品极品毛片系列视频| 色悠久久久久综合先锋影音下载| 国产精品扒开腿做爽爽爽的视频| 亚洲成人a**站| 欧美夫妻性生活视频| 欧美一区二区三区精品电影| 成人激情av在线| 91精品国产乱码久久久久久久久| 在线观看成人黄色| 久久久久这里只有精品| 粉嫩老牛aⅴ一区二区三区| 久久久综合免费视频| 国产99久久久欧美黑人| 日韩在线观看视频免费| 91国内在线视频| 久久免费在线观看| 亚洲性69xxxbbb| 欧美日韩国产999| 亚洲石原莉奈一区二区在线观看| 97香蕉超级碰碰久久免费的优势| 亚洲爱爱爱爱爱| 国产91ⅴ在线精品免费观看| 日韩亚洲综合在线| 久久精品国产视频| 欧美极品少妇xxxxⅹ喷水| 国产69精品久久久久9| 国产精品美女无圣光视频| 亚洲xxxxx性| 奇米影视亚洲狠狠色| 日韩欧美中文字幕在线播放| 国语自产在线不卡| 亚洲天堂免费观看| 久久久精品在线观看| 高清欧美性猛交xxxx黑人猛交| 久久久99免费视频| 成人亚洲综合色就1024| 国产视频在线一区二区| 午夜精品美女自拍福到在线| 亚洲日本中文字幕| 亚洲第一偷拍网| 欧美高清在线观看| 亚洲欧美国产一本综合首页| 一本一本久久a久久精品综合小说| 欧美黄色片在线观看| 一本色道久久综合亚洲精品小说| 国产视频一区在线| 一区二区三区高清国产| 亚洲tv在线观看| 国产精品久久久久影院日本| 亚洲精品xxx| 欧美一性一乱一交一视频| 精品久久久久国产| 夜夜躁日日躁狠狠久久88av| 亚洲激情自拍图| 欧美日本在线视频中文字字幕| 97av在线视频| 欧美精品一二区| 国内外成人免费激情在线视频| 久久最新资源网| 欧美性生交大片免网| 亚洲最大成人在线| 欧美视频中文字幕在线| 91精品成人久久| 午夜精品一区二区三区视频免费看| 国内精久久久久久久久久人| 在线观看欧美www| 久久99久久99精品中文字幕| 一本色道久久88综合日韩精品| 性亚洲最疯狂xxxx高清| 欧美黑人性猛交| 国产一区二区三区18| 亚洲人成电影在线播放| 国产精品女人久久久久久| 欧美激情视频在线免费观看 欧美视频免费一| 色yeye香蕉凹凸一区二区av| 中文字幕久精品免费视频| 97超级碰碰碰| 亚洲一区二区三区四区在线播放| 亚洲乱码一区二区| 高清在线视频日韩欧美| 欧美日韩亚洲一区二区| 中文字幕亚洲色图| 另类色图亚洲色图| 91色视频在线导航| 日韩在线视频线视频免费网站| 国产成人综合一区二区三区| 国产欧美日韩丝袜精品一区| 中文字幕在线国产精品| 国产成人精品最新| 亚洲丝袜在线视频| 日韩大胆人体377p| 欧美国产极速在线| 欧美性猛交xxxxx免费看| 日韩视频在线免费观看| 日韩亚洲国产中文字幕|