18丨理論四接口隔離原則有哪三種應用原則中的接口該如何理解_W_第1頁
18丨理論四接口隔離原則有哪三種應用原則中的接口該如何理解_W_第2頁
18丨理論四接口隔離原則有哪三種應用原則中的接口該如何理解_W_第3頁
18丨理論四接口隔離原則有哪三種應用原則中的接口該如何理解_W_第4頁
18丨理論四接口隔離原則有哪三種應用原則中的接口該如何理解_W_第5頁
已閱讀5頁,還剩13頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

1、18 | 理論四:接口隔離原則有哪三種應用?原則中的“接口”該如何理解?2019-12-13 王爭設計模式之美進入課程講述:馮永吉時長 13:56 大小 12.77M上幾節(jié)課中,我們學習了 SOLID 原則中的單一職責原則、開閉原則和里式替換原則,今天我們學習第四個原則,接口隔離原則。它對應 SOLID 中的英文字母“I”。對于這個原則, 最關鍵就是理解其中“接口”的含義。那針對“接口”,不同的理解方式,對應在原則上也有不同的解讀方式。除此之外,接口隔離原則跟我們之前講到的單一職責原則還有點兒類似,所以今天我也會具體講一下它們之間的區(qū)別和聯系。話不多說,現在就讓我們正式開始今天的學習吧!如何理

2、解“接口隔離原則”?加微信:642945106 發(fā)送“贈送”領取贈送精品課程發(fā)數字“2”獲取眾籌列表下載APP接口隔離原則的英文翻譯是“ Interface Segregation Principle”,縮寫為 ISP。Robert Martin 在 SOLID 原則中是這樣定義它的:“Clients should not be forced to depend upon interfaces that they do not use?!敝弊g成中文的話就是:客戶端不應該強迫依賴它不需要的接口。其中的“客戶端”,可以理解為接口的調用者或者使用者。實際上,“接口”這個名詞可以用在很多場合中。生活中

3、我們可以用它來指插座接口等。在軟件開發(fā)中,我們既可以把它看作一組抽象的約定,也可以具體指系統(tǒng)與系統(tǒng)之間的 API接口,還可以特指面向對象編程語言中的接口等。前面我提到,理解接口隔離原則的關鍵,就是理解其中的“接口”二字。在這條原則中,我們可以把“接口”理解為下面三種東西:一組 API 接口集合單個 API 接口或函數OOP 中的接口概念接下來,我就按照這三種理解方式來詳細講一下,在不同的場景下,這條原則具體是如何解讀和應用的。把“接口”理解為一組 API 接口集合我們還是結合一個例子來講解。微服務用戶系統(tǒng)提供了一組跟用戶相關的 API 給其他系統(tǒng)使用,比如:注冊、登錄、獲取用戶信息等。具體代碼

4、如下所示:復制代碼12345678910public interface UserService boolean register(String cellphone, String password); boolean login(String cellphone, String password); UserInfo getUserInfoById(long id);UserInfo getUserInfoByCellphone(String cellphone);public class UserServiceImpl implements UserService /.現在,我們的管理系統(tǒng)

5、要實現刪除用戶的功能,希望用戶系統(tǒng)提供一個刪除用戶的接口。這個時候我們該如何來做呢?你可能會說,這不是很簡單嗎,我只需要在 UserService中新添加一個 deleteUserByCellphone() 或 deleteUserById() 接口就可以了。這個方法可以解決問題,但是也隱藏了一些安全隱患。刪除用戶是一個非常慎重的操作,我們只希望通過管理系統(tǒng)來執(zhí)行,所以這個接口只限于給管理系統(tǒng)使用。如果我們把它放到 UserService 中,那所有使用到 UserService的系統(tǒng),都可以調用這個接口。不加限制地被其他業(yè)務系統(tǒng)調用,就有可能導致誤刪用戶。當然,最好的解決方案是從架構設計的層

