整合營(yíng)銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          輸入一個(gè)網(wǎng)址之后為什么會(huì)返回很多文件,而不是一個(gè)HT

          輸入一個(gè)網(wǎng)址之后為什么會(huì)返回很多文件,而不是一個(gè)HTML文件?

          實(shí)輸入U(xiǎn)RL網(wǎng)址最開始就是得到一個(gè)HTML文件。但是這個(gè)HTML文件里面有很多下圖這種類似的代碼。這其實(shí)就是引用javascript代碼,是通過引用文件的方式使用。

          這個(gè)時(shí)候就會(huì)進(jìn)一步加載這個(gè)HTML提到的js文件。所以便會(huì)收到多個(gè)文件。

          這些js、CSS等文件得作用就是對(duì)HTML內(nèi)容的處理。如果不加載全這些文件,網(wǎng)頁(yè)內(nèi)容將會(huì)不完整。

          if...else 是所有高級(jí)編程語(yǔ)言都有的必備功能。但現(xiàn)實(shí)中的代碼往往存在著過多的 if...else。雖然 if...else 是必須的,但濫用 if...else 會(huì)對(duì)代碼的可讀性、可維護(hù)性造成很大傷害,進(jìn)而危害到整個(gè)軟件系統(tǒng)。現(xiàn)在軟件開發(fā)領(lǐng)域出現(xiàn)了很多新技術(shù)、新概念,但 if...else 這種基本的程序形式并沒有發(fā)生太大變化。使用好 if...else 不僅對(duì)于現(xiàn)在,而且對(duì)于將來,都是十分有意義的。今天我們就來看看如何“干掉”代碼中的 if...else,還代碼以清爽。

          問題一:if...else 過多

          問題表現(xiàn)

          if...else 過多的代碼可以抽象為下面這段代碼。其中只列出5個(gè)邏輯分支,但實(shí)際工作中,能見到一個(gè)方法包含10個(gè)、20個(gè)甚至更多的邏輯分支的情況。另外,if...else 過多通常會(huì)伴隨著另兩個(gè)問題:邏輯表達(dá)式復(fù)雜和 if...else 嵌套過深。對(duì)于后兩個(gè)問題,本文將在下面兩節(jié)介紹。本節(jié)先來討論 if...else 過多的情況。

          如果想學(xué)習(xí)Java工程化、高性能及分布式、深入淺出。微服務(wù)、Spring,MyBatis,Netty源碼分析的朋友可以加我的Java高級(jí)交流:854630135,群里有阿里大牛直播講解技術(shù),以及Java大型互聯(lián)網(wǎng)技術(shù)的視頻免費(fèi)分享給大家。

           1 if (condition1) {
           2 
           3 } else if (condition2) {
           4 
           5 } else if (condition3) {
           6 
           7 } else if (condition4) {
           8 
           9 } else {
          10 
          11 }
          

          通常,if...else 過多的方法,通常可讀性和可擴(kuò)展性都不好。從軟件設(shè)計(jì)角度講,代碼中存在過多的 if...else 往往意味著這段代碼違反了違反單一職責(zé)原則和開閉原則。因?yàn)樵趯?shí)際的項(xiàng)目中,需求往往是不斷變化的,新需求也層出不窮。所以,軟件系統(tǒng)的擴(kuò)展性是非常重要的。而解決 if...else 過多問題的最大意義,往往就在于提高代碼的可擴(kuò)展性。

          如何解決

          接下來我們來看如何解決 if...else 過多的問題。下面我列出了一些解決方法。

          1. 表驅(qū)動(dòng)
          2. 職責(zé)鏈模式
          3. 注解驅(qū)動(dòng)
          4. 事件驅(qū)動(dòng)
          5. 有限狀態(tài)機(jī)
          6. Optional
          7. Assert
          8. 多態(tài)

          方法一:表驅(qū)動(dòng)

          介紹

          對(duì)于邏輯表達(dá)模式固定的 if...else 代碼,可以通過某種映射關(guān)系,將邏輯表達(dá)式用表格的方式表示;再使用表格查找的方式,找到某個(gè)輸入所對(duì)應(yīng)的處理函數(shù),使用這個(gè)處理函數(shù)進(jìn)行運(yùn)算。

          適用場(chǎng)景

          邏輯表達(dá)模式固定的 if...else

          實(shí)現(xiàn)與示例

          1 if (param.equals(value1)) {
          2 doAction1(someParams);
          3 } else if (param.equals(value2)) {
          4 doAction2(someParams);
          5 } else if (param.equals(value3)) {
          6 doAction3(someParams);
          7 }
          8 // ...
          

          可重構(gòu)為

          1 Map<?, Function<?> action> actionMappings=new HashMap<>(); // 這里泛型 ? 是為方便演示,實(shí)際可替換為你需要的類型
          2 
          3 // When init
          4 actionMappings.put(value1, (someParams) -> { doAction1(someParams)});
          5 actionMappings.put(value2, (someParams) -> { doAction2(someParams)});
          6 actionMappings.put(value3, (someParams) -> { doAction3(someParams)});
          7 
          8 // 省略 null 判斷
          9 actionMappings.get(param).apply(someParams);
          

          上面的示例使用了 Java 8 的 Lambda 和 Functional Interface,這里不做講解。

          表的映射關(guān)系,可以采用集中的方式,也可以采用分散的方式,即每個(gè)處理類自行注冊(cè)。也可以通過配置文件的方式表達(dá)。總之,形式有很多。

          還有一些問題,其中的條件表達(dá)式并不像上例中的那樣簡(jiǎn)單,但稍加變換,同樣可以應(yīng)用表驅(qū)動(dòng)。下面借用《編程珠璣》中的一個(gè)稅金計(jì)算的例子:

           1 if income <=2200
           2 tax=0
           3 else if income <=2700
           4 tax=0.14 * (income - 2200)
           5 else if income <=3200
           6 tax=70 + 0.15 * (income - 2700)
           7 else if income <=3700
           8 tax=145 + 0.16 * (income - 3200)
           9 ......
          10 else
          11 tax=53090 + 0.7 * (income - 102200)
          

          對(duì)于上面的代碼,其實(shí)只需將稅金的計(jì)算公式提取出來,將每一檔的標(biāo)準(zhǔn)提取到一個(gè)表格,在加上一個(gè)循環(huán)即可。具體重構(gòu)之后的代碼不給出,大家自己思考。

          方法二:職責(zé)鏈模式

          介紹

          當(dāng) if...else 中的條件表達(dá)式靈活多變,無法將條件中的數(shù)據(jù)抽象為表格并用統(tǒng)一的方式進(jìn)行判斷時(shí),這時(shí)應(yīng)將對(duì)條件的判斷權(quán)交給每個(gè)功能組件。并用鏈的形式將這些組件串聯(lián)起來,形成完整的功能。

          適用場(chǎng)景

          條件表達(dá)式靈活多變,沒有統(tǒng)一的形式。

          實(shí)現(xiàn)與示例

          職責(zé)鏈的模式在開源框架的 Filter、Interceptor 功能的實(shí)現(xiàn)中可以見到很多。下面看一下通用的使用模式:

          重構(gòu)前:

          1 public void handle(request) {
          2 if (handlerA.canHandle(request)) {
          3 handlerA.handleRequest(request);
          4 } else if (handlerB.canHandle(request)) {
          5 handlerB.handleRequest(request);
          6 } else if (handlerC.canHandle(request)) {
          7 handlerC.handleRequest(request);
          8 }
          9 }
          

          重構(gòu)后:

          如果想學(xué)習(xí)Java工程化、高性能及分布式、深入淺出。微服務(wù)、Spring,MyBatis,Netty源碼分析的朋友可以加我的Java高級(jí)交流:854630135,群里有阿里大牛直播講解技術(shù),以及Java大型互聯(lián)網(wǎng)技術(shù)的視頻免費(fèi)分享給大家。

           1 public void handle(request) {
           2 handlerA.handleRequest(request);
           3 }
           4 
           5 public abstract class Handler {
           6 protected Handler next;
           7 public abstract void handleRequest(Request request);
           8 public void setNext(Handler next) { this.next=next; }
           9 }
          10 
          11 public class HandlerA extends Handler {
          12 public void handleRequest(Request request) {
          13 if (canHandle(request)) doHandle(request);
          14 else if (next !=null) next.handleRequest(request);
          15 }
          16 }
          

          當(dāng)然,示例中的重構(gòu)前的代碼為了表達(dá)清楚,做了一些類和方法的抽取重構(gòu)。現(xiàn)實(shí)中,更多的是平鋪式的代碼實(shí)現(xiàn)。

          注:職責(zé)鏈的控制模式

          職責(zé)鏈模式在具體實(shí)現(xiàn)過程中,會(huì)有一些不同的形式。從鏈的調(diào)用控制角度看,可分為外部控制和內(nèi)部控制兩種。

          外部控制不靈活,但是減少了實(shí)現(xiàn)難度。職責(zé)鏈上某一環(huán)上的具體實(shí)現(xiàn)不用考慮對(duì)下一環(huán)的調(diào)用,因?yàn)橥獠拷y(tǒng)一控制了。但是一般的外部控制也不能實(shí)現(xiàn)嵌套調(diào)用。如果有嵌套調(diào)用,并且希望由外部控制職責(zé)鏈的調(diào)用,實(shí)現(xiàn)起來會(huì)稍微復(fù)雜。

          內(nèi)部控制就比較靈活,可以由具體的實(shí)現(xiàn)來決定是否需要調(diào)用鏈上的下一環(huán)。但如果調(diào)用控制模式是固定的,那這樣的實(shí)現(xiàn)對(duì)于使用者來說是不便的。

          設(shè)計(jì)模式在具體使用中會(huì)有很多變種,大家需要靈活掌握

          方法三:注解驅(qū)動(dòng)

          介紹

          通過 Java 注解(或其它語(yǔ)言的類似機(jī)制)定義執(zhí)行某個(gè)方法的條件。在程序執(zhí)行時(shí),通過對(duì)比入?yún)⑴c注解中定義的條件是否匹配,再?zèng)Q定是否調(diào)用此方法。具體實(shí)現(xiàn)時(shí),可以采用表驅(qū)動(dòng)或職責(zé)鏈的方式實(shí)現(xiàn)。

          適用場(chǎng)景

          適合條件分支很多多,對(duì)程序擴(kuò)展性和易用性均有較高要求的場(chǎng)景。通常是某個(gè)系統(tǒng)中經(jīng)常遇到新需求的核心功能。

          實(shí)現(xiàn)與示例

          很多框架中都能看到這種模式的使用,比如常見的 Spring MVC。因?yàn)檫@些框架很常用,demo 隨處可見,所以這里不再上具體的演示代碼了。

          這個(gè)模式的重點(diǎn)在于實(shí)現(xiàn)。現(xiàn)有的框架都是用于實(shí)現(xiàn)某一特定領(lǐng)域的功能,例如 MVC。故業(yè)務(wù)系統(tǒng)如采用此模式需自行實(shí)現(xiàn)相關(guān)核心功能。主要會(huì)涉及反射、職責(zé)鏈等技術(shù)。具體的實(shí)現(xiàn)這里就不做演示了。

          方法四:事件驅(qū)動(dòng)

          介紹

          通過關(guān)聯(lián)不同的事件類型和對(duì)應(yīng)的處理機(jī)制,來實(shí)現(xiàn)復(fù)雜的邏輯,同時(shí)達(dá)到解耦的目的。

          適用場(chǎng)景

          從理論角度講,事件驅(qū)動(dòng)可以看做是表驅(qū)動(dòng)的一種,但從實(shí)踐角度講,事件驅(qū)動(dòng)和前面提到的表驅(qū)動(dòng)有多處不同。具體來說:

          1. 表驅(qū)動(dòng)通常是一對(duì)一的關(guān)系;事件驅(qū)動(dòng)通常是一對(duì)多;
          2. 表驅(qū)動(dòng)中,觸發(fā)和執(zhí)行通常是強(qiáng)依賴;事件驅(qū)動(dòng)中,觸發(fā)和執(zhí)行是弱依賴

          正是上述兩者不同,導(dǎo)致了兩者適用場(chǎng)景的不同。具體來說,事件驅(qū)動(dòng)可用于如訂單支付完成觸發(fā)庫(kù)存、物流、積分等功能。

          實(shí)現(xiàn)與示例

          實(shí)現(xiàn)方式上,單機(jī)的實(shí)踐驅(qū)動(dòng)可以使用 Guava、Spring 等框架實(shí)現(xiàn)。分布式的則一般通過各種消息隊(duì)列方式實(shí)現(xiàn)。但是因?yàn)檫@里主要討論的是消除 if...else,所以主要是面向單機(jī)問題域。因?yàn)樯婕熬唧w技術(shù),所以此模式代碼不做演示。

          方法五:有限狀態(tài)機(jī)

          介紹

          有限狀態(tài)機(jī)通常被稱為狀態(tài)機(jī)(無限狀態(tài)機(jī)這個(gè)概念可以忽略)。先引用維基百科上的定義:

          有限狀態(tài)機(jī)(英語(yǔ):finite-state machine,縮寫:FSM),簡(jiǎn)稱狀態(tài)機(jī),是表示有限個(gè)狀態(tài)以及在這些狀態(tài)之間的轉(zhuǎn)移和動(dòng)作等行為的數(shù)學(xué)模型。

          其實(shí),狀態(tài)機(jī)也可以看做是表驅(qū)動(dòng)的一種,其實(shí)就是當(dāng)前狀態(tài)和事件兩者組合與處理函數(shù)的一種對(duì)應(yīng)關(guān)系。當(dāng)然,處理成功之后還會(huì)有一個(gè)狀態(tài)轉(zhuǎn)移處理。

          適用場(chǎng)景

          雖然現(xiàn)在互聯(lián)網(wǎng)后端服務(wù)都在強(qiáng)調(diào)無狀態(tài),但這并不意味著不能使用狀態(tài)機(jī)這種設(shè)計(jì)。其實(shí),在很多場(chǎng)景中,如協(xié)議棧、訂單處理等功能中,狀態(tài)機(jī)有這其天然的優(yōu)勢(shì)。因?yàn)檫@些場(chǎng)景中天然存在著狀態(tài)和狀態(tài)的流轉(zhuǎn)。

          實(shí)現(xiàn)與示例

          實(shí)現(xiàn)狀態(tài)機(jī)設(shè)計(jì)首先需要有相應(yīng)的框架,這個(gè)框架需要實(shí)現(xiàn)至少一種狀態(tài)機(jī)定義功能,以及對(duì)于的調(diào)用路由功能。狀態(tài)機(jī)定義可以使用 DSL 或者注解的方式。原理不復(fù)雜,掌握了注解、反射等功能的同學(xué)應(yīng)該可以很容易實(shí)現(xiàn)。

          方法六:Optional

          介紹

          Java 代碼中的一部分 if...else 是由非空檢查導(dǎo)致的。因此,降低這部分帶來的 if...else 也就能降低整體的 if...else 的個(gè)數(shù)。

          Java 從 8 開始引入了 Optional 類,用于表示可能為空的對(duì)象。這個(gè)類提供了很多方法,用于相關(guān)的操作,可以用于消除 if...else。開源框架 Guava 和 Scala 語(yǔ)言也提供了類似的功能。

          使用場(chǎng)景

          有較多用于非空判斷的 if...else。

          實(shí)現(xiàn)與示例

          傳統(tǒng)寫法:

          1 String str="Hello World!";
          2 if (str !=null) {
          3 System.out.println(str);
          4 } else {
          5 System.out.println("Null");
          6 }
          

          使用 Optional 之后:

          1 Optional<String> strOptional=Optional.of("Hello World!");
          2 strOptional.ifPresentOrElse(System.out::println, () -> System.out.println("Null"));
          

          Optional 還有很多方法,這里不一一介紹了。但請(qǐng)注意,不要使用 get() 和 isPresent() 方法,否則和傳統(tǒng)的 if...else 無異。

          擴(kuò)展:Kotlin Null Safety

          Kotlin 帶有一個(gè)被稱為 Null Safety 的特性:

          bob?.department?.head?.name
          

          對(duì)于一個(gè)鏈?zhǔn)秸{(diào)用,在 Kotlin 語(yǔ)言中可以通過 ?. 避免空指針異常。如果某一環(huán)為 null,那整個(gè)鏈?zhǔn)奖磉_(dá)式的值便為 null。

          方法七:Assert 模式

          介紹

          上一個(gè)方法適用于解決非空檢查場(chǎng)景所導(dǎo)致的 if...else,類似的場(chǎng)景還有各種參數(shù)驗(yàn)證,比如還有字符串不為空等等。很多框架類庫(kù),例如 Spring、Apache Commons 都提供了工具里,用于實(shí)現(xiàn)這種通用的功能。這樣大家就不必自行編寫 if...else 了。

          • Apache Commons Lang 中的 Validate 類:https://commons.apache.org/proper/commons-lang/javadocs/api-3.1/org/apache/commons/lang3/Validate.html
          • Spring 的 Assert 類:https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/Assert.html

          使用場(chǎng)景

          通常用于各種參數(shù)校驗(yàn)

          擴(kuò)展:Bean Validation

          類似上一個(gè)方法,介紹 Assert 模式順便介紹一個(gè)有類似作用的技術(shù) —— Bean Validation。Bean Validation 是 Java EE 規(guī)范中的一個(gè)。Bean Validation 通過在 Java Bean 上用注解的方式定義驗(yàn)證標(biāo)準(zhǔn),然后通過框架統(tǒng)一進(jìn)行驗(yàn)證。也可以起到了減少 if...else 的作用。

          方法八:多態(tài)

          介紹

          使用面向?qū)ο蟮亩鄳B(tài),也可以起到消除 if...else 的作用。

          使用場(chǎng)景

          鏈接中給出的示例比較簡(jiǎn)單,無法體現(xiàn)適合使用多態(tài)消除 if...else 的具體場(chǎng)景。一般來說,當(dāng)一個(gè)類中的多個(gè)方法都有類似于示例中的 if...else 判斷,且條件相同,那就可以考慮使用多態(tài)的方式消除 if...else。

          同時(shí),使用多態(tài)也不是徹底消除 if...else。而是將 if...else 合并轉(zhuǎn)移到了對(duì)象的創(chuàng)建階段。在創(chuàng)建階段的 if..,我們可以使用前面介紹的方法處理。

          小結(jié)

          上面這節(jié)介紹了 if...else 過多所帶來的問題,以及相應(yīng)的解決方法。除了本節(jié)介紹的方法,還有一些其它的方法。比如,在《重構(gòu)與模式》一書中就介紹了“用 Strategy 替換條件邏輯”、“用 State 替換狀態(tài)改變條件語(yǔ)句”和“用 Command 替換條件調(diào)度程序”這三個(gè)方法。其中的“Command 模式”,其思想同本文的“表驅(qū)動(dòng)”方法大體一致。另兩種方法,因?yàn)樵凇吨貥?gòu)與模式》一書中已做詳細(xì)講解,這里就不再重復(fù)。

          何時(shí)使用何種方法,取決于面對(duì)的問題的類型。上面介紹的一些適用場(chǎng)景,只是一些建議,更多的需要開發(fā)人員自己的思考。

          問題二:if...else 嵌套過深

          問題表現(xiàn)

          if...else 多通常并不是最嚴(yán)重的的問題。有的代碼 if...else 不僅個(gè)數(shù)多,而且 if...else 之間嵌套的很深,也很復(fù)雜,導(dǎo)致代碼可讀性很差,自然也就難以維護(hù)。

           1 if (condition1) {
           2 action1();
           3 if (condition2) {
           4 action2();
           5 if (condition3) {
           6 action3();
           7 if (condition4) {
           8 action4();
           9 }
          10 }
          11 }
          12 }
          

          if...else 嵌套過深會(huì)嚴(yán)重地影響代碼的可讀性。當(dāng)然,也會(huì)有上一節(jié)提到的兩個(gè)問題。

          如何解決

          上一節(jié)介紹的方法也可用用來解決本節(jié)的問題,所以對(duì)于上面的方法,此節(jié)不做重復(fù)介紹。這一節(jié)重點(diǎn)一些方法,這些方法并不會(huì)降低 if...else 的個(gè)數(shù),但是會(huì)提高代碼的可讀性:

          1. 抽取方法
          2. 衛(wèi)語(yǔ)句

          方法一:抽取方法

          介紹

          抽取方法是代碼重構(gòu)的一種手段。定義很容易理解,就是將一段代碼抽取出來,放入另一個(gè)單獨(dú)定義的方法。借用 https://refactoring.com/catalog/extractMethod.html 中的定義:

          適用場(chǎng)景

          if...else 嵌套嚴(yán)重的代碼,通常可讀性很差。故在進(jìn)行大型重構(gòu)前,需先進(jìn)行小幅調(diào)整,提高其代碼可讀性。抽取方法便是最常用的一種調(diào)整手段。

          實(shí)現(xiàn)與示例

          重構(gòu)前:

           1 public void add(Object element) {
           2 if (!readOnly) {
           3 int newSize=size + 1;
           4 if (newSize > elements.length) {
           5 Object[] newElements=new Object[elements.length + 10];
           6 for (int i=0; i < size; i++) {
           7 newElements[i]=elements[i];
           8 }
           9 
          10 elements=newElements
          11 }
          12 elements[size++]=element;
          13 }
          14 }
          

          重構(gòu)后:

           1 public void add(Object element) {
           2 if (readOnly) {
           3 return;
           4 }
           5 
           6 if (overCapacity()) {
           7 grow();
           8 }
           9 
          10 addElement(element);
          11 }
          

          方法二:衛(wèi)語(yǔ)句

          介紹

          在代碼重構(gòu)中,有一個(gè)方法被稱為“使用衛(wèi)語(yǔ)句替代嵌套條件語(yǔ)句”https://refactoring.com/catalog/replaceNestedConditionalWithGuardClauses.html。直接看代碼:

           1 double getPayAmount() {
           2 double result;
           3 if (_isDead) result=deadAmount();
           4 else {
           5 if (_isSeparated) result=separatedAmount();
           6 else {
           7 if (_isRetired) result=retiredAmount();
           8 else result=normalPayAmount();
           9 };
          10 }
          11 return result;
          12 }
          

          重構(gòu)之后

          1 double getPayAmount() {
          2 if (_isDead) return deadAmount();
          3 if (_isSeparated) return separatedAmount();
          4 if (_isRetired) return retiredAmount();
          5 return normalPayAmount();
          6 }
          

          使用場(chǎng)景

          當(dāng)看到一個(gè)方法中,某一層代碼塊都被一個(gè) if...else 完整控制時(shí),通常可以采用衛(wèi)語(yǔ)句。

          問題三:if...else 表達(dá)式過于復(fù)雜

          問題表現(xiàn)

          if...else 所導(dǎo)致的第三個(gè)問題來自過于復(fù)雜的條件表達(dá)式。下面給個(gè)簡(jiǎn)單的例子,當(dāng) condition 1、2、3、4 分別為 true、false,請(qǐng)大家排列組合一下下面表達(dá)式的結(jié)果。

          1 if ((condition1 && condition2 ) || ((condition2 || condition3) && condition4)) {
          2 
          3 }
          

          我想沒人愿意干上面的事情。關(guān)鍵是,這一大坨表達(dá)式的含義是什么?關(guān)鍵便在于,當(dāng)不知道表達(dá)式的含義時(shí),沒人愿意推斷它的結(jié)果。

          所以,表達(dá)式復(fù)雜,并不一定是錯(cuò)。但是表達(dá)式難以讓人理解就不好了。

          如何解決

          對(duì)于 if...else 表達(dá)式復(fù)雜的問題,主要用代碼重構(gòu)中的抽取方法、移動(dòng)方法等手段解決。因?yàn)檫@些方法在《代碼重構(gòu)》一書中都有介紹,所以這里不再重復(fù)。

          歡迎工作一到八年的Java工程師朋友們加入Java高級(jí)交流:854630135

          本群提供免費(fèi)的學(xué)習(xí)指導(dǎo) 架構(gòu)資料 以及免費(fèi)的解答

          不懂得問題都可以在本群提出來 之后還會(huì)有直播平臺(tái)和講師直接交流噢

          擊上方 "程序員小樂"關(guān)注, 星標(biāo)或置頂一起成長(zhǎng)

          每天凌晨00點(diǎn)00分, 第一時(shí)間與你相約


          每日英文

          If nothing can be done, then just let nature take its course.

          如果無能為力,那就它順其自然吧。


          每日掏心話

          一切順其自然,專注于過程而少想結(jié)果,安于當(dāng)下,不想過去亦不想未來,把每一個(gè)發(fā)生都當(dāng)作一種修煉。

          來自:艾瑞克·邵 | 責(zé)編:樂樂

          鏈接:cnblogs.com/eric-shao/p/10115577.html

          程序員小樂(ID:study_tech)第 834 次推文 圖片來自百度


          往日回顧:前后端分離模式下的權(quán)限設(shè)計(jì)方案


          正文


          前言


          if...else 是所有高級(jí)編程語(yǔ)言都有的必備功能。但現(xiàn)實(shí)中的代碼往往存在著過多的 if...else。雖然 if...else 是必須的,但濫用 if...else 會(huì)對(duì)代碼的可讀性、可維護(hù)性造成很大傷害,進(jìn)而危害到整個(gè)軟件系統(tǒng)。現(xiàn)在軟件開發(fā)領(lǐng)域出現(xiàn)了很多新技術(shù)、新概念,但 if...else 這種基本的程序形式并沒有發(fā)生太大變化。使用好 if...else 不僅對(duì)于現(xiàn)在,而且對(duì)于將來,都是十分有意義的。今天我們就來看看如何“干掉”代碼中的 if...else,還代碼以清爽。


          問題一:if…else 過多


          問題表現(xiàn)


          if...else 過多的代碼可以抽象為下面這段代碼。其中只列出5個(gè)邏輯分支,但實(shí)際工作中,能見到一個(gè)方法包含10個(gè)、20個(gè)甚至更多的邏輯分支的情況。另外,if...else 過多通常會(huì)伴隨著另兩個(gè)問題:邏輯表達(dá)式復(fù)雜和 if...else 嵌套過深。對(duì)于后兩個(gè)問題,本文將在下面兩節(jié)介紹。本節(jié)先來討論 if...else 過多的情況。

          if (condition1) {

          } else if (condition2) {

          } else if (condition3) {

          } else if (condition4) {

          } else {

          }


          通常,if...else 過多的方法,通常可讀性和可擴(kuò)展性都不好。從軟件設(shè)計(jì)角度講,代碼中存在過多的 if...else 往往意味著這段代碼違反了違反單一職責(zé)原則和開閉原則。因?yàn)樵趯?shí)際的項(xiàng)目中,需求往往是不斷變化的,新需求也層出不窮。所以,軟件系統(tǒng)的擴(kuò)展性是非常重要的。而解決 if...else 過多問題的最大意義,往往就在于提高代碼的可擴(kuò)展性。


          如何解決


          接下來我們來看如何解決 if...else 過多的問題。下面我列出了一些解決方法。


          • 表驅(qū)動(dòng)

          • 職責(zé)鏈模式

          • 注解驅(qū)動(dòng)

          • 事件驅(qū)動(dòng)

          • 有限狀態(tài)機(jī)

          • Optional

          • Assert

          • 多態(tài)


          方法一:表驅(qū)動(dòng)


          介紹


          對(duì)于邏輯表達(dá)模式固定的 if...else 代碼,可以通過某種映射關(guān)系,將邏輯表達(dá)式用表格的方式表示;再使用表格查找的方式,找到某個(gè)輸入所對(duì)應(yīng)的處理函數(shù),使用這個(gè)處理函數(shù)進(jìn)行運(yùn)算。


          適用場(chǎng)景

          邏輯表達(dá)模式固定的 if...else


          實(shí)現(xiàn)與示例


          if (param.equals(value1)) {
          doAction1(someParams);
          } else if (param.equals(value2)) {
          doAction2(someParams);
          } else if (param.equals(value3)) {
          doAction3(someParams);
          }
          // ...


          可重構(gòu)為


          Map<?, Function<?> action> actionMappings=new HashMap<>(); // 這里泛型 ? 是為方便演示,實(shí)際可替換為你需要的類型

          // When init
          actionMappings.put(value1, (someParams) -> { doAction1(someParams)});
          actionMappings.put(value2, (someParams) -> { doAction2(someParams)});
          actionMappings.put(value3, (someParams) -> { doAction3(someParams)});

          // 省略 null 判斷
          actionMappings.get(param).apply(someParams);


          上面的示例使用了 Java 8 的 Lambda 和 Functional Interface,這里不做講解。

          表的映射關(guān)系,可以采用集中的方式,也可以采用分散的方式,即每個(gè)處理類自行注冊(cè)。也可以通過配置文件的方式表達(dá)。總之,形式有很多。


          還有一些問題,其中的條件表達(dá)式并不像上例中的那樣簡(jiǎn)單,但稍加變換,同樣可以應(yīng)用表驅(qū)動(dòng)。下面借用《編程珠璣》中的一個(gè)稅金計(jì)算的例子:


          if income <=2200
          tax=0
          else if income <=2700
          tax=0.14 * (income - 2200)
          else if income <=3200
          tax=70 + 0.15 * (income - 2700)
          else if income <=3700
          tax=145 + 0.16 * (income - 3200)
          ......
          else
          tax=53090 + 0.7 * (income - 102200)


          對(duì)于上面的代碼,其實(shí)只需將稅金的計(jì)算公式提取出來,將每一檔的標(biāo)準(zhǔn)提取到一個(gè)表格,在加上一個(gè)循環(huán)即可。具體重構(gòu)之后的代碼不給出,大家自己思考。


          方法二:職責(zé)鏈模式


          介紹


          當(dāng) if...else 中的條件表達(dá)式靈活多變,無法將條件中的數(shù)據(jù)抽象為表格并用統(tǒng)一的方式進(jìn)行判斷時(shí),這時(shí)應(yīng)將對(duì)條件的判斷權(quán)交給每個(gè)功能組件。并用鏈的形式將這些組件串聯(lián)起來,形成完整的功能。


          適用場(chǎng)景


          條件表達(dá)式靈活多變,沒有統(tǒng)一的形式。


          實(shí)現(xiàn)與示例


          職責(zé)鏈的模式在開源框架的 Filter、Interceptor 功能的實(shí)現(xiàn)中可以見到很多。下面看一下通用的使用模式:


          重構(gòu)前:


          public void handle(request) {
          if (handlerA.canHandle(request)) {
          handlerA.handleRequest(request);
          } else if (handlerB.canHandle(request)) {
          handlerB.handleRequest(request);
          } else if (handlerC.canHandle(request)) {
          handlerC.handleRequest(request);
          }
          }

          重構(gòu)后:


          public void handle(request) {
          handlerA.handleRequest(request);
          }

          public abstract class Handler {
          protected Handler next;
          public abstract void handleRequest(Request request);
          public void setNext(Handler next) { this.next=next; }
          }

          public class HandlerA extends Handler {
          public void handleRequest(Request request) {
          if (canHandle(request)) doHandle(request);
          else if (next !=null) next.handleRequest(request);
          }
          }

          當(dāng)然,示例中的重構(gòu)前的代碼為了表達(dá)清楚,做了一些類和方法的抽取重構(gòu)。現(xiàn)實(shí)中,更多的是平鋪式的代碼實(shí)現(xiàn)。


          注:職責(zé)鏈的控制模式


          職責(zé)鏈模式在具體實(shí)現(xiàn)過程中,會(huì)有一些不同的形式。從鏈的調(diào)用控制角度看,可分為外部控制和內(nèi)部控制兩種。


          外部控制不靈活,但是減少了實(shí)現(xiàn)難度。職責(zé)鏈上某一環(huán)上的具體實(shí)現(xiàn)不用考慮對(duì)下一環(huán)的調(diào)用,因?yàn)橥獠拷y(tǒng)一控制了。但是一般的外部控制也不能實(shí)現(xiàn)嵌套調(diào)用。如果有嵌套調(diào)用,并且希望由外部控制職責(zé)鏈的調(diào)用,實(shí)現(xiàn)起來會(huì)稍微復(fù)雜。具體可以參考 Spring Web Interceptor 機(jī)制的實(shí)現(xiàn)方法。


          內(nèi)部控制就比較靈活,可以由具體的實(shí)現(xiàn)來決定是否需要調(diào)用鏈上的下一環(huán)。但如果調(diào)用控制模式是固定的,那這樣的實(shí)現(xiàn)對(duì)于使用者來說是不便的。


          設(shè)計(jì)模式在具體使用中會(huì)有很多變種,大家需要靈活掌握


          方法三:注解驅(qū)動(dòng)


          介紹


          通過 Java 注解(或其它語(yǔ)言的類似機(jī)制)定義執(zhí)行某個(gè)方法的條件。在程序執(zhí)行時(shí),通過對(duì)比入?yún)⑴c注解中定義的條件是否匹配,再?zèng)Q定是否調(diào)用此方法。具體實(shí)現(xiàn)時(shí),可以采用表驅(qū)動(dòng)或職責(zé)鏈的方式實(shí)現(xiàn)。


          適用場(chǎng)景


          適合條件分支很多多,對(duì)程序擴(kuò)展性和易用性均有較高要求的場(chǎng)景。通常是某個(gè)系統(tǒng)中經(jīng)常遇到新需求的核心功能。


          實(shí)現(xiàn)與示例


          很多框架中都能看到這種模式的使用,比如常見的 Spring MVC。因?yàn)檫@些框架很常用,demo 隨處可見,所以這里不再上具體的演示代碼了。


          這個(gè)模式的重點(diǎn)在于實(shí)現(xiàn)。現(xiàn)有的框架都是用于實(shí)現(xiàn)某一特定領(lǐng)域的功能,例如 MVC。故業(yè)務(wù)系統(tǒng)如采用此模式需自行實(shí)現(xiàn)相關(guān)核心功能。主要會(huì)涉及反射、職責(zé)鏈等技術(shù)。具體的實(shí)現(xiàn)這里就不做演示了。


          方法四:事件驅(qū)動(dòng)


          介紹


          通過關(guān)聯(lián)不同的事件類型和對(duì)應(yīng)的處理機(jī)制,來實(shí)現(xiàn)復(fù)雜的邏輯,同時(shí)達(dá)到解耦的目的。


          適用場(chǎng)景


          從理論角度講,事件驅(qū)動(dòng)可以看做是表驅(qū)動(dòng)的一種,但從實(shí)踐角度講,事件驅(qū)動(dòng)和前面提到的表驅(qū)動(dòng)有多處不同。具體來說:


          • 表驅(qū)動(dòng)通常是一對(duì)一的關(guān)系;事件驅(qū)動(dòng)通常是一對(duì)多;

          • 表驅(qū)動(dòng)中,觸發(fā)和執(zhí)行通常是強(qiáng)依賴;事件驅(qū)動(dòng)中,觸發(fā)和執(zhí)行是弱依賴


          正是上述兩者不同,導(dǎo)致了兩者適用場(chǎng)景的不同。具體來說,事件驅(qū)動(dòng)可用于如訂單支付完成觸發(fā)庫(kù)存、物流、積分等功能。


          實(shí)現(xiàn)與示例


          實(shí)現(xiàn)方式上,單機(jī)的實(shí)踐驅(qū)動(dòng)可以使用 Guava、Spring 等框架實(shí)現(xiàn)。分布式的則一般通過各種消息隊(duì)列方式實(shí)現(xiàn)。但是因?yàn)檫@里主要討論的是消除 if...else,所以主要是面向單機(jī)問題域。因?yàn)樯婕熬唧w技術(shù),所以此模式代碼不做演示。


          方法五:有限狀態(tài)機(jī)


          介紹


          有限狀態(tài)機(jī)通常被稱為狀態(tài)機(jī)(無限狀態(tài)機(jī)這個(gè)概念可以忽略)。先引用維基百科上的定義:

          有限狀態(tài)機(jī)(英語(yǔ):finite-state machine,縮寫:FSM),簡(jiǎn)稱狀態(tài)機(jī),是表示有限個(gè)狀態(tài)以及在這些狀態(tài)之間的轉(zhuǎn)移和動(dòng)作等行為的數(shù)學(xué)模型。

          其實(shí),狀態(tài)機(jī)也可以看做是表驅(qū)動(dòng)的一種,其實(shí)就是當(dāng)前狀態(tài)和事件兩者組合與處理函數(shù)的一種對(duì)應(yīng)關(guān)系。當(dāng)然,處理成功之后還會(huì)有一個(gè)狀態(tài)轉(zhuǎn)移處理。


          適用場(chǎng)景


          雖然現(xiàn)在互聯(lián)網(wǎng)后端服務(wù)都在強(qiáng)調(diào)無狀態(tài),但這并不意味著不能使用狀態(tài)機(jī)這種設(shè)計(jì)。其實(shí),在很多場(chǎng)景中,如協(xié)議棧、訂單處理等功能中,狀態(tài)機(jī)有這其天然的優(yōu)勢(shì)。因?yàn)檫@些場(chǎng)景中天然存在著狀態(tài)和狀態(tài)的流轉(zhuǎn)。


          實(shí)現(xiàn)與示例


          實(shí)現(xiàn)狀態(tài)機(jī)設(shè)計(jì)首先需要有相應(yīng)的框架,這個(gè)框架需要實(shí)現(xiàn)至少一種狀態(tài)機(jī)定義功能,以及對(duì)于的調(diào)用路由功能。狀態(tài)機(jī)定義可以使用 DSL 或者注解的方式。原理不復(fù)雜,掌握了注解、反射等功能的同學(xué)應(yīng)該可以很容易實(shí)現(xiàn)。


          參考技術(shù):


          • Apache Mina State Machine
            Apache Mina 框架,雖然在 IO 框架領(lǐng)域不及 Netty,但它卻提供了一個(gè)狀態(tài)機(jī)的功能。https://mina.apache.org/mina-project/userguide/ch14-state-machine/ch14-state-machine.html。有自己實(shí)現(xiàn)狀態(tài)機(jī)功能的同學(xué)可以參考其源碼。

          • Spring State Machine
            Spring 子項(xiàng)目眾多,其中有個(gè)不顯山不露水的狀態(tài)機(jī)框架 —— Spring State Machine https://projects.spring.io/spring-statemachine/。可以通過 DSL 和注解兩種方式定義。


          上述框架只是起到一個(gè)參考的作用,如果涉及到具體項(xiàng)目,需要根據(jù)業(yè)務(wù)特點(diǎn)自行實(shí)現(xiàn)狀態(tài)機(jī)的核心功能。


          方法六:Optional

          介紹


          Java 代碼中的一部分 if...else 是由非空檢查導(dǎo)致的。因此,降低這部分帶來的 if...else 也就能降低整體的 if...else 的個(gè)數(shù)。


          Java 從 8 開始引入了 Optional 類,用于表示可能為空的對(duì)象。這個(gè)類提供了很多方法,用于相關(guān)的操作,可以用于消除 if...else。開源框架 Guava 和 Scala 語(yǔ)言也提供了類似的功能。


          使用場(chǎng)景


          有較多用于非空判斷的 if...else。


          實(shí)現(xiàn)與示例


          傳統(tǒng)寫法:


          String str="Hello World!";
          if (str !=null) {
          System.out.println(str);
          } else {
          System.out.println("Null");
          }


          使用 Optional 之后:


          1 Optional<String> strOptional=Optional.of("Hello World!");
          2 strOptional.ifPresentOrElse(System.out::println, () -> System.out.println("Null"));

          Optional 還有很多方法,這里不一一介紹了。但請(qǐng)注意,不要使用 get() 和 isPresent() 方法,否則和傳統(tǒng)的 if...else 無異。


          擴(kuò)展:Kotlin Null Safety


          Kotlin 帶有一個(gè)被稱為 Null Safety 的特性:

          bob?.department?.head?.name


          對(duì)于一個(gè)鏈?zhǔn)秸{(diào)用,在 Kotlin 語(yǔ)言中可以通過 ?. 避免空指針異常。如果某一環(huán)為 null,那整個(gè)鏈?zhǔn)奖磉_(dá)式的值便為 null。


          方法七:Assert 模式


          介紹


          上一個(gè)方法適用于解決非空檢查場(chǎng)景所導(dǎo)致的 if...else,類似的場(chǎng)景還有各種參數(shù)驗(yàn)證,比如還有字符串不為空等等。很多框架類庫(kù),例如 Spring、Apache Commons 都提供了工具里,用于實(shí)現(xiàn)這種通用的功能。這樣大家就不必自行編寫 if...else 了。


          • Apache Commons Lang 中的 Validate 類:https://commons.apache.org/proper/commons-lang/javadocs/api-3.1/org/apache/commons/lang3/Validate.html

          • Spring 的 Assert 類:https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/Assert.html


          使用場(chǎng)景


          通常用于各種參數(shù)校驗(yàn)


          擴(kuò)展:Bean Validation


          類似上一個(gè)方法,介紹 Assert 模式順便介紹一個(gè)有類似作用的技術(shù) —— Bean Validation。Bean Validation 是 Java EE 規(guī)范中的一個(gè)。Bean Validation 通過在 Java Bean 上用注解的方式定義驗(yàn)證標(biāo)準(zhǔn),然后通過框架統(tǒng)一進(jìn)行驗(yàn)證。也可以起到了減少 if...else 的作用。


          方法八:多態(tài)

          介紹


          使用面向?qū)ο蟮亩鄳B(tài),也可以起到消除 if...else 的作用。在代碼重構(gòu)這本書中,對(duì)此也有介紹:

          https://refactoring.com/catalog/replaceConditionalWithPolymorphism.html


          使用場(chǎng)景


          鏈接中給出的示例比較簡(jiǎn)單,無法體現(xiàn)適合使用多態(tài)消除 if...else 的具體場(chǎng)景。一般來說,當(dāng)一個(gè)類中的多個(gè)方法都有類似于示例中的 if...else 判斷,且條件相同,那就可以考慮使用多態(tài)的方式消除 if...else。


          同時(shí),使用多態(tài)也不是徹底消除 if...else。而是將 if...else 合并轉(zhuǎn)移到了對(duì)象的創(chuàng)建階段。在創(chuàng)建階段的 if..,我們可以使用前面介紹的方法處理。


          小結(jié)


          上面這節(jié)介紹了 if...else 過多所帶來的問題,以及相應(yīng)的解決方法。除了本節(jié)介紹的方法,還有一些其它的方法。比如,在《重構(gòu)與模式》一書中就介紹了“用 Strategy 替換條件邏輯”、“用 State 替換狀態(tài)改變條件語(yǔ)句”和“用 Command 替換條件調(diào)度程序”這三個(gè)方法。其中的“Command 模式”,其思想同本文的“表驅(qū)動(dòng)”方法大體一致。另兩種方法,因?yàn)樵凇吨貥?gòu)與模式》一書中已做詳細(xì)講解,這里就不再重復(fù)。


          何時(shí)使用何種方法,取決于面對(duì)的問題的類型。上面介紹的一些適用場(chǎng)景,只是一些建議,更多的需要開發(fā)人員自己的思考。


          問題二:if…else 嵌套過深


          問題表現(xiàn)


          if...else 多通常并不是最嚴(yán)重的的問題。有的代碼 if...else 不僅個(gè)數(shù)多,而且 if...else 之間嵌套的很深,也很復(fù)雜,導(dǎo)致代碼可讀性很差,自然也就難以維護(hù)。


          if (condition1) {
          action1();
          if (condition2) {
          action2();
          if (condition3) {
          action3();
          if (condition4) {
          action4();
          }
          }
          }
          }


          if...else 嵌套過深會(huì)嚴(yán)重地影響代碼的可讀性。當(dāng)然,也會(huì)有上一節(jié)提到的兩個(gè)問題。


          如何解決


          上一節(jié)介紹的方法也可用用來解決本節(jié)的問題,所以對(duì)于上面的方法,此節(jié)不做重復(fù)介紹。這一節(jié)重點(diǎn)一些方法,這些方法并不會(huì)降低 if...else 的個(gè)數(shù),但是會(huì)提高代碼的可讀性:


          • 抽取方法

          • 衛(wèi)語(yǔ)句


          方法一:抽取方法


          介紹


          抽取方法是代碼重構(gòu)的一種手段。定義很容易理解,就是將一段代碼抽取出來,放入另一個(gè)單獨(dú)定義的方法。借

          用 https://refactoring.com/catalog/extractMethod.html 中的定義:


          適用場(chǎng)景

          if...else 嵌套嚴(yán)重的代碼,通常可讀性很差。故在進(jìn)行大型重構(gòu)前,需先進(jìn)行小幅調(diào)整,提高其代碼可讀性。抽取方法便是最常用的一種調(diào)整手段。


          實(shí)現(xiàn)與示例


          重構(gòu)前:


          public void add(Object element) {
          if (!readOnly) {
          int newSize=size + 1;
          if (newSize > elements.length) {
          Object[] newElements=new Object[elements.length + 10];
          for (int i=0; i < size; i++) {
          newElements[i]=elements[i];
          }

          elements=newElements
          }
          elements[size++]=element;
          }
          }


          重構(gòu)后:


          public void add(Object element) {
          if (readOnly) {
          return;
          }

          if (overCapacity()) {
          grow();
          }

          addElement(element);
          }


          方法二:衛(wèi)語(yǔ)句


          介紹


          在代碼重構(gòu)中,有一個(gè)方法被稱為“使用衛(wèi)語(yǔ)句替代嵌套條件語(yǔ)句”https://refactoring.com/catalog/replaceNestedConditionalWithGuardClauses.html。直接看代碼:


          double getPayAmount() {
          double result;
          if (_isDead) result=deadAmount();
          else {
          if (_isSeparated) result=separatedAmount();
          else {
          if (_isRetired) result=retiredAmount();
          else result=normalPayAmount();
          };
          }
          return result;
          }


          重構(gòu)之后


          double getPayAmount() {
          if (_isDead) return deadAmount();
          if (_isSeparated) return separatedAmount();
          if (_isRetired) return retiredAmount();
          return normalPayAmount();
          }


          使用場(chǎng)景


          當(dāng)看到一個(gè)方法中,某一層代碼塊都被一個(gè) if...else 完整控制時(shí),通常可以采用衛(wèi)語(yǔ)句。


          問題三:if…else 表達(dá)式過于復(fù)雜


          問題表現(xiàn)


          if...else 所導(dǎo)致的第三個(gè)問題來自過于復(fù)雜的條件表達(dá)式。下面給個(gè)簡(jiǎn)單的例子,當(dāng) condition 1、2、3、4 分別為 true、false,請(qǐng)大家排列組合一下下面表達(dá)式的結(jié)果。


          1 if ((condition1 && condition2 ) || ((condition2 || condition3) && condition4)) {
          2
          3 }

          我想沒人愿意干上面的事情。關(guān)鍵是,這一大坨表達(dá)式的含義是什么?關(guān)鍵便在于,當(dāng)不知道表達(dá)式的含義時(shí),沒人愿意推斷它的結(jié)果。


          所以,表達(dá)式復(fù)雜,并不一定是錯(cuò)。但是表達(dá)式難以讓人理解就不好了。


          如何解決


          對(duì)于 if...else 表達(dá)式復(fù)雜的問題,主要用代碼重構(gòu)中的抽取方法、移動(dòng)方法等手段解決。因?yàn)檫@些方法在《代碼重構(gòu)》一書中都有介紹,所以這里不再重復(fù)。


          總結(jié)


          本文一個(gè)介紹了10種(算上擴(kuò)展有12種)用于消除、簡(jiǎn)化 if...else 的方法。還有一些方法,如通過策略模式、狀態(tài)模式等手段消除 if...else 在《重構(gòu)與模式》一書中也有介紹。


          正如前言所說,if...else 是代碼中的重要組成部分,但是過度、不必要地使用 if...else,會(huì)對(duì)代碼的可讀性、可擴(kuò)展性造成負(fù)面影響,進(jìn)而影響到整個(gè)軟件系統(tǒng)。


          “干掉”if...else 的能力高低反映的是程序員對(duì)軟件重構(gòu)、設(shè)計(jì)模式、面向?qū)ο笤O(shè)計(jì)、架構(gòu)模式、數(shù)據(jù)結(jié)構(gòu)等多方面技術(shù)的綜合運(yùn)用能力,反映的是程序員的內(nèi)功。要合理使用 if...else,不能沒有設(shè)計(jì),也不能過度設(shè)計(jì)。這些對(duì)技術(shù)的綜合、合理地運(yùn)用都需要程序員在工作中不斷的摸索總結(jié)。


          歡迎在留言區(qū)留下你的觀點(diǎn),一起討論提高。如果今天的文章讓你有新的啟發(fā),學(xué)習(xí)能力的提升上有新的認(rèn)識(shí),歡迎轉(zhuǎn)發(fā)分享給更多人。


          猜你還想看


          阿里、騰訊、百度、華為、京東最新面試題匯集

          從上帝視角看Java如何運(yùn)行

          一次項(xiàng)目代碼重構(gòu):使用Spring容器干掉條件判斷

          手把手教你用1行代碼實(shí)現(xiàn)人臉識(shí)別

          關(guān)注訂閱號(hào)「程序員小樂」,收看更多精彩內(nèi)容
          嘿,你在看嗎?


          主站蜘蛛池模板: 国产情侣一区二区| 亚洲一区二区三区在线播放| 色一情一乱一伦一区二区三欧美| 中文字幕一区二区日产乱码| 无码免费一区二区三区免费播放| 天堂不卡一区二区视频在线观看| 国模无码一区二区三区| 成人无码AV一区二区| 国产精品成人一区二区| 精品视频在线观看一区二区| 天海翼一区二区三区高清视频| 久久久久人妻一区二区三区vr | 中文字幕久久亚洲一区 | 无码人妻精品一区二区三区99仓本| 日本韩国黄色一区二区三区 | 亚洲一区二区三区免费在线观看| 日韩一区二区久久久久久| 免费无码一区二区三区蜜桃| 国产在线观看一区二区三区精品| 一区二区三区在线播放视频| 视频一区视频二区日韩专区| 国产中文字幕一区| 国产伦精品一区二区三区在线观看| 韩国一区二区三区| 精品女同一区二区三区免费播放 | 日韩社区一区二区三区| 成人在线视频一区| 国产午夜精品一区二区| 一本AV高清一区二区三区| 亚洲国产精品一区二区久久hs | 日韩精品一区二区三区不卡| 无码AⅤ精品一区二区三区| 精品国产香蕉伊思人在线在线亚洲一区二区 | 亚洲变态另类一区二区三区| 精品人妻少妇一区二区三区不卡 | 日本美女一区二区三区 | 人妻夜夜爽天天爽一区| 日韩精品一区二区三区视频 | 日韩美一区二区三区| 亚洲爽爽一区二区三区| 日韩免费视频一区二区|