【设计模式】适配器模式:如何巧妙地过滤游戏中的敏感词

真正的大师,永远都怀着一颗学徒的心 -------无极剑圣 · 易

适配器是什么?

适配器是一个接口转换器,它可以是一个独立的硬件接口设备,允许硬件或电子接口与其它硬件或电子接口相连,也可以是信息接口。比如:电源适配器、三角架基座转接部件、USB与串口的转接设备等(百度百科)。

很抽象?我们来看几张图片,你就会明白适配器是什么了。

【设计模式】适配器模式:如何巧妙地过滤游戏中的敏感词_第1张图片

投影转接头,由于有些电脑的接口与投影的接口不一致,所以就会导致电脑使用不了投影,所以投影转接头就是将电脑和投影两者做了适配,让电脑可能正常使用与他不匹配的投影。
【设计模式】适配器模式:如何巧妙地过滤游戏中的敏感词_第2张图片
耳机转接头,用过苹果手机的应该都知道,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 {

    @Override
    public void doSomthing1() {
        super.todo1();
    }

    @Override
    public 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;
   }

   @Override
   public void doSomthing1() {
       //交给 MyAdaptee 实现
       myAdaptee.todo1();
   }

   @Override
   public void doSomthing2() {
        System.out.println("我是被重新实现的dosomthing2");
   }

   @Override
   public 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
这是 todo3

Process finished with exit code 0

这两种实现方式都比较的巧妙,如果看不太明白的话可以结合着实际的应用场景再试试,个人觉得还是没有那么难理解,不过现在还有一个问题需要我们进一步分析,是什么呢?什么时候使用类适配器,什么时候使用对象适配器呢?我们都说组合优于继承,当然是优先选择对象适配器啦,千万不要有这种想法,虽然组合优于继承,但这并不能说明任何情况组合都比继承适用,那我们如何来判断这两种实现方式呢?

标准主要有两个,第一个是 MyAdaptee 的接口个数,第二个则是 MyAdaptee 与 ITarget 的契合程度。这怎么理解呢?

  • MyAdaptee 接口较少,类适配器和对象适配器两者任选其一即可。
  • MyAdaptee 接口很多,但是大部分的接口 都是与 ITarget 相同,只是少部分存在区别,那么推荐使用类适配器(继承实现),改动代码量相对于对象适配器来说较少。
  • MyAdaptee 接口很多,并且大部分的接口都不相同,这个时候推荐使用对象适配器(组合实现),因为组合比继承更加的灵活。

适配器模式应用场景

适配器模式的应用场景还是比较多的,但是这种设计模式多用于补救,当程序设计出现问题,升级改变较大时,需要一个类似适配器的东西将他们连接起来,所以我们在正常的开发中使用这种模式的机会还是不多的,也希望同学们设计的程序都是很稳定,不会用到适配器来做补救。

  1. 敏感词过滤
  2. 升级兼容
  3. 架构设计缺陷
  4. Slf4j日志打印
  5. 外部依赖

应用场景太多,我拿敏感词过滤来做一个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();

    @Override
    public 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();

    @Override
    public 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();


    @Override
    public 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)。将一个类的接口转接成用户所期待的。一个适配使得因接口不兼容而不能在一起工作的类能在一起工作,做法是将类自己的接口包裹在一个已存在的类中。简单点来说,就是让两个原本不发通信的两个类通过转接编程可以正常通信。

适配器的两种实现方式?

类适配器:使用继承关系实现,如果需要适配的接口很多,并且大部分相同,只存在少部分不同的场景推荐使用类适配器。

对象适配器:对象适配器使用组合关系实现,如果需要适配的接口很多,并且大部分接口都不相同的场景下推荐对象适配器。

适配器的应用场景

适配器模式的应用场景还是比较多的,但是这种设计模式多用于补救,当程序设计出现问题,升级改变较大时,需要一个类似适配器的东西将他们连接起来,所以我们在正常的开发中使用这种模式的机会还是不多的,也希望同学们设计的程序都是很稳定,不会用到适配器来做补救。

  1. 敏感词过滤
  2. 升级兼容
  3. 架构设计缺陷
  4. Slf4j日志打印
  5. 外部依赖

总的来说,适配器用于补救,当我们设计接口的时候一定要考虑到它的扩展性,不能为了一时方便,导致后面为了填坑而掉头发。所以不要总是想着先实现,后面再优化,可能到了后面,头发掉完了都不一定能成功将它优化。

如果觉得我的博客对你有所帮助,还希望你能吝啬点个赞加关注,读者的肯定就是我写作的动力!

源代码:https://github.com/361426201/design-mode.git

推荐的设计模式文章

【设计模式】单例模式

【设计模式】工厂模式:你还在使用一堆的if/else创建对象吗?

【设计模式】建造者模式:你创建对象的方式有它丝滑吗?

【设计模式】原型模式:如何快速的克隆出一个对象?

【设计模式】策略模式:我是一个有谋略的类

【设计模式】观察者模式:一个注册功能也能使用到设计模式?

【设计模式】门面模式:接口就像门面,一眼就能看出你的代码水平

【设计模式】职责链模式:如果第三方短信平台挂了怎么办?

【设计模式】代理模式:神奇的代理模式,节省了我80%开发时间

你可能感兴趣的:(设计模式,java,设计模式,适配器模式)