<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    我的隱式生活(My Implicit Life)

    繼續(xù)搞“對象”,玩OO.

    首頁 新隨筆 聯(lián)系 聚合 管理
      11 Posts :: 1 Stories :: 39 Comments :: 0 Trackbacks

    2006年2月16日 #

    近期寫了個電子書的C/S模式的下載工具,一個server端,一個client端。

    目的就是想在公司能很方便的訪問家里那些收集很久電子書,方便查閱。

    用了1,2個星期,雖然寫的很爛,但是沒有用任何第三方的產(chǎn)品(server or db)。

    現(xiàn)在里面的書籍已經(jīng)接近200本了。

    注:server就用了家里的adsl,所以速度慢,關(guān)閉不定時。畢竟玩玩嘛。

    有興趣的朋友先裝個jdk1.5。再運行下面壓縮包里的exe文件執(zhí)行即可。

    點此下載

    User ID:???????????????blogjava
    Password:???????????? blogjava
    ?

    posted @ 2006-10-15 13:21 marco 閱讀(3472) | 評論 (9)編輯 收藏

    Java Collection Framwork中的類的確是最重要的基礎(chǔ)api,實現(xiàn)任何算法,基本上都很難離開它。

    因此理解這堆“集合(Collection)類”很有必要。聲明一下,以前一直都是叫它們集合類,但是好像Think In Java的作者鄙視了這個說法,嚴格的說應(yīng)該叫Container類,而后看了它整整一章書以后,覺得還是人家說的有道理。

    它說這個container類庫,包含了兩大類,Collection和Map,而Collection又可以分為List和Set。當然這些抽象概念都被定義成了接口。

    話說,這樣的分類的確是嚴格按照類之間的繼承關(guān)系來說得,但是俺總覺得很別扭,真動手的時候,還是很難選擇。當然,Anytime and Anywhere使用ArrayList絕對都能解決問題,但這樣做畢竟太農(nóng)民了一點。

    所以,我自己有了一些想法。先回歸到最基本最基本的數(shù)據(jù)結(jié)構(gòu)的層面,管你是Collection還是Container,反正描述的都是一堆東西吧。數(shù)據(jù)結(jié)構(gòu)第一章講了一個結(jié)構(gòu):在物理上連續(xù)分配空間的順序結(jié)構(gòu),叫順序表(希望記性是好的),而離散分配空間的,應(yīng)該叫做鏈表,最常用的就是單鏈表。這兩個東西,其實就是很多復(fù)雜數(shù)據(jù)結(jié)構(gòu)的基礎(chǔ),還記得嗎,當時就是講完這些東西,才開始講棧、隊列、二叉樹、有向無向圖的。所以,這個順序結(jié)構(gòu)是很基礎(chǔ)的。而在JAVA中,順序表對應(yīng)的就是List接口,而一般順序表就是ArrayList(有效進行隨機index查找);而單鏈表就是LinkedList(有效進行插入和刪除),兩個的優(yōu)劣當年都講爛了,這里就不說了。

    有了這兩個結(jié)構(gòu)以后,JAVA就不提供Stack和Queue單獨的類了,因為,用戶可以用上面兩個類輕易的去實現(xiàn)。

    那Set和Map有怎么跟List連上關(guān)系呢?

    我認為可以把它們看成是無序和單一的List(Map只是兩個有映射關(guān)系的List罷了)。

    Set和Map無序和單一的特性,決定了它們天大的需求就是根據(jù)關(guān)鍵字(元素對象)檢索。so,為了效率,必須hash。

    有了HashSet和HashMap。

    同時,如果非要保持住元素的順序,有了LinkedHashSet、LinkedHashMap。


    結(jié)論:

    假如你的需求是
    1:往Container中放的對象是無序且單一的;
    2:經(jīng)常要檢索。
    用HashSet或HashMap吧。

    ps:這兩個條件其實是一回事,因為如果是不單一的話,你去檢索它干嘛。

    如果進而需要保持元素的順序,不要讓他順便iteration,那就選擇LinkedHashSet和LinkedHashMap。

    假如你的需求不滿足以上1&2,那你放心,List肯定能幫你解決,你只要稍微想一下是ArrayList好還是LinkedList好。

    題外話:

    關(guān)于Hash,務(wù)必記得要讓自己的元素對象override hashCode()和 equles() 方法,要不你直接可以洗了睡。

    關(guān)于所有這些Container,務(wù)必記得有個輔助類叫Interator,遍歷盡量要用它。

    關(guān)于一些老的Stack、Vector、HashTable,聽說以后不要用了哦。收到啦!!

    posted @ 2006-09-20 16:53 marco 閱讀(2325) | 評論 (0)編輯 收藏

    任何信息,基本都是以文字的形式傳播和記錄下來的。

    在計算機中,文字就是字符的集合,也就是字符串,C就是因為對字符串設(shè)計的不好,才那么容易溢出。而別的一些高級語言,對于這個進行了很多的改進。

    編程的人由于技術(shù)方向和應(yīng)用方向的不同,日常編程的內(nèi)容差距很大。但是對于字符串的處理,那可是永遠都避不開的工作。

    昨天跑步的時候,想了一下,對于字符串的操作有那么多(search,match,split,replace),感覺很煩雜,能不能抓住這些操作的一個基本集?

    不知道對不對,反正想出來了一個,這個基本操作就是search,這里的search的意思是:在輸入串中找到目標串的開始位置(start index),和結(jié)束位置(end index)。

    有了這個基本集,別的操作都很好衍生出來:

    局部match:其實就是要求search操作至少返回一個start index。

    全match:其實要求search操作的至少返回一個start index,并且start index要為零,end index要為輸入串的全長。

    split:其實就是search操作之后,把前一個end index和當前的start index之間的字符串截出來而已。

    replace:其實就是search操作之后,把start index和end index之間的字符串換成另外的而已。

    所以,歸根到底,都是一個search操作的拓展罷了。這么一想,感覺清晰多了。

    這么一來,API對search的能力支持的好壞和效率高低是衡量字符串操作功能的標準,當然,如果有直接支持match,split,replace操作的話就更好了。

    java對字符串search的支持,最基本的就是下面的String的indexOf方法:

    int indexOf(String str)
    ????????? Returns the index within this string of the first occurrence of the specified substring.

    這里我想說的是,很多時候我們所謂要search的目標串,根本就不是固定單一的,而是變化多樣的。如果只有一兩種情況,最多用兩次上面的方法唄。但是有些情況是近乎不可能羅列的,例如,我們講的代表email的字符串,我們不可能遍歷它吧。

    所以,需要一種能夠通用表達字符串格式的語言。這就是Regular Expression(re)。

    假如上面方法indexOf的str參數(shù)能支持re做為參數(shù)的話,那對于這種多樣的search也可以用上面的方法了。

    可惜,indexOf不支持re作為參數(shù)。

    so,以下就介紹java api中可以用re作為參數(shù)的字符串操作方法(參數(shù)中的regex就是re)。

    --------------------->>
    String類的:

    全match操作:
    boolean matches(String regex)
    ????????? Tells whether or not this string matches the given regular expression.

    全replace操作:
    String replaceAll(String regex, String replacement)
    ????????? Replaces each substring of this string that matches the given regular expression with the given replacement.

    首個replace操作:
    String replaceFirst(String regex, String replacement)
    ????????? Replaces the first substring of this string that matches the given regular expression with the given replacement.

    全split操作:
    String[] split(String regex)
    ????????? Splits this string around matches of the given regular expression.


    有限制數(shù)的split操作:
    String[] split(String regex, int limit)
    ????????? Splits this string around matches of the given regular expression.

    <<---------------------

    可惜啊,可惜,可惜java的String類里面沒有可以支持re的search方法,那如果要用re來search,只好使用java中專門的re類庫。

    java中的re類庫主要就兩個類,一個叫Pattern,顧名思義,代表re的類。一個叫Matcher類,反映當前match狀況的類(如存放了當前search到的位置,匹配的字符串等等信息)。

    一般在構(gòu)造中,“re的表達式”作為參數(shù)傳遞入Pattern類,“輸入串(待過濾串)”作為參數(shù)傳遞入Matcher類。

    然后使用Matcher類的字符串search方法就可以了。Matcher真正提供search功能的API叫find。下面列出。
    --------------------->>
    Matcher類search操作相關(guān)的方法:

    boolean lookingAt()
    ????????? Attempts to match the input sequence, starting at the beginning, against the pattern.

    boolean matches()
    ????????? Attempts to match the entire input sequence against the pattern.

    boolean find()
    ????????? Attempts to find the next subsequence of the input sequence that matches the pattern.

    String group()
    ????????? Returns the input subsequence matched by the previous match.

    <<---------------------

    前三個都是search方法,返回成功與否。第四個是返回當前search上的字符串。

    ok,至此。使用re的search操作也有眉目了。

    當然,Pattern和Matcher也包含直接使用re進行的match,split,replace操作。

    --------------------->>
    Patter類別的字符串操作方法

    全match操作:
    static boolean matches(String regex, CharSequence input)
    ????????? Compiles the given regular expression and attempts to match the given input against it.

    全split操作:
    String[] split(CharSequence input)
    ????????? Splits the given input sequence around matches of this pattern.

    有限制數(shù)的split操作:
    String[] split(CharSequence input, int limit)
    ????????? Splits the given input sequence around matches of this pattern.


    Matcher類別的字符串操作方法

    全replace操作:
    String replaceAll(String replacement)
    ????????? Replaces every subsequence of the input sequence that matches the pattern with the given replacement string.

    首個replace操作:
    String replaceFirst(String replacement)
    ????????? Replaces the first subsequence of the input sequence that matches the pattern with the given replacement string.

    動態(tài)replace(replacement可以根據(jù)被替代的字符串變化而變化)
    Matcher appendReplacement(StringBuffer sb, String replacement)
    ????????? Implements a non-terminal append-and-replace step.

    StringBuffer appendTail(StringBuffer sb)
    ????????? Implements a terminal append-and-replace step.

    <<---------------------

    總結(jié):
    當必須使用re的時候,search操作就要用到Pattern,Matcher,當然動態(tài)的replace操作也要用到這兩個類。而別的match,replace,split操作,可以使用pattern,Matcher,當然也可以直接使用String,推薦還是用回咱們的String吧。

    注:以上都是看jdk1.4以上的文檔得出的結(jié)論,以前版本不能用不負責任。

    posted @ 2006-08-31 15:13 marco 閱讀(2692) | 評論 (0)編輯 收藏

    創(chuàng)建和銷毀對象

    重點關(guān)注對象的創(chuàng)建和銷毀:什么時候、如何創(chuàng)建對象,什么時候、什么條件下應(yīng)該避免創(chuàng)建對象,如何保證對象在合適的方式下被銷毀,如何在銷毀對象之前操作一些必須的清理行為。

    嘗試用靜態(tài)工廠方法代替構(gòu)造器

    如果一個 client 要實例化一個對象來使用,傻 b 都知道應(yīng)該先調(diào)用類的構(gòu)造器來 new 一個對象,之后再調(diào)用相應(yīng)的方法。除了這個方式, Java Effective 還建議了另一種方法:用靜態(tài)工廠方法來提供一個類的實例。以下的例子不反映兩者的優(yōu)劣,只是反映兩者在代碼實現(xiàn)上的不同,優(yōu)劣之后再談:

    假設(shè)咱們要一個顏色為黑色、長度為 50cm 的錘子,自然就用構(gòu)造器創(chuàng)建一個

    Hammer myHammer =? new Hammer(Color.BLACK, 50);

    而用靜態(tài)工廠方法來實例化一個對象,如下

    Hammer myHammer = Hammer.factory(Color.BLACK,50);

    也可以用專門的一個工廠類來實例化

    Hammer myHammer = Toolkit.factory(“Hammer”, Color.BLACK,50);??

    單純從上面的代碼上看,真的只有傻 b 才會選擇靜態(tài)工廠的方法,完全就是多此一舉,直接 new 又快又爽,搞這么麻煩做莫斯(武漢話“什么”的意思)?

    別急,別急,你急個莫 b (武漢粗話:基本就是“你急個毛”的意思)?

    下面就說說用靜態(tài)工廠代替構(gòu)造器的好處( advantage )和不好處( disadvantage )。

    第一個好處,講你都不信,行家們認為,構(gòu)造器有一個不好的地方就是:這個方法的簽名( signture )太固定了。

    構(gòu)造器的名字是固定的,生個 Hammer ,構(gòu)造器的名字就是 Hammer (……),唯一能變化的地方就是參數(shù),假設(shè)我的這個錘子有兩個很變態(tài)的構(gòu)造需要:

    1 :第一個參數(shù)是顏色( Color 型),第二個參數(shù)是錘子頭的重量( int 型)。

    Hammer Color c, int kg {

    //remainder omited

    }

    2 :第一個參數(shù)是顏色( Color 型),第二個參數(shù)是錘子的長度( int 型)。

    Hammer Color c, int cm {

    //remainder omited

    }

    感覺滿足需要了,但是細心一看,完了,構(gòu)造器的參數(shù)列表類型重復(fù)了,肯定編譯通不過,這是面向?qū)ο髽?gòu)造器天生的缺陷——唯一的變化就是參數(shù),參數(shù)都分辨不了,就真的分辨不了。

    而另外就算參數(shù)能分辨的了,構(gòu)造器一多,它的參數(shù)一多,您根本就不知道每個參數(shù)是用來干什么的,只能去查閱文檔,在您已經(jīng)眼花繚亂的時候再去查文檔,一個一個的對,折磨人的活。

    這個時候,您就可以考慮用靜態(tài)工廠方法來實例化對象了。因為靜態(tài)工廠方法有一個最簡單的特點就是:他有可以變化的方法名(構(gòu)造器的名字變不了)。用名字的不同來代表不同的構(gòu)造需要,這么簡單的普通的特點在這里就是它相對于構(gòu)造器的 advantage

    如上面的錘子的例子可以這樣:

    1 Hammer.produceByWeight (Color c, int kg){

    //remainder omited

    }

    2 Hammer.produceByHeight (Color c, int cm){

    //remainder omited

    }

    這是不是一目了然多了。嗯,我是這樣認為的。

    第二個好處,“靜態(tài)工廠方法不需要每次都真的去實例化一個對象”——其實這也是另一些優(yōu)化方法的前提。

    構(gòu)造器的每次 invoke 必定會產(chǎn)生一個新的對象,而靜態(tài)工廠方法經(jīng)過一定的控制,完全可以不用每次 invoke 都生成一個新的對象。

    為什么不每次都生成一個對象的原因就不必說了,因為原因太明顯。這個原因就是為什么要“共享”對象的原因。

    下面講講通常使用的兩種共享具體策略,也就是具體方法了:

    1 :單例模式的需要,一旦需要某個對象有單例的需要,必定對于這類對象的構(gòu)造只能用靜態(tài)工廠方法了。

    2 flyweight 模式和不變( immutable 模式的需要,這兩個模式很多時候都說一起使用的,一旦一些對象我們認為是不變的,那自然就想拿來重用,也就說共享,而 flyweight 就是用來重用這些小粒度對象的。

    Boolean.valueOf (boolean) 方法:

    Boolean a = Boolean.valueOf (100);

    Boolean b = Boolean.valueOf (100);

    ?a,??b兩個引用都是指向同一個對象。

    這些對象都是不變的,而 valueOf 的控制就是用的 flyweight 方法。

    這種一個狀態(tài)(如上面一個數(shù)字)對應(yīng)的對象只有一個還有一個好處,就是可以直接通過比較“引用”來判斷他們是否 equel (這里的 equel 是邏輯相等的意思),以前需要 a.equels(b) ,而一旦用“ flyweight 模式和不變( immutable 模式”后,避免了產(chǎn)生多余的相同對象,用 a==b 就可以達到 a.equels(b) 的目的了。這樣當然優(yōu)化了 performance ??

    第三個好處,其實就是工廠方法的核心好處——我把它稱為“抽象類型構(gòu)造器”。它可以為我們提供一個抽象類型的實例,同時必要的隱藏了抽象類型的具體結(jié)構(gòu)。這是 new 怎么都達不到的。

    這種模式的好處其實就是面向?qū)ο蟮淖詈诵牡暮锰帲橄蠛途唧w可以分離,一旦抽象定義好了,具體的東西可以慢慢的變化,慢慢的拓展——開閉原則。

    Collections Framework API ,都是描述集合類型的接口,也就是對于客戶端來看,只有 Collection 這個類要認識,而實際上,實現(xiàn)這個接口的 Collection 是多種多樣的。如果要讓用戶都知道這些具體實現(xiàn)的 Collection ,就增加了復(fù)雜度。

    這時,通過一個靜態(tài)工廠方法,就可以隱藏各種 Collection 的具體實現(xiàn),而讓 Client 只使用返回的 Collection 對象就可以了。

    這里還可以加上一些權(quán)限控制,如這些實現(xiàn)只要對于工廠來講是可以訪問的,不用是 public 的,而他們只要通過 public 的工廠就可以提供給用戶。非常有利于代碼的安全。

    靜態(tài)工廠方法的第一個缺點就是:使用靜態(tài)工廠方法創(chuàng)建的類的構(gòu)造器經(jīng)常都是非公共或非 protected 的。 這樣,以后這些類就沒有辦法被繼承了。不過也有人說,不用繼承就用 composition 唄。也是!呵呵。

    靜態(tài)工廠方法的第二個缺點是:在 jdk 文檔里,這些靜態(tài)工廠方法很難跟別的靜態(tài)方法相區(qū)別。 而文檔中,構(gòu)造器是很容易看到的。

    為了一定程度解決這個問題,我們可以用一些比較特別的名字來給這類靜態(tài)工廠方法來命名。最常用的有:

    valueOf —— 用來放回跟參數(shù)“相同值”的對象。

    getInstance —— 返回一個對象的實例。單例模式中,就是返回單例對象。

    總結(jié):靜態(tài)工廠方法和構(gòu)造器都有各自的特點。最好在考慮用構(gòu)造器之前能先考慮一下靜態(tài)工廠方法,往往,后者更有用一點。如果權(quán)衡了以后也看不出那個好用一些,那就用構(gòu)造器,畢竟簡單本分多了。

    posted @ 2006-07-15 12:35 marco 閱讀(626) | 評論 (0)編輯 收藏

         摘要: 關(guān)鍵字:Observer Pattern、Java Thread、Java Swing Application 1 近來的閱讀 近來寒暑不常,希自珍慰。武漢天氣不是狂冷,就是狂熱,不時還給我整個雪花,就差冰雹了。   自己做的事吧,也沒有什么勁兒。看看自己喜歡的東西,等著希望中的學(xué)校能給我offers(是復(fù)數(shù)),看著自己想去又不想去的公司的未來同事在群里面幻想未來的樣子,別操你大...  閱讀全文
    posted @ 2006-03-14 01:51 marco 閱讀(2603) | 評論 (2)編輯 收藏

    昨天,以前師兄做的系統(tǒng)因為漏洞又被投訴,頭讓俺做個presentation給實驗室下一級同學(xué)總結(jié)一下,避免以后再犯錯。今天講完了就放上來,存?zhèn)€證,以后也好翻閱溝通。

    不涉及主機安全、網(wǎng)絡(luò)安全、數(shù)據(jù)庫安全,只從Web應(yīng)用程序的角度,整理歸納一下面臨的主要安全問題。

    點擊看大圖。
    r_Web應(yīng)用程序安全問題.jpg

    posted @ 2006-03-09 00:12 marco 閱讀(750) | 評論 (5)編輯 收藏

    這幾天瞄了幾本設(shè)計模式的書,沒有細看具體模式啦,而是老是琢磨那些深奧無比的話。這些話經(jīng)常出現(xiàn)在計算機的書籍中,很有禪意,也有哲理。聽說,高手就喜歡寫點這樣的話。

    還有就是細心體味了一下OO的設(shè)計原則,這些原則是凌駕于模式之上的,也就是更宏觀的原則。

    其中,最高指導(dǎo)的一個就是“開-閉”原則。別的原則,里氏代換原則、依賴倒置原則、組合/聚合復(fù)用原則和迪米特法則都是為了達到“開-閉”原則而出現(xiàn)的規(guī)則。

    這些原則告訴我很多東西,聚焦于一點就是要“面向抽象”來做一切事情。

    分析對象的時候,要多分析設(shè)計“抽象”的概念,對象之間的聯(lián)系要多基于抽象的概念而不是具體,這樣具體才能能夠變化,這樣才是開閉。用我自己的話就是要“游走于 抽象”。

    這里有一個我必須記住的就是,在封裝變化時候,多用聚合/組合,少用繼承。在封裝原子變化并且是同類型對象時才用繼承,別的都盡量用聚合/組合。而且盡量不要用多級繼承,多級繼承一般意味著有兩種變化脈絡(luò),可能的話,讓兩種變化脈絡(luò)獨立演化。很明顯,一獨立演化,又要聚合/組合了。

    還有一個必須記住的是:運用抽象以后,客戶端的使用發(fā)生了巨大的變化。不再是指那兒用那兒。而是要做更多的準備工作,因為運用抽象,本身就把具體“組合”的職責推遲到使用的階段。那誰使用,肯定是客戶端。所以,客戶端的使用要革新。要習(xí)慣用工廠,習(xí)慣把一系列的抽象定具體了,并按照一定方式“組合”起來用。而且,最終要善于用接口來調(diào)用方法。

    用小飛推薦的一個工具畫了個圖,如下:
    o_好的OO思想.jpg

                           MARCO ZHANG 2006年2月27日7:18:57

    posted @ 2006-02-27 07:40 marco 閱讀(868) | 評論 (4)編輯 收藏


    “共享”的思想

    共享的idea是生活中最基本的idea,不必有意的使用,到處已經(jīng)存在了。在生活中,大部分事物都是被多人多次使用的,這都是共享的實際應(yīng)用。

     

    之于OO的共享

     

    OO中的共享,無非就是說讓“對象”也能被“多人多次”(這里的“人”也無非就是進程、線程而已)使用,更詳細的說,就是要讓對象的生存空間更大一些,生存周期更長一些。

     

    自己悶個兒腦子,提煉出了幾個需要使用共享的環(huán)境(context),也可以說是原因吧:

    1.         為了保持“對象”的一致,我們需要共享。例如,“國家主席”就一個,不能多了,如果多了,難免決策混亂。

    2.         為了控制“對象”的存儲空間,我們需要共享。畢竟,目前來說,系統(tǒng)的memory還是編程時最珍貴的資源。

    3.         為了優(yōu)化“對象”的創(chuàng)建消耗,我們需要共享。如果,一個對象的創(chuàng)建過程消耗太大,系統(tǒng)不能支持頻繁的創(chuàng)建,共享的使用它也是一個好主意。

    4.         等等。

     

    而在實際的應(yīng)用中,往往我并沒有細想“我為什么使用共享?”,已經(jīng)不自覺的就用了;如果真的認真分析起來,基于的環(huán)境也是多樣,并不會只是上面的其中一種。

     

    常用的“共享”方法或模式(我曾經(jīng)用過的,知道的不多,望諒解):

    1.         Singleton模式”:一個class就一個對象實例,大家都用它,滿足context1

    2.         pool技術(shù)”:只提供一定數(shù)目的對象,大家都用他們,實現(xiàn)context2context3

    3.         flyweight模式”:一個class的一個狀態(tài)就一個對象實例,實現(xiàn)一個狀態(tài)對象的共享,實現(xiàn)context2context3

     

    使用時要注意的地方:

    1.         確定共享的scope。例如,在Java Web Application中就是選擇是pagesession還是application,當然也可以是jvm級別的static

    2.         確認thread safe。當共享的對象可能被多個線程共享時,這是不可以回避的問題。

    3.         應(yīng)對對象狀態(tài)的變化。一旦共享的對象發(fā)生了變化,我們怎么處理?改變之,舍棄之?也是我們需要確定的。

     

    項目中的應(yīng)用:

     

    項目需求:

    為學(xué)校的同學(xué)提供Web查詢,查詢的內(nèi)容有很多。其中,“查課表”、“查考表”是最為關(guān)鍵的需求,以后可能還要提供“查詢空閑自習(xí)教室”的功能。

    在這些查詢中,有一個共同點,就是都涉及“教室”這一對象。“查課表”時要告訴同學(xué)在哪個教室上課,“查考表”時要告訴同學(xué)在哪個教室考試,等等。

     

    數(shù)據(jù)庫設(shè)計:

    對于“查課表”用例,有關(guān)的數(shù)據(jù)庫設(shè)計如下:
    o_5-1-1.jpg 


    對象層的設(shè)計:

    o_5-1.JPG
     

    學(xué)生每查詢一門課程的課表,系統(tǒng)就會sql查詢“視圖V_LESSONSCHEDULE”,進而生成一個LessonSchedule對象,然后返回給用戶顯示。當然,在生成這個LessonSchedule對象的過程中,屬于它的Classroom對象,以及更深一步的BuildingArea對象都會生成。下面就是這個過程的順序圖:

     

    o_5-2.JPG 


    因此,每生成一個“課表”對象(
    LessonSchedule)或“考表”對象(ExamSchedule)時,都要:

    1.         查數(shù)據(jù)庫中的教室、教學(xué)樓、校區(qū)的信息;

    2.         創(chuàng)建相應(yīng)的“教室對象”(包括了屬于它的“教學(xué)樓”對象和“校區(qū)”對象)。

     

    考慮共享“教室”對象

    “教室”對象一旦可以生成以后,完全可以給后續(xù)共享使用,不必要每個查詢都要去生成。

     

    詳細說是基于下面的考慮:

    1.         這類查詢用例(查課表,查考表)發(fā)生的頻繁很高很高,也就是說,一旦讓用戶查起來,系統(tǒng)中會產(chǎn)生大量的“教室”對象這類對象,應(yīng)該說會占很大的內(nèi)存空間。

    2.         共享“教室”對象后,可以減少對數(shù)據(jù)庫的查詢次數(shù),并降低了查詢粒度(以前是基于二級視圖查詢,現(xiàn)在可以基于基本表查詢),提高了一點數(shù)據(jù)庫查詢性能。

     

    當然,同時我腦袋中也有反對的聲音:

    1.         雖說,這類查詢會產(chǎn)生很多相同的“教室”對象,但是JVM本生提供的垃圾回收功能完全可以處理它。除非,“同時”有很多很多這類對象都在被使用,根本回收不了,才會造成內(nèi)存短缺。

    2.         如果,我以某種共享機制讓這些“教室”對象,在系統(tǒng)中存在下來(延長了生命周期)了。而它們本身的數(shù)目就很多(如,我們學(xué)校就有××),可能還沒有等你用上,系統(tǒng)已經(jīng)掛了。另外,如果不是同時有很多查詢需要,我留這么多“教室”對象在系統(tǒng)里,反而是占了內(nèi)存,而不是優(yōu)化了內(nèi)存的使用。

    1.         所有模式的通病――“增加了復(fù)雜度”。

     

    結(jié)論:

    經(jīng)過我們分析,系統(tǒng)對于“教室”對象的重復(fù)使用,頻繁程度非常高。一般,有10個人同時使用,就有5個人左右涉及的用例要使用“教室”對象。把它共享起來,還是有一定必要的。

     

    進一步考慮:

    而實際上,我們可以進一步共享下去:

    除了讓客戶端共享“教室對象(Classroom)”外,還可以讓“教室對象”共享“教學(xué)樓對象(Building)”,和讓“教學(xué)樓對象”共享“校區(qū)對象(Area)”。

    因此,最終的共享是在三級上都實現(xiàn)。

     

    Flyweight模式:

     

    特點:

    書上說:“享元模式可以使系統(tǒng)中的大量小粒度對象被共享使用”。第一,對象出現(xiàn)的量要大,想必這比較好理解,很少使用的也就沒有必要共享了;第二,要小粒度,我比較納悶?難道對于大粒度對象就不行嗎?可能書上認為,大粒度對象的共享已經(jīng)占了比較大的空間,沒有小對象那么有效吧。

     

    另外,書上還說,要使用“享元模式”,被共享的對象的狀態(tài)(類別)要比較固定,這樣就可以為每一個狀態(tài)僅僅創(chuàng)建一個對象。當然,如果每次使用對象時,對象的狀態(tài)都是不一樣的,那就根本不存在共享這些對象的必要了。

     

    聯(lián)系項目思考:

    基于上面對項目的分析,“教室”、“教學(xué)樓”、“校區(qū)”對象都是在系統(tǒng)中會被大量使用的對象,而且粒度的確比較小;并且它們有固定的類別,而且不易改變。如校區(qū)對象,暫時就有4個。教學(xué)樓可能4050個左右。很適合“享元模式”的使用環(huán)境。

     

    確定共享方式:

    1.         確定共享對象的scope。在本web程序中,這些共享對象的scope理應(yīng)是application,而更簡單的一個作法就是把這些對象設(shè)為static,我選擇后者。

    2.         確認thread safe。這些對象是可能被多個servlet訪問的,也就是有可能存在多線程訪問。但是,由于這些對象的可變性很差,一旦創(chuàng)建就不大可能變化。因此,我決定把這寫共享對象設(shè)計成不變模式的,一旦創(chuàng)建就只會被讀取,而不會改寫,這樣就不存在多線程控制的問題了。

    3.         應(yīng)對對象狀態(tài)的變化,如某個教室的類型變了。這里采取的是舍棄的方法,為每個工廠添加了一個清空方法――clear(),用于清空已經(jīng)生成的共享對象。

     

    設(shè)計類圖:

    o_5-3.JPG 

    當然,也可以把這些工廠都設(shè)計成“Singleton模式”的,使它們只會有一個實例。

     

    客戶端使用:

    由于共享的對象都被包含在了“課表”和“考表”對象里,不會被客戶端直接訪問,因而不會對客戶端的使用有任何影響:

     

    實例代碼

     
     1//取得編號為32號課程的“課表對象”
     2
     3LessonSchedule oneLesson = LessonSchedule.findByLessonNum(32);
     4
     5 
     6
     7//獲得教室對象
     8
     9Classroom oneClassroom = oneLesson.getLnkClassroom();
    10
    11 
    12
    13//獲得教學(xué)樓對象
    14
    15Building oneBuilding = oneClassroom.getLnkBuilding();
    16
    17 
    18
    19//獲得校區(qū)對象
    20
    21Area oneArea = oneBuilding. getLnkArea();
    22
    23 
    24
    25//再次重新生成一個編號為32號的“課表對象”
    26
    27LessonSchedule twoLesson = LessonSchedule.findByLessonNum(32);
    28
    29 
    30
    31//獲得教室對象
    32
    33Classroom twoClassroom = twoLesson.getLnkClassroom();
    34
    35 
    36
    37//獲得教學(xué)樓對象
    38
    39Building twoBuilding = twoClassroom.getLnkBuilding();
    40
    41 
    42
    43//獲得校區(qū)對象
    44
    45Area twoArea = twoBuilding. getLnkArea();
    46

    oneClassroomtwoClassroomoneBuildingtwoBuildingoneAreatwoArea由于都是32號課程的東西,根據(jù)我們的設(shè)計意圖,應(yīng)該實現(xiàn)共享。

    而實際上,它們每對的確是同一個對象的引用。因此,實現(xiàn)了預(yù)期的設(shè)想。

     

    Review

    在本項目中,當?shù)谝淮卧O(shè)計出來的時候,我們發(fā)現(xiàn)了某些對象恰好有共享的需要。

     

    而更多的實際情況是,這些需要共享的“信息或狀態(tài)”在設(shè)計中并不是那么恰好的表現(xiàn)為“一個對象”的粒度,而是要不就包含在一個對象內(nèi)部,要不就跨幾個對象。在這樣的情況下,共享的設(shè)計更多是發(fā)生在代碼重構(gòu)階段而不是第一的設(shè)計階段。當然,為了共享對象而做出的代碼重構(gòu),最重要的一步就是把需要共享的“信息或狀態(tài)”設(shè)計成為新的對象。

     

    對于,“享元模式”來說,就是要把需要共享的“信息或狀態(tài)”設(shè)計成“享元對象”。別的在此就不說了,因為我也不懂了,呵呵。


                                              MARCO ZHANG 2006年2月23日13:48:49

    posted @ 2006-02-23 14:14 marco 閱讀(1285) | 評論 (4)編輯 收藏

    廢話:

    預(yù)料中的日志3暫時生產(chǎn)不出來,話說難產(chǎn)就好,別夭折就行,有點掉價哦,呵呵。

     

    因為有些東西還沒有想清楚。那就先搞個四吧,這個東西還是清楚那么一點的。

    一版描述:

    項目需求

    使每個servlet能對用戶訪問權(quán)限進行檢查。簡單來說,就是要給每個servlet加個鎖,有鑰匙的用戶才能訪問。

     

    而項目中用戶所謂的訪問權(quán)限是基于他擁有的角色。也就是說,servlet對用戶訪問權(quán)限的檢查,就是對他所擁有角色的檢查。暫時,每個用戶只能擁有一個角色。

     

    項目的角色很多,但是在web端暫時只有如下的三種:

     

    項目暫時角色

    游客,學(xué)生,教師

     

    既然這樣,servlet的加鎖方式就有:

     

    servlet加鎖方式

     游客,學(xué)生,教師,
    游客或?qū)W生,游客或教師,學(xué)生或教師,
    游客或?qū)W生或教師

     注:上面的“游客”就是“游客角色有權(quán)訪問”的意思,依此類推。

     

    這里只有關(guān)系“或”(||),如果一個servlet的加鎖方式是“學(xué)生或教師”,也就是說擁有學(xué)生或教師角色的用戶都可以訪問它。關(guān)系“與”(&&)在這里不太可能存在,因為沒有需求說:某個servlet一定只能由“既是學(xué)生又是教師的用戶”才能訪問,而且前面也說了,暫時一個用戶“有且只有”一個角色。

     

    So we can get following function


    “加鎖方式數(shù)” = 2的“角色數(shù)”次方 - 1


    “加鎖方式數(shù)”是關(guān)于“角色數(shù)”的指數(shù)函數(shù),也就是說它是關(guān)于“角色數(shù)”成“指數(shù)級”增長的,應(yīng)該說很快了吧。


    3
    個角色就有23次方-1個,也就是7個加鎖方式。

     

    運用OO的最基本方式,就是封裝對象。既然有為servlet“看門”的責任,那就把這個責任封裝成一個對象,用個俗名:validator

     

    接著,運用共性和個性的分析方法,既然有那么多種不同的看門方式(加鎖方式),那就搞一個接口,然后讓各種“不同”都實現(xiàn)這個接口,形成子類。那就有下面的圖:

    o_4-1.JPG

    可以看到,由于有7個加鎖方式,那就要有7個子類。每個子類根據(jù)自己邏輯override接口的validate方法。

     

    這樣,對于一個servlet,想讓它上什么樣的鎖,只要讓它拿到對應(yīng)的子類的引用即可,如下圖中的ClientServlet,我們規(guī)定只能有“學(xué)生或教師”才能訪問它。它的部分代碼便是:


    o_4-2.JPG

     

     

    1//new對應(yīng)的Validator接口的子類。
    2//這里是學(xué)生或教師可訪問,因此要new Student_Or_Teacher_Validator
    3Validator validator = new Student_Validator();
    4//然后調(diào)用驗證方法就可以了
    5boolean ok = validator.validate();

    至此,第一個解決方案就出來了。

    思考:

    不足

    validator接口的子類數(shù)目隨“角色數(shù)”成“指數(shù)級”增長,數(shù)量太多;而且子類中重復(fù)邏輯的代碼很多,如“Student_Or_Teacher_Validator”就重復(fù)了“Student_Validator”和“Teacher_Validator”的邏輯,萬一“Student_Validator”的邏輯要改,只要涉及Student的子類都要跟著改,維護上不方便。

     

    進一步改進的可能

    Student_Or_Teacher_Validator類”的validate方法很大程度上就是“Student_Validator類”的validate方法和“Teacher_Validator類”的validate方法“或操作”出來的結(jié)果。

     

    因此,能不能考慮由“Student_Validator類的validate方法”和“Teacher_Validatorvalidate方法”動態(tài)的構(gòu)造一個功能如“Student_Or_Teacher_Validator類的validate方法”。

     

    這樣,“Student_Or_Teacher_Validator”就可以省略了,只剩下一些原子的“角色類”。要實現(xiàn)Student_Or_Teacher_Validator的驗證功能,拿Student_ValidatorTeacher_Validator裝配一下就可以了。

     

    結(jié)論,需要根據(jù)實際情況,動態(tài)的改變(裝配)“Validator接口對象”的validate方法。

     

    第一個火花就是“裝飾模式”。它可以讓客戶端動態(tài)的組裝對象的方法。真神奇!

    第二版來啦

    o_4.JPG

     

    注:上圖中出現(xiàn)了AndRelationAnd的一系列角色類,可以暫時省略不看,因為前面說了,現(xiàn)在還沒有“與”關(guān)系這個需求。只看Or的就可以。

     

    我喜歡叫這個模式為洋蔥模式,一層包一層,最外層對象某方法的邏輯是由內(nèi)部一層一層對象的同一方法組合出來的。

     

    使用了這個模式,便不用如一版那樣實現(xiàn)那么多子類,只要實現(xiàn)幾個“角色類”即可,這里有3個(學(xué)生角色:Or_Student、教師角色:Or_Teacher、游客角色:Or_Guest)。所有一版中別的子類都可以由這3個組裝出來。

     

    如要生成一版中的Student_Or_Teacher_Validator對象,可以用Or_StudentOr_Teacher兩個對象“Or”出來:

     

    1Validator validator = new Or_Student(new Or_Teacher(OrRelation(req)));

     

    如要生成一版中的Guest_Or_Student_Or_Teacher_Validator對象,可以用Or_StudentOr_TeacherOr _Guest三個對象“Or”出來:

     

    1Validator validator = new Or_Student(new Or_Teacher(new Or_Guest(OrRelation(req))));

     

     

    這種一層包一層的new方式,是不是很像洋蔥?第一次看是很不習(xí)慣,看多了就覺得習(xí)慣了。

    對客戶端的影響:

    一版中,客戶端要什么樣的驗證類,就直接使用具體類。

    二版中,客戶端要什么樣的驗證類,它的工作多了那么一丁點,它需要先組裝一下,正如上面的例子。這種組裝的方法很易于理解和使用,不會給客戶端帶來任何的不便。如果實在覺得客戶端組裝不出來(傻B客戶端),也可以搞個工廠給它supply

    優(yōu)點:

    相對一版最明顯的優(yōu)點就是類的數(shù)目少了很多。

     

    一版不是說“指數(shù)級”嗎?這里只是線性的了。假設(shè)某一天系統(tǒng)拓展到有10個角色,一版就有210次方那么多個,也就是1024個驗證類。

     

    而二版還是10個角色類,別的都可以在客戶端使用的時候,動態(tài)的組裝

     

    更重要的是代碼結(jié)構(gòu)好了,重復(fù)邏輯少了。每個邏輯都以最atomic的大小放到最應(yīng)該的地方。

     

    進而,維護的代價少多了。如某天“教師角色”的驗證邏輯發(fā)生了變化,只要改動Or_Teacher一個地方即可。

                     MARCO ZHANG 2006年2月18日23:49:56

    posted @ 2006-02-18 23:53 marco 閱讀(1248) | 評論 (5)編輯 收藏

        說起這個工廠模式,一時還真不知道該如何說起。反正這是我的開發(fā)日志,不提理論的東西,理論的東西那里都有,我只想把具體實踐記錄下來給師弟師妹們作個參考,積累點經(jīng)驗。所有這些文字都是集中講一點――“在什么情況下為什么用某種模式好,為什么好,為什么在那種情況下能想起來用?”。

           研究生院項目中“明顯”使用了“工廠方法模式”。其實在遇到具體問題的時候,即使我們不知道有這個模式存在,我們也肯定會造一個類似的東西出來。但是,肯定沒有書上論述的那么好,那么全面。我想這就是看書的好處吧。

     

    工廠方法出現(xiàn)的必然(我的理解,一個很狹隘并幼稚理的人的理解)

     

           剛開始使用這個東西的時候,只是感覺是單純的一種模式,用于創(chuàng)建需要的對象。

           但是隨著使用和思考的深入,越發(fā)發(fā)現(xiàn)它給我的啟示不只在于單純的對象創(chuàng)建,而是告訴我應(yīng)該怎么理解“產(chǎn)品”,怎么得到“產(chǎn)品”,怎么消費“產(chǎn)品”,以至于以后怎么設(shè)計“產(chǎn)品”。

           下面這個線索是我對它出現(xiàn)必然性的理解:

    1.         “針對接口編程”

      這是OO世界中經(jīng)典的規(guī)范,不管你主動還是被動,你天天都在用這個東西。

        接口是共性的表示,在對象的世界中,共性和個性的辯證關(guān)系是最重要的關(guān)系。在萬千的對象中,通過它們之間的共性和個性,可以形成最基本對象層級架構(gòu)。

    假設(shè)我們的討論域中有以下一些對象:“學(xué)生”、“大學(xué)生”、“小學(xué)生”、“中學(xué)生”;我們不用細想,學(xué)過一天OO的人都可以為這些耳熟能詳?shù)膶ο髠儯ㄟ^個性和共性的關(guān)系得出下面的結(jié)構(gòu)圖。

    o_1.JPG

     

           把這些對象之間的關(guān)系定義成這樣是順理成章的。

           下一步肯定是讓客戶端“使用”這個接口啦。也就是如下圖:

     

    o_2.JPG

    2.         接口和具體類的矛盾

    勿庸置疑,我們只希望Client跟接口Student打交道,讓它根本就不知道Student有哪些子類,絕對不希望直接跟它們打交道。

    但這里出現(xiàn)的困難是,接口都是“假”的,都是由具體類upcast的。

    如果Client要使用接口StudentClient中必須會出現(xiàn)下面的代碼:

    Student marco = new Small_Student();

           只要一出現(xiàn)這個代碼,就說明Client不只跟Student打交道了,它知道了Small_Student類,這違反了我們預(yù)先的想法。

    3.         找“人”幫我去創(chuàng)建“接口對象”

        從上圖體現(xiàn)出來的結(jié)構(gòu)看,Client只想跟Student打交道的目的是實現(xiàn)不了的了。

           最簡單的方法就是找另外的“幫手”去幫我生成這個“接口對象”。這個幫手它知道“接口對象”的具體類型,但是它為客戶端提供的卻一定是“接口類型”。這就符合我們的要求了。如圖:

    o_3.JPG

        這樣,Client就可以既用到了“Student接口對象”,又不用因為“只有具體類才能創(chuàng)建對象”的規(guī)則,而必須對其子類結(jié)構(gòu)有完全的了解。它成功的解決了2中的矛盾。

           而“負責創(chuàng)建具體類對象的任務(wù)”全部都落在了這個“幫手”身上,這個“幫手”(Student_Factory)就是工廠模式中的工廠類,更具體的說,它就是“簡單工廠模式”中的“簡單工廠類”。

           我覺得,即使一點都不知道工廠模式,一旦我遇到了2里說的矛盾,我也會用這樣的方法處理。

    4.         這個“幫手”不符合“開-閉原則”

    這個幫手的確不錯了,而且一躍成為系統(tǒng)中最重要的對象了,所有“創(chuàng)建具體類的邏輯”都放進去了,也就是因為重要,萬一掛了不就慘了。

    再者,它不符合“開-閉”原則,我不能在不修改這個幫手的情況下添加任何一個產(chǎn)品。在這個例子中就是,如果那天我有病非要加一個“幼兒園學(xué)生”進來,那您就必須修改這個“幫手”的代碼了,這個“幫手”現(xiàn)在就變成Version2(如下圖)了,這個二版的幫手,可以在“代碼”層實現(xiàn)對一版(還沒有添加幼兒園學(xué)生之前)的通用,但這種保證在“開-閉”原則看來,還是不夠的,不保險的,它要的是在類的結(jié)構(gòu)上的保證。聲明一下,這是我很感性的理解,不正確的可能性很高。

    o_4.JPG

    5.         讓“幫手”也多態(tài)

    這里可以嘗試讓“幫手”也多態(tài)一下,這樣“每種學(xué)生類創(chuàng)建的任務(wù)”都被分派到了多態(tài)出來的類中去了。這時,再有新的學(xué)生類型加進來,添加一個對應(yīng)的幫手就可以了。這樣雖然類多了一些,但是符合“開-閉”原則,書上稱之為“工廠方法模式”。如圖:

    o_5.JPG

        假如Client現(xiàn)在要使用一個小學(xué)生,代碼如下:

    1        //創(chuàng)建一個小學(xué)生工廠類這個幫手
    2        Student_Factory factory = new Small_Student_Factory();
    3        //求助這個幫手,幫我創(chuàng)建一個

    4        Student primaryStudent = factory.produce();
    5        //這時就可以使用這個小學(xué)生了,讓它玩玩游戲吧

    6        primaryStudent.playGames();
    7

        在這里還是要強調(diào)兩點:

    n         雖然實際上Client的確使用了一個小學(xué)生對象(Small_Student),但這里Client也認為它就是Student對象,這里一定要用Student接口來隱藏它的具體類。

    n         另外,卻不需要用Student_Factory這個接口來隱藏它的具體類,因為,Client實際就是通過“選擇它的具體類”這招兒來“選擇創(chuàng)建的學(xué)生類型”。這里的Student_Factory更多的功能不是“隱藏”具體類,而是“規(guī)范”具體類。

     

    項目實踐

     

           扯淡到此,該聯(lián)系我們的項目啦。

           由于是做研究生院的項目,其中巨大的需求就是要讓同學(xué)能在網(wǎng)上提交各種申請單,申請退學(xué)的,申請轉(zhuǎn)專業(yè)的,申請復(fù)學(xué)的,申請保留學(xué)籍的,除了申請女朋友的外,應(yīng)有盡有。       對于這些單子,用最最基本OO思維,根據(jù)共性個性分析方式,抽象出一個申請單接口,和若干的具體類。

           當然,除了概念上感性上吻合以外,在項目中它們也要“真”的有巨大的共性才行,如都要提交,修改,刪除,審核,打印等功能。

           靠,既然都這樣了,肯定就用一接口規(guī)定它了。

           想到這里,加上有了上面的思考,不用說了,就用工廠方法模式啦。

     

    o_6.JPG

        圖中Ent_Shift就是申請單接口。等于前面分析的Student接口。還可以看到有很多具體類,前面例子中是代表各種學(xué)生,這里就是代表各種申請單。

           當然,這里還有很多工廠,也就是前面一直叫的“幫手”。

    o_7.JPG

           Bean_Shift就是工廠接口,相當于前面的Student_Factory接口。還有很多的具體類就是生產(chǎn)各種申請單的工廠類。

           下面就是使用“申請單工廠方法模式”的一段客戶端代碼:

     1        //聲明申請單接口
     2        Ent_Shift es = null;
     3        //聲明申請單工廠接口

     4        Bean_Shift bs = null;
     5        //根據(jù)傳入的申請單類型數(shù)字身成對應(yīng)的申請單工廠

     6        switch (shiftTypeID) {
     7            case
     Bean_Shift.SHIFT_CHANGETEAAP :
     8                bs = new
     Bean_Shift_CHANGETEAAP();
     9                break
    ;
    10            case
     Bean_Shift.SHIFT_RESERVEAP :
    11                bs = new
     Bean_Shift_RESERVEAP();
    12                break
    ;
    13            case
     Bean_Shift.SHIFT_RENEWAP :
    14                bs = new
     Bean_Shift_RENEWAP();
    15                break
    ;
    16             //省略了別的申請單……………………

    17            default :
    18                this.forwardErr(req, resp, "所選擇的異動類別不存在"
    );
    19        }

    20
    21        try 
    {
    22            //調(diào)用工廠接口的生產(chǎn)方法

    23            es = bs.getBlankShift(stuID);
    24
                
    25        }
     catch (Exception e) {
    26            this
    .forwardErr(req, resp, DB_ERROR);
    27        }

    28        //調(diào)用單子的提交方法
    29        es.submit(req);
    30
            
    31        //發(fā)給頁面去顯示

    32        es.fowrardWithSessionObject(
    33
                    req,
    34
                    resp,
    35
                    Ent_Shift.nameInSessionAndRequest);
    36

     

    升華

     

           個人比較同意《Design Pattern Explained》中作者講的,要用好很多的模式,其中都有一個思路,就是用接口或抽象類來隱藏子類的不同。

           我每當看到這時,老是會被一種思路困擾――“new只能new具體類啊,這咋能隱藏呢,這隱藏還有什么用呢?”。

           作者仿佛也曾經(jīng)有過我的這個傻B苦惱,它的解決方法就是:根本在使用對象的時候,特別是設(shè)計階段,盡量不去想對象是在那里被new的。他認為:反正有了工廠模式后,你總有辦法把他們new出來的。

           所以,我用了工廠模式后更發(fā)的啟發(fā)是:以后設(shè)計的時候不要想一個Client怎么創(chuàng)建一個對象,盡管放心大膽的先繼續(xù)想,直接使用就好了。反正最后我還有工廠模式呢。

           因此俺的副標題才是“Ignore how they were created”,呵呵。
                                        MARCO ZHANG 2006年2月16日3:52:10

    posted @ 2006-02-16 03:53 marco 閱讀(1398) | 評論 (9)編輯 收藏

    主站蜘蛛池模板: 曰批全过程免费视频在线观看无码 | 一个人免费高清在线观看| 国产福利在线观看免费第一福利| 性色av无码免费一区二区三区| 日本免费观看网站| 伊人久久精品亚洲午夜| 亚洲91av视频| 亚洲日韩亚洲另类激情文学| 无码的免费不卡毛片视频| 久久久久久久99精品免费观看| 国产91免费在线观看| 免费观看日本污污ww网站一区| 亚洲婷婷五月综合狠狠爱| 亚洲videosbestsex日本| 色天使亚洲综合一区二区| 男女一边桶一边摸一边脱视频免费| 老汉精品免费AV在线播放| 亚洲国产精品自在自线观看| 尤物视频在线免费观看| 最刺激黄a大片免费网站| 国产美女被遭强高潮免费网站| 亚洲人成色77777| 亚洲一区二区三区播放在线 | 亚洲AV无码成人精品区日韩 | 成人久久免费网站| 日韩免费a级毛片无码a∨| 亚洲精品A在线观看| 亚洲色偷偷偷网站色偷一区| 特级毛片A级毛片100免费播放| 免费无码VA一区二区三区| 国产中文字幕免费| 亚洲综合国产精品| 国产精品亚洲精品日韩动图| 日本一卡精品视频免费| 免费国产成人午夜私人影视 | 中文字幕免费观看| 亚洲日本在线观看视频| 亚洲xxxx18| 精品亚洲永久免费精品| 午夜高清免费在线观看| 亚洲AV天天做在线观看|