【设计模式】适配器模式:如何巧妙地过滤游戏中的敏感词
真正的大師,永遠都懷著一顆學徒的心 -------無極劍圣 · 易
適配器是什么?
適配器是一個接口轉換器,它可以是一個獨立的硬件接口設備,允許硬件或電子接口與其它硬件或電子接口相連,也可以是信息接口。比如:電源適配器、三角架基座轉接部件、USB與串口的轉接設備等(百度百科)。
很抽象?我們來看幾張圖片,你就會明白適配器是什么了。
投影轉接頭,由于有些電腦的接口與投影的接口不一致,所以就會導致電腦使用不了投影,所以投影轉接頭就是將電腦和投影兩者做了適配,讓電腦可能正常使用與他不匹配的投影。
耳機轉接頭,用過蘋果手機的應該都知道,3.5mm的耳機接口是無法接入蘋果手機的,但是我又想用它,怎么辦呢?這個時候耳機轉接頭出現了,他負責將3.5mm的耳機接口適配成蘋果支持的耳機接口,這樣,我們就可以拿著3.5mm的耳機去連接蘋果手機。
通過這兩個例子大家對適配器是什么應該有了一個大致的了解了吧,而我們今天要講的內容是設計模式中常見的一種:適配器模式。
什么是適配器模式?
適配器模式(英語:adapter pattern)有時候也稱包裝樣式或者包裝(英語:wrapper)。將一個類的接口轉接成用戶所期待的。一個適配使得因接口不兼容而不能在一起工作的類能在一起工作,做法是將類自己的接口包裹在一個已存在的類中。
專業解釋總是那么的不近人情,讓人琢磨不透,我來舉個簡單的例子吧,假如你開發的系統現在正在升級,由 1.0 -> 2.0 許多接口都發生了翻天覆地的變化,由于兼容性的問題,并不能直接將老接口刪除,但是老接口的實現確實嚴重拖慢了程序的效率,老接口和新接口的參數完全不一樣,這個時候怎么辦呢?有沒有一個類可以幫忙做個中轉,將老接口的數據轉換成新接口的數據,然后調用新接口,這樣效率是不是就快很多了呢?沒錯,適配器模式就是干這個的,他就是將兩個原本不兼容的方法或者接口通過轉接變成可以相互通信的工具。
看起來是不是很簡單呢?適配器模式的原理確實很簡單,那我們應該怎么去實現它呢?適配器模式的實現方式主要有兩種:繼承、組合,什么時候用繼承,什么時候用組合呢?簡單點來說就是類適配器使用繼承關系實現,對象適配器使用組合關系實現,很抽象?沒關系,我們一起用代碼實現這兩種方式,看完代碼之后你就能理解這兩種實現方式了。
廢話不多說,先看第一種:類適配器。
package com.liuxing.adapter.adaptee;/*** @ProjectName: hxjm* @Package: com.hxjm.fish* @ClassName: FishGwService* @Author: 流星007* @Description: 需要轉接的接口定義 類適配器 基于繼承* csdn:https://blog.csdn.net/qq_33220089* 今日頭條:https://www.toutiao.com/c/user/5372182357/#mid=1637641735275523* @Date: 2021/4/19 20:38* @Version: 1.0*/public interface ITarget {void doSomthing1();void doSomthing2();void doSomthing3(); } package com.liuxing.adapter.adaptee;/*** @ClassName MyAdaptee* @Description 與Itarget定義的接口不兼容的類* csdn:https://blog.csdn.net/qq_33220089* 今日頭條:https://www.toutiao.com/c/user/5372182357/#mid=1637641735275523* @Author ymy* @Date 2021/4/27 11:14*/ public class MyAdaptee {public void todo1(){System.out.println("這是 todo1");}public void todo2(){System.out.println("這是 todo2");}public void doSomthing3(){System.out.println("這是 todo3");} } package com.liuxing.adapter.adaptee;/*** @ClassName MyAdaptor* @Description 適配器,將原本不兼容Itarget的接口轉化為兼容Itarget的接口* csdn:https://blog.csdn.net/qq_33220089* 今日頭條:https://www.toutiao.com/c/user/5372182357/#mid=1637641735275523* @Author ymy* @Date 2021/4/27 11:20*/ public class MyAdaptor extends MyAdaptee implements ITarget {@Overridepublic void doSomthing1() {super.todo1();}@Overridepublic void doSomthing2() {System.out.println("我是被重新實現的dosomthing2");}}第二種:對象適配器
package com.liuxing.adapter;/*** @ClassName MyObjectAdaptor* @Description 適配器,對象適配器* @Author: 流星007* @Date 2021/4/27 14:08*/public class MyObjectAdaptor implements ITarget {private MyAdaptee myAdaptee;public MyObjectAdaptor(MyAdaptee myAdaptee) {this.myAdaptee = myAdaptee;}@Overridepublic void doSomthing1() {//交給 MyAdaptee 實現myAdaptee.todo1();}@Overridepublic void doSomthing2() {System.out.println("我是被重新實現的dosomthing2");}@Overridepublic void doSomthing3() {myAdaptee.doSomthing3();}} package com.liuxing.adapter;import com.sun.corba.se.spi.oa.ObjectAdapter;/*** @ClassName Test* @Description 測試* @Author: 流星007* @Date 2021/4/27 13:56*/ public class Test {public static void main(String[] args) {//類適配器ITarget classAdaptor = new MyClassAdaptor();classAdaptor.doSomthing1();classAdaptor.doSomthing2();classAdaptor.doSomthing3();System.out.println("============================");//對象適配器ITarget objectAdaptor = new MyObjectAdaptor(new MyAdaptee());objectAdaptor.doSomthing1();objectAdaptor.doSomthing2();objectAdaptor.doSomthing3();} }- ITarget:表示需要轉接成的接口定義。
- MyAdaptee:是不兼容 ITarget 接口定義的接口。
- MyClassAdaptor:將 Adaptee 轉化成一組符合 ITarget 接口定義的接口(類適配器)。
- MyObjectAdaptor:將 Adaptee 轉化成一組符合 ITarget 接口定義的接口(對象適配器)。
- Test:測試類
輸出結果如下
這是 todo1 我是被重新實現的dosomthing2 這是 todo3 ============================ 這是 todo1 我是被重新實現的dosomthing2 這是 todo3Process finished with exit code 0這兩種實現方式都比較的巧妙,如果看不太明白的話可以結合著實際的應用場景再試試,個人覺得還是沒有那么難理解,不過現在還有一個問題需要我們進一步分析,是什么呢?什么時候使用類適配器,什么時候使用對象適配器呢?我們都說組合優于繼承,當然是優先選擇對象適配器啦,千萬不要有這種想法,雖然組合優于繼承,但這并不能說明任何情況組合都比繼承適用,那我們如何來判斷這兩種實現方式呢?
標準主要有兩個,第一個是 MyAdaptee 的接口個數,第二個則是 MyAdaptee 與 ITarget 的契合程度。這怎么理解呢?
- MyAdaptee 接口較少,類適配器和對象適配器兩者任選其一即可。
- MyAdaptee 接口很多,但是大部分的接口 都是與 ITarget 相同,只是少部分存在區別,那么推薦使用類適配器(繼承實現),改動代碼量相對于對象適配器來說較少。
- MyAdaptee 接口很多,并且大部分的接口都不相同,這個時候推薦使用對象適配器(組合實現),因為組合比繼承更加的靈活。
適配器模式應用場景
適配器模式的應用場景還是比較多的,但是這種設計模式多用于補救,當程序設計出現問題,升級改變較大時,需要一個類似適配器的東西將他們連接起來,所以我們在正常的開發中使用這種模式的機會還是不多的,也希望同學們設計的程序都是很穩定,不會用到適配器來做補救。
應用場景太多,我拿敏感詞過濾來做一個demo說明吧,敏感詞大家應該都了解過,玩游戲的時候如果有一個隊友超鬼了,那么其他隊友就會對他進行親切的問候,這個時候有些太過優雅的詞會被 ”**“ 代替,這個就是我們說的敏感詞過濾。
假設我們系統一共有三家敏感詞過濾的廠商,但是他們的接口標準都不相同,一般的寫法是什么樣的呢?上代碼
package com.liuxing.adapter.demo;import org.apache.commons.lang3.ObjectUtils;import java.util.ArrayList; import java.util.List;/*** @ClassName AliSensitiveWordFilter* @Description 某里云過濾* @Author liuxing007* @Date 2021/4/27 16:07*/ public class AliSensitiveWordFilter {private static final List<String> sensitiveWords = new ArrayList<>(3);static {sensitiveWords.add("垃圾");sensitiveWords.add("廢物");sensitiveWords.add("滾");}/*** @param filterWordVo* @return java.lang.String* @Description 過濾* @Date 2021/4/27 16:34*/public String filterWord(FilterWordVo filterWordVo) {if (ObjectUtils.isEmpty(filterWordVo)) {return null;}String repWord = filterWordVo.getRepWord();String word = filterWordVo.getWord();for (String sensitiveWord : sensitiveWords) {if (word.indexOf(sensitiveWord) >= 0) {System.out.println("找到敏感詞:" + sensitiveWord + ",直接替換");word = word.replaceAll(sensitiveWord, repWord);}}return word;} } package com.liuxing.adapter.demo;import org.apache.commons.lang3.StringUtils;import java.util.ArrayList; import java.util.List;/*** @ClassName DuSensitiveWordFilter* @Description 某度云過濾* @Author liuxing007* @Date 2021/4/27 16:07*/ public class DuSensitiveWordFilter {private static final List<String> sensitiveWords = new ArrayList<>(3);static {sensitiveWords.add("爸");sensitiveWords.add("媽");sensitiveWords.add("爺");}/*** @param word 需要過濾的內容* @param repWord 需要替換成什么* @return java.lang.String* @Description 過濾* @Date 2021/4/27 16:34*/public String filterWord(String word, String repWord) {if (StringUtils.isAllEmpty(word, repWord)) {return null;}for (String sensitiveWord : sensitiveWords) {if (word.indexOf(sensitiveWord) >= 0) {System.out.println("找到敏感詞:" + sensitiveWord + ",直接替換");word = word.replaceAll(sensitiveWord, repWord);}}return word;} } package com.liuxing.adapter.demo;import org.apache.commons.lang3.StringUtils;import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map;/*** @ClassName WeiSensitiveWordFilter* @Description 某為云過濾* @Author ymy* @Date 2021/4/27 16:07*/ public class WeiSensitiveWordFilter {private static Map<Integer, String> filterMap = new HashMap<>();private static final List<String> sensitiveWords = new ArrayList<>(3);static {//將敏感詞過濾為 *filterMap.put(1, "*");//將敏感詞過濾為空字符串filterMap.put(2, "");//將敏感詞過濾為 -filterMap.put(3, "-");sensitiveWords.add("干");sensitiveWords.add("操");sensitiveWords.add("懟");}/*** @param word 需要過濾的內容* @param type 需要替換成什么* @return java.lang.String* @Description 過濾* @Date 2021/4/27 16:34*/public String filterWord(String word, Integer type) {if (StringUtils.isEmpty(word) || type == null) {return null;}String repWord = filterMap.get(type);for (String sensitiveWord : sensitiveWords) {if (word.indexOf(sensitiveWord) >= 0) {System.out.println("找到敏感詞:" + sensitiveWord + ",直接替換");word = word.replaceAll(sensitiveWord, repWord);}}return word;} } package com.liuxing.adapter.demo.normal;import com.liuxing.adapter.demo.AliSensitiveWordFilter; import com.liuxing.adapter.demo.DuSensitiveWordFilter; import com.liuxing.adapter.demo.FilterWordVo; import com.liuxing.adapter.demo.WeiSensitiveWordFilter;/*** @ClassName SensitiveWordFilterService* @Description 敏感詞過濾處理* @Author liuxing007* @Date 2021/4/27 17:28*/ public class SensitiveWordFilterService {private AliSensitiveWordFilter aliSensitiveWordFilter = new AliSensitiveWordFilter();private DuSensitiveWordFilter duSensitiveWordFilter = new DuSensitiveWordFilter();private WeiSensitiveWordFilter weiSensitiveWordFilter = new WeiSensitiveWordFilter();public String filter(String word) {FilterWordVo filterWordVo = FilterWordVo.builder().word(word).repWord("*").build();word = aliSensitiveWordFilter.filterWord(filterWordVo);word = duSensitiveWordFilter.filterWord(word, "*");word = weiSensitiveWordFilter.filterWord(word, 1);return word;}} package com.liuxing.adapter.demo;import lombok.Builder; import lombok.Data;/*** @ClassName FilterWordVo* @Description 敏感詞過濾參數* @Author liuxing007* @Date 2021/4/27 16:09*/ @Data @Builder public class FilterWordVo {/***需要替換的內容*/private String word;/***替換成什么*/private String repWord; } package com.liuxing.adapter.demo.normal;/*** @ClassName Test* @Description 敏感詞過濾測試* @Author liuxing007* @Date 2021/4/27 16:38*/ public class Test {public static void main(String[] args) {SensitiveWordFilterService sensitiveWordFilterService = new SensitiveWordFilterService();String word = sensitiveWordFilterService.filter("你就是一個垃圾,這么菜,你媽媽沒教你打游戲嗎?干");System.out.println("過濾后的內容:" + word);} }- AliSensitiveWordFilter:某里云過濾
- DuSensitiveWordFilter:某度過濾
- WeiSensitiveWordFilter:某為云過濾
- FilterWordVo:敏感詞過濾所需要的參數信息
- SensitiveWordFilterService:敏感詞過濾處理類
- Test:測試類
一共有三家過濾敏感詞的廠商,我在 SensitiveWordFilterService 類中 filter()方法分別組裝了這三家廠商需要的參數,然后進行依次調用過濾,將最終的結果進行返回。這樣寫完全沒有問題,但是這里有一個問題,那就是擴展性很差,如果我繼續增加一個廠商或者刪除一個廠商,我都需要去改 SensitiveWordFilterService 類中的 filter()方法,非常麻煩而且還是容出現bug,所以這個時候,就需要我們的適配器模式上場了,我們把過濾這件事情交給它,由它去對每個廠商做適配,這樣,在后續改動的過程中,我就不需要平凡的去改動 SensitiveWordFilterService 類 。
適配器改造代碼開始
package com.liuxing.adapter.demo.adaptor;public interface ISensitiveWordFilter {/*** @Description 過濾* @Date 2021/4/27 17:51* @param word* @return java.lang.String*/String filter(String word); } package com.liuxing.adapter.demo.adaptor;import com.liuxing.adapter.demo.AliSensitiveWordFilter; import com.liuxing.adapter.demo.FilterWordVo;/*** @ClassName AliSensitiveWordFilterAdaptor* @Description 某里云適配類* @Author liuxing007* @Date 2021/4/27 17:52*/ public class AliSensitiveWordFilterAdaptor implements ISensitiveWordFilter {private AliSensitiveWordFilter aliSensitiveWordFilter = new AliSensitiveWordFilter();@Overridepublic String filter(String word) {FilterWordVo filterWordVo = FilterWordVo.builder().word(word).repWord("*").build();return aliSensitiveWordFilter.filterWord(filterWordVo);} } package com.liuxing.adapter.demo.adaptor;import com.liuxing.adapter.demo.DuSensitiveWordFilter;/*** @ClassName AliSensitiveWordFilterAdaptor* @Description 某度適配類* @Author liuxing007* @Date 2021/4/27 17:52*/ public class DuSensitiveWordFilterAdaptor implements ISensitiveWordFilter {private DuSensitiveWordFilter duSensitiveWordFilter = new DuSensitiveWordFilter();@Overridepublic String filter(String word) {return duSensitiveWordFilter.filterWord(word, "*");} } package com.liuxing.adapter.demo.adaptor;import com.liuxing.adapter.demo.WeiSensitiveWordFilter;/*** @ClassName AliSensitiveWordFilterAdaptor* @Description 某為適配類* @Author liuxing007* @Date 2021/4/27 17:52*/ public class WuiSensitiveWordFilterAdaptor implements ISensitiveWordFilter {private final WeiSensitiveWordFilter weiSensitiveWordFilter = new WeiSensitiveWordFilter();@Overridepublic String filter(String word) {return weiSensitiveWordFilter.filterWord(word, 1);} } package com.liuxing.adapter.demo.adaptor;import java.util.ArrayList; import java.util.List;/*** @ClassName AdaptorManagent* @Description 適配器管理* @Author liuxing007* @Date 2021/4/27 17:57*/ public class AdaptorManagent {private List<ISensitiveWordFilter> sensitiveWordFilters = new ArrayList<>();public void addAdaptor(ISensitiveWordFilter sensitiveWordFilter) {sensitiveWordFilters.add(sensitiveWordFilter);}public String filter(String word){for(ISensitiveWordFilter sensitiveWordFilter: sensitiveWordFilters){word = sensitiveWordFilter.filter(word);}return word;}} package com.liuxing.adapter.demo.adaptor;/*** @ClassName AdaptorTest* @Description 測試* @Author ymy* @Date 2021/4/27 18:01*/ public class AdaptorTest {public static void main(String[] args) {AdaptorManagent adaptorManagent = new AdaptorManagent();adaptorManagent.addAdaptor(new AliSensitiveWordFilterAdaptor());adaptorManagent.addAdaptor(new DuSensitiveWordFilterAdaptor());adaptorManagent.addAdaptor(new WuiSensitiveWordFilterAdaptor());String word = adaptorManagent.filter("你就是一個垃圾,這么菜,你媽媽沒教你打游戲嗎?干");System.out.println("過濾后的內容:" + word);}}很明顯,通過引入適配器模式的改造,代碼變多了,這是不是就意味著變復雜了呢?有沒有變復雜,我覺得它是相對的,如果敏感詞過濾只有兩家廠商,永遠都不會添加或者刪除了,我們引入適配器模式,這樣確實把簡單問題復雜化了,有點畫蛇添足,很明顯,敏感詞過濾的廠商當然是多多益善,而且變動也會比較頻繁,比如某一家廠商價格升高了,那我就換一家,我覺得現在用的這幾家還是不能過濾所有的敏感詞,我又引入了 2 家新的廠商,在這些情況下,普通的實現方式會比較吃力,每次修改廠商都需要改動核心代碼,非常容易就整出bug,不但你難過,測試也難過,明明之前沒有問題的,加了一個廠商就導致所有廠商都有問題了,一個腦袋兩個大,所以,這個時候,推薦使用適配器模式,在適配器模式下,你無需改動過濾的核心代碼,如果你添加新的廠商,只需要新增一個類,并且實現ISensitiveWordFilter 接口即可,有的同學可能就會問了,添加刪除的時候不是在 main 函數中也有修改嗎?沒錯,目前demo中確實需要在main函數中做修改,但是正常開發中,肯定是不會這么寫的,你可以通過注解的方式在項目啟動的時候就把所有的適配器都加載出來,這樣后續的改動,只需要增加或者刪除實現類即可,非常的方便。
總結
什么是適配器模式?
適配器模式(英語:adapter pattern)有時候也稱包裝樣式或者包裝(英語:wrapper)。將一個類的接口轉接成用戶所期待的。一個適配使得因接口不兼容而不能在一起工作的類能在一起工作,做法是將類自己的接口包裹在一個已存在的類中。簡單點來說,就是讓兩個原本不發通信的兩個類通過轉接編程可以正常通信。
適配器的兩種實現方式?
類適配器:使用繼承關系實現,如果需要適配的接口很多,并且大部分相同,只存在少部分不同的場景推薦使用類適配器。
對象適配器:對象適配器使用組合關系實現,如果需要適配的接口很多,并且大部分接口都不相同的場景下推薦對象適配器。
適配器的應用場景?
適配器模式的應用場景還是比較多的,但是這種設計模式多用于補救,當程序設計出現問題,升級改變較大時,需要一個類似適配器的東西將他們連接起來,所以我們在正常的開發中使用這種模式的機會還是不多的,也希望同學們設計的程序都是很穩定,不會用到適配器來做補救。
總的來說,適配器用于補救,當我們設計接口的時候一定要考慮到它的擴展性,不能為了一時方便,導致后面為了填坑而掉頭發。所以不要總是想著先實現,后面再優化,可能到了后面,頭發掉完了都不一定能成功將它優化。
如果覺得我的博客對你有所幫助,還希望你能吝嗇點個贊加關注,讀者的肯定就是我寫作的動力!
源代碼:https://github.com/361426201/design-mode.git
推薦的設計模式文章
【設計模式】單例模式
【設計模式】工廠模式:你還在使用一堆的if/else創建對象嗎?
【設計模式】建造者模式:你創建對象的方式有它絲滑嗎?
【設計模式】原型模式:如何快速的克隆出一個對象?
【設計模式】策略模式:我是一個有謀略的類
【設計模式】觀察者模式:一個注冊功能也能使用到設計模式?
【設計模式】門面模式:接口就像門面,一眼就能看出你的代碼水平
【設計模式】職責鏈模式:如果第三方短信平臺掛了怎么辦?
【設計模式】代理模式:神奇的代理模式,節省了我80%開發時間
總結
以上是生活随笔為你收集整理的【设计模式】适配器模式:如何巧妙地过滤游戏中的敏感词的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 14天精读掌握《陶哲轩:实分析》第11天
- 下一篇: 学习报告(2)