6、面,通過接口鑒權的方式來限制接口的調用。不過,如果暫時沒有鑒權框架來支持,我們還可以從代碼設計的層面,盡量避免接口被誤用。我們參照接口隔離原則,調用者不應該強迫依賴它不需要的接口,將刪除接口單獨放到另外一個接口 RestrictedUserService 中,然后將 RestrictedUserService 只打包提供給理系統(tǒng)來使用。具體的代碼實現如下所示:管復制代碼123456789101112131415public interface UserService boolean register(String cellphone, String password); boolean log

7、in(String cellphone, String password); UserInfo getUserInfoById(long id);UserInfo getUserInfoByCellphone(String cellphone);public interface RestrictedUserService boolean deleteUserByCellphone(String cellphone); boolean deleteUserById(long id);public class UserServiceImpl implements UserService, Rest

8、rictedUserService / . 省略實現代碼.在剛剛的這個例子中,我們把接口隔離原則中的接口,理解為一組接口集合,它可以是某個微服務的接口,也可以是某個類庫的接口等等。在設計微服務或者類庫接口的時候,如果部 分接口只被部分調用者使用,那我們就需要將這部分接口隔離出來,單獨給對應的調用者使 用,而不是強迫其他調用者也依賴這部分不會被用到的接口。把“接口”理解為單個 API 接口或函數現在我們再換一種理解方式,把接口理解為單個接口或函數(以下為了方便講解,我都簡稱為“函數”)。那接口隔離原則就可以理解為:函數的設計要功能單一,不要將多個不同的功能邏輯在一個函數中實現。接下來,我們還是通

9、過一個例子來解釋一下。在上面的代碼中,count() 函數的功能不夠單一,包含很多不同的統(tǒng)計功能,比如,求最大值、最小值、平均值等等。按照接口隔離原則,我們應該把 count() 函數拆成幾個更小粒度的函數,每個函數負責一個獨立的統(tǒng)計功能。拆分之后的代碼如下所示:不過,你可能會說,在某種意義上講,count() 函數也不能算是職責不夠單一,畢竟它做的事情只跟統(tǒng)計相關。我們在講單一職責原則的時候,也提到過類似的問題。實際上,判定功能是否單一,除了很強的主觀性,還需要結合具體的場景。如果在項目中,對每個統(tǒng)計需求,Statistics 定義的那幾個統(tǒng)計信息都有涉及,那 count() 函數的設計就是

10、合理的。相反,如果每個統(tǒng)計需求只涉及 Statistics 羅列的統(tǒng)計信息中一部分,比如,有的只需要用到 max、min、average 這三類統(tǒng)計信息,有的只需要用到average、sum。而 count() 函數每次都會把所有的統(tǒng)計信息計算一遍,就會做很多無用復制代碼1 public Long max(Collection dataSet) /. 2 public Long min(Collection dataSet) /. 3 public Long average(Colletion dataSet) /. 4 / . 省略其他統(tǒng)計函數.復制代碼1 public class Stat

11、istics 2 private Long max;3 private Long min;4 private Long average;5 private Long sum;6 private Long percentile99;7 private Long percentile999;8 /. 省略 constructor/getter/setter 等方法.9 1011 public Statistics count(Collection dataSet) 12 Statistics statistics = new Statistics();13/. 省略計算邏輯.14return st

12、atistics;15 功,勢必影響代碼的性能,特別是在需要統(tǒng)計的數據量很大的時候。所以,在這個應用場景下,count()函數的設計就有點不合理了,我們應該按照第二種設計思路,將其拆分成粒度更細的多個統(tǒng)計函數。不過,你應該已經發(fā)現,接口隔離原則跟單一職責原則有點類似,不過稍微還是有點區(qū)別。單一職責原則針對的是模塊、類、接口的設計。而接口隔離原則相對于單一職責原則,一方面它更側重于接口的設計,另一方面它的思考的角度不同。它提供了一種判斷接口是否職責單一的標準:通過調用者如何使用接口來間接地判定。如果調用者只使用部分接口或接口的部分功能,那接口的設計就不夠職責單一。把“接口”理解為 OOP 中的接

13、口概念除了剛講過的兩種理解方式,我們還可以把“接口”理解為 OOP 中的接口概念,比如Java 中的 interface。我還是通過一個例子來給你解釋。假設我們的項目中用到了三個外部系統(tǒng):Redis、MySQL、Kafka。每個系統(tǒng)都對應一系列配置信息,比如地址、端口、訪問超時時間等。為了在內存中存儲這些配置信息,供項目中的其他模塊來使用,我們分別設計實現了三個 Configuration 類:RedisConfig、MysqlConfig、KafkaConfig。具體的代碼實現如下所示。注意,這里我只給出了RedisConfig 的代碼實現,另外兩個都是類似的,我這里就不貼了。復制代碼123

14、45678910111213141516171819public class RedisConfig ConfigSource configSource; / 配置中心(比如 zookeeper)String address; int timeout;int maxTotal;private private privateprivate/ 省略其他配置: maxWaitMillis,maxIdle,minIdle.public RedisConfig(ConfigSource configSource) this.configSource = configSource;public Strin

15、g getAddress() return this.address;/. 省略其他 get()、init() 方法.public void update() / 從 configSource 加載配置到 address/timeout/maxTotal.202122 public class KafkaConfig /. 省略. public class MysqlConfig /. 省略. 23現在,我們有一個新的功能需求,希望支持 Redis 和 Kafka 配置信息的熱更新。所謂“熱更新(hot update)”就是,如果在配置中心中更改了配置信息,我們希望在不用重啟系統(tǒng)的情況下,能將

16、最新的配置信息加載到內存中(也就是 RedisConfig、KafkaConfig 類中)。但是,因為某些原因,我們并不希望對 MySQL 的配置信息進行熱更新。為了實現這樣一個功能需求,我們設計實現了一個 ScheduledUpdater 類,以固定時間頻率(periodInSeconds)來調用 RedisConfig、KafkaConfig 的 update() 方法更新配置信息。具體的代碼實現如下所示:復制代碼123456789101112131415161718192021222324252627282930publicvoidinterface Updater update();p

17、ublicclass RedisConfig implemets Updater /. 省略其他屬性和方法.Overridepublic void update() /. public class KafkaConfig implements Updater /. 省略其他屬性和方法. Overridepublic void update() /. public class MysqlConfig /. 省略其他屬性和方法.public class ScheduledUpdater private private privateprivatefinal ScheduledExecutorSer

18、vice executor long initialDelayInSeconds;long periodInSeconds;Updater updater;= Executors.newSingleThreapublic ScheduleUpdater(Updater updater, long initialDelayInSeconds, long p this.updater = updater;this.initialDelayInSeconds = initialDelayInSeconds; this.periodInSeconds = periodInSeconds;3132333

19、43536373839404142434445464748495051525354public void run() executor.scheduleAtFixedRate(new Runnable() Overridepublic void run() updater.update();, this.initialDelayInSeconds, this.periodInSeconds, TimeUnit.SECONDS)public class Application ConfigSource configSource = newZookeeperConfigSource(/* 省略參數

20、 */);public publicpublicstatic staticstaticfinal finalfinalRedisConfig KafkaConfigMySqlConfigredisConfig kafkaConfigmysqlConfig=new newnewRedisConfig(configSource); KakfaConfig(configSource);MysqlConfig(configSource);publicstaticvoid main(String args) ScheduledUpdater redisConfigUpdater = newredisCo

21、nfigUpdater.run();ScheduledUpdater(redisConfig, 30ScheduledUpdater kafkaConfigUpdater = newredisConfigUpdater.run();ScheduledUpdater(kafkaConfig, 60剛剛的熱更新的需求我們已經搞定了?,F在,我們又有了一個新的監(jiān)控功能需求。通過命令行來查看 Zookeeper 中的配置信息是比較麻煩的。所以,我們希望能有一種更加方便的配置信息查看方式。我們可以在項目中開發(fā)一個內嵌的SimpleHttpServer,輸出項目的配置信息到一個固定的HTTP 地址,比如:h

22、ttp:/:2389/config 。我們只需要在瀏覽器中輸入這個地址,就可以顯示出系統(tǒng)的配置信息。不過,出于某些原因,我們只想暴露 MySQL 和 Redis的配置信息,不想暴露 Kafka 的配置信息。為了實現這樣一個功能,我們還需要對上面的代碼做進一步改造。改造之后的代碼如下所示:復制代碼12345publicvoidinterface Updater update();publicinterface Viewer 678910111213141516171819202122232425262728293031323334353637383940414243444546

23、4748495051525354555657String outputInPlainText();Map output();public class RedisConfig implemets Updater, Viewer/. 省略其他屬性和方法. Overridepublic void update() /. Overridepublic String outputInPlainText() /. Overridepublic Map output() /.Updater public class KafkaConfig implements/. 省略其他屬性和方法. Overridepu

24、blic void update() /. public class MysqlConfig implements/. 省略其他屬性和方法. Overridepublic String outputInPlainText()OverrideViewer /. public Map output() /.public class SimpleHttpServer private privateprivateString host; int port;MapString, List viewers = new HashMap();public SimpleHttpServer(String hos

25、t, int port) /.public void addViewers(String urlDirectory, Viewer viewer) if (!viewers.containsKey(urlDirectory) viewers.put(urlDirectory, new ArrayList();this.viewers.get(urlDirectory).add(viewer);public void run() /. public class Application ConfigSource configSource = newZookeeperConfigSource();p

26、ublic publicpublicstatic staticstaticfinal finalfinalRedisConfig KafkaConfigMySqlConfigredisConfig kafkaConfigmysqlConfig=new newnewRedisConfig(configSource KakfaConfig(configSourceMySqlConfig(configSourcepublicstaticvoid main(String args) 5859606162636465666768697071ScheduledUpdater redisConfigUpda

27、ter =new ScheduledUpdater(redisConfig, 300, 300); redisConfigUpdater.run();ScheduledUpdater kafkaConfigUpdater =new ScheduledUpdater(kafkaConfig, 60, 60); redisConfigUpdater.run();SimpleHttpServer simpleHttpServer = new SimpleHttpServer(“”, simpleHttpServer.addViewer(/config, redisConfig);

28、simpleHttpServer.addViewer(/config, mysqlConfig);simpleHttpServer.run();至此,熱更新和監(jiān)控的需求我們就都實現了。我們來回顧一下這個例子的設計思想。我們設計了兩個功能非常單一的接口:Updater 和 Viewer。ScheduledUpdater 只依賴Updater 這個跟熱更新相關的接口,不需要被強迫去依賴不需要的 Viewer 接口,滿足接口隔離原則。同理,SimpleHttpServer 只依賴跟查看信息相關的 Viewer 接口,不依賴不需要的 Updater 接口,也滿足接口隔離原則。你可能會說,如果我們不遵守

29、接口隔離原則,不設計 Updater 和 Viewer 兩個小接口,而是設計一個大而全的 Config 接口,讓 RedisConfig、KafkaConfig、MysqlConfig 都實現這個 Config 接口,并且將原來傳遞給 ScheduledUpdater 的 Updater 和傳遞給SimpleHttpServer 的 Viewer,都替換為 Config,那會有什么問題呢?我們先來看一下, 按照這個思路來實現的代碼是什么樣的。復制代碼1234567891011121314public interface Config void update();String outputInP

30、lainText(); Map output();public class RedisConfig implements Config /. 需要實現 Config 的三個接口 update/outputIn./outputpublic class KafkaConfig implements Config /. 需要實現 Config 的三個接口 update/outputIn./output15161718192021222324252627282930313233343536373839404142434445public class MysqlConfig implements Con

31、fig /. 需要實現 Config 的三個接口 update/outputIn./outputpublic class ScheduledUpdater /. 省略其他屬性和方法. private Config config;public ScheduleUpdater(Config config, long initialDelayInSeconds, this.config = config;/./.longperiopublic class SimpleHttpServer private privateprivateString host; int port;MapString, L

32、ist viewers = new HashMap();public SimpleHttpServer(String host, int port) /.public void addViewer(String urlDirectory, Config config) if (!viewers.containsKey(urlDirectory) viewers.put(urlDirectory, new ArrayList();viewers.get(urlDirectory).add(config);public void run() /. 這樣的設計思路也是能工作的,但是對比前后兩個設計思

33、路,在同樣的代碼量、實現復雜度、同等可讀性的情況下,第一種設計思路顯然要比第二種好很多。為什么這么說呢?主要有兩點原因。首先,第一種設計思路更加靈活、易擴展、易復用。因為 Updater、Viewer 職責更加單一,單一就意味了通用、復用性好。比如,我們現在又有一個新的需求,開發(fā)一個 Metrics 性能統(tǒng)計模塊,并且希望將 Metrics 也通過 SimpleHttpServer 顯示在網頁上,以方便查看。這個時候,盡管 Metrics 跟 RedisConfig 等沒有任何關系,但我們仍然可以讓Metrics 類實現非常通用的 Viewer 接口,復用 SimpleHttpServer 的

34、代碼實現。具體的代碼如下所示:復制代碼1234567891011121314151617181920publicpublicclassclassApiMetrics implements Viewer /.DbMetrics implements Viewer /.publicclassApplication ConfigSource configSource = newZookeeperConfigSource();public public public publicpublicstatic static static staticstaticfinal final final final

35、finalRedisConfig KafkaConfigMySqlConfigredisConfig kafkaConfigmySqlConfig=new newnewRedisConfig(configSource KakfaConfig(configSourceMySqlConfig(configSourceApiMetrics apiMetrics =new ApiMetrics();DbMetrics dbMetrics = new DbMetrics();publicstaticvoid main(String args) SimpleHttpServer simpleHttpSer

36、ver = new SimpleHttpServer(“”, simpleHttpServer.addViewer(/config, redisConfig); simpleHttpServer.addViewer(/config, mySqlConfig); simpleHttpServer.addViewer(/metrics, apiMetrics); simpleHttpServer.addViewer(/metrics, dbMetrics);simpleHttpServer.run();其次,第二種設計思路在代碼實現上做了一些無用功。因為 Config 接口中包含

37、兩類不相關的 接 口 , 一 類 是 update(), 一 類 是 output() 和 outputInPlainText() 。 理 論 上 , KafkaConfig 只需要實現 update() 接口,并不需要實現 output() 相關的接口。同理, MysqlConfig 只需要實現 output() 相關接口,并需要實現 update() 接口。但第二種設計思路要求 RedisConfig、KafkaConfig、MySqlConfig 必須同時實現 Config 的所有接口函數(update、output、outputInPlainText)。除此之外,如果我們要往 Conf

38、ig 中繼續(xù)添加一個新的接口,那所有的實現類都要改動。相反,如果我們的接口粒度比較小,那涉及改動的類就比較少。重點回顧今天的內容到此就講完了。我們一塊來總結回顧一下,你需要掌握的重點內容。1. 如何理解“接口隔離原則”?理解“接口隔離原則”的重點是理解其中的“接口”二字。這里有三種不同的理解。如果把“接口”理解為一組接口集合,可以是某個微服務的接口,也可以是某個類庫的接口等。如果部分接口只被部分調用者使用,我們就需要將這部分接口隔離出來,單獨給這部分調用者使用,而不強迫其他調用者也依賴這部分不會被用到的接口。如果把“接口”理解為單個 API 接口或函數,部分調用者只需要函數中的部分功能,那我們

39、就需要把函數拆分成粒度更細的多個函數,讓調用者只依賴它需要的那個細粒度函數。如果把“接口”理解為 OOP 中的接口,也可以理解為面向對象編程語言中的接口語法。那接口的設計要盡量單一,不要讓接口的實現類和調用者,依賴不需要的接口函數。2. 接口隔離原則與單一職責原則的區(qū)別單一職責原則針對的是模塊、類、接口的設計。接口隔離原則相對于單一職責原則,一方面更側重于接口的設計,另一方面它的思考角度也是不同的。接口隔離原則提供了一種判斷接 口的職責是否單一的標準:通過調用者如何使用接口來間接地判定。如果調用者只使用部分 接口或接口的部分功能,那接口的設計就不夠職責單一。課堂討論今天課堂討論的話題是這樣的:

40、java.util.concurrent 并發(fā)包提供了 AtomicInteger 這樣一個原子類,其中有一個函數getAndIncrement() 是這樣定義的:給整數增加一,并且返回未増之前的值。我的問題是,這個函數的設計是否符合單一職責原則和接口隔離原則?為什么?歡迎在留言區(qū)寫下你的答案,和同學一起交流和分享。如果有收獲,也歡迎你把這篇文章分享給你的朋友。復制代碼1 /*2 * Atomically increments by one the current value.3 * return the previous value4*/5 public final int getAndIn

41、crement() /. 版權歸極客邦科技所有,未經許可不得傳播售賣。 頁面已增加防盜追蹤,如有侵權極客邦將依法追究其法律責任。上一篇17 | 理論三:里式替換(LSP)跟多態(tài)有何區(qū)別?哪些代碼違背了LSP?下一篇19 | 理論五:控制反轉、依賴反轉、依賴注入,這三者有何區(qū)別和聯系?精選留言 (61)辣么大2019-12-13Java.util.concurrent.atomic包下提供了機器底層級別實現的多線程環(huán)境下原子操作,相比自己實現類似的功能更加高效。AtomicInteger提供了intValue() 獲取當前值incrementAndGet() 相當于+i展開寫留言431NoAsk

42、2019-12-13單一職責原則針對的是模塊、類、接口的設計。getAndIncrease()雖然集合了獲取和增加兩個功能,但是它作為對atomicInteger的值的常用方法,提供對其值的常規(guī)操作,是滿足單一原則的。從單一原則的下面這個解釋考慮,是不滿足接口隔離原則的?!叭绻{用者只使用部分展開111時光流逝,而我們在干.2019-12-13老師可以每次課對上一次課的思考題做下解答嗎展開作者回復: 集中答疑一下吧 課都提前錄好了10李小四2019-12-13設計模式_18純理論分析,這么設計是不符合“接口隔離”原則的,畢竟,get是一個操作,increment 是另一個操作。結合具體場景,A

43、tomic類的設計目的是保證操作的原子性,專門看了一下AtomicIntege展開26北島明月2019-12-13理由是這個方法完成的邏輯就是一個功能:新增和返回舊值。只不過是兩步操作罷了。如果你想獲取,就用get方法,自增就用increment 方法。都有提供哇。展開5小晏子2019-12-13思考題:先看是否符合單一職責原則,這個函數的功能是加1然后返回之前的值,做了兩件事,是不符合單一職責原則的!但是卻符合接口隔離原則,從調用者的角度來看的話,因為這個類是Atomic類,需要的所有操作都是原子的,所以為了滿足調用者需要原子性的完成加一返回的操作,提供一個展開4墨雨2019-12-13單一

44、職責是針對于模塊、類在具體的需求業(yè)務場景下是否符合只做一件事情的原則。而接口隔離原則就細化到了接口方面,我是這樣理解的,如果接口中的某些方法對于需要繼承實現它的類來說是多余的,那么這個接口的設計就不符合接口隔離原則,可以考慮再拆分細化。對于課后思考題,他只對該數做了相關操作符合單一職責原則。但從接口、函數來看它展開13黃林晴2019-12-13思考題:個人感覺,不符合單一職責,也不符合接口隔離,因為函數做了兩件事,不應該把獲取當前值和值加1放在一起,因為1.用戶可能需要-1 *1等其他運算操作再返回原始值,這樣就要n個方法每個方法中都有返回原始值的操作。展開2Chen2019-12-13getAndIncrement()符合接口隔離原則,這是不是一個大而全的函數,而是一個細粒度的函數,跟

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論