策略模式

最近我在项目中做了一个地图API相关的功能开发,需求就是,国内调百度地图API,海外就调谷歌地图API。功能实现很简单,但是这个功能开发完后,让我对策略模式有了更深的理解和印象。在这里我把当时的想法和遇到的问题完整记录下来。

首先,定义一个地图接口。

/**
 * 地图service
 */
public interface IMapService {
    /**
     * 兴趣点查询
     *
     * @param poi 兴趣点
     * @return
     */
    void queryPlaceOfInterest(PoiInfo poi);
}

然后,用百度和谷歌地图API分别实现这个接口。

/**
 * 百度地图service
 */
public class BaiduMapServiceImpl implements IMapService {
    @Override
    public void queryPlaceOfInterest(PoiInfo poi) {
        System.out.println("baidu地图兴趣点查询!");
    }
}

/**
 * 谷歌地图service
 */
public class GoogleMapServiceImpl implements IMapService {
    @Override
    public void queryPlaceOfInterest(PoiInfo poi) {
        System.out.println("google地图兴趣点查询!");
    }
}

就是这样,这个功能就基本完成了。客户端使用如下:

public class Client {
    public static void main(String[] args) {
        /**
         * 客户端持有百度地图实现类和谷歌地图实现类
         */
        IMapService  baiduMap = new BaiduMapServiceImpl ();
        IMapService  goolgeMap = new GoogleMapServiceImpl();

        // 待查询的兴趣点
        PoiInfo poi = new PoiInfo();

        baiduMap.queryPlaceOfInterest(poi);
        goolgeMap.queryPlaceOfInterest(poi);

    }
}

这是我一开始的思路,我底层提供不同地图的实现类,客户端想用什么地图,由他自己调用即可。其实,这不符合软件设计的原则——高内聚低耦合开闭原则

我这里就是客户端直接对实现类的引用。造成客户端代码和地图实现类高度耦合了。这种代码的问题就是,扩展性差。
以后有其他地图要接入,如:高德。我会提供一个高德地图实现类,然后客户端代码也要改动,添加一个对高德地图实现的引用。这样子其实很不友好,对客户端代码侵入性较高,不利于扩展。

其实,我们可以给客户端提供一个适配器,客户端只持有适配器,具体的服务由适配器去调用,最后达到解耦的目的。

那如何去操作呢?策略模式就完全解决了我的问题!

策略模式

策略模式作为一种软件设计模式,指对象有某个行为,但是在不同的场景中,该行为有不同的实现算法。比如每个人都要“交个人所得税”,但是“在美国交个人所得税”和“在中国交个人所得税”就有不同的算税方法。

组成

  • 抽象策略角色: 策略类,通常由一个接口或者抽象类实现。
  • 具体策略角色:包装了相关的算法和行为。
  • 环境角色:持有一个策略类的引用,最终给客户端调用

UML图

这里写图片描述

策略模式的具体实现

接下来,我们只要按照策略模式的角色组成完善代码即可。

  • 抽象策略角色,就是我代码中的IMapService接口。
  • 具体策略角色,就是我要实现的具体地图实现类,如百度,高德,谷歌等。
  • 环境角色,这个角色很重要,大家可以发现,我之前的代码设计其实就是缺了这个角色,这也正是我要的“适配器”。它的作用将策略类从客户端解耦出来。所以我要做的工作就是,设计这个环境角色类。

在我的代码中,环境角色要干的事情是,它先选择正确的具体地图实现类,然后执行对应的方法,最终把结果返回给客户端即可。

为了让环境角色类可以判断具体的实现类,我在每个实现类中增加地图类型的标识。代码如下:

/**
 * 地图类型枚举类
 * @author chunxingzhang
 *
 */
public enum MapTypeEnum {
    BAIDU,GOOGLE,GAODE
}

/**
 * 地图service
 */
public interface IMapService {

    /**
     * 是否支持poi的地图类型
     * @param poi
     * @return
     */
    boolean supportMapType(PoiInfo poi);

    /**
     * 兴趣点查询
     *
     * @param poi 兴趣点
     * @return
     */
    void queryPlaceOfInterest(PoiInfo poi);
}

public class BaiduMapServiceImpl implements IMapService {

    @Override
    public void queryPlaceOfInterest(PoiInfo poi) {
        System.out.println("baidu地图兴趣点查询!");
    }

    /**
     * 支持百度
     */
    @Override
    public boolean supportMapType(PoiInfo poi) {
        return MapTypeEnum.BAIDU.ordinal() == poi.mapType;
    }

}

public class GoogleMapServiceImpl implements IMapService {

    @Override
    public void queryPlaceOfInterest(PoiInfo poi) {
        // TODO Auto-generated method stub
        System.out.println("google地图兴趣点查询!");
    }

    /**
     * 支持谷歌
     */
    @Override
    public boolean supportMapType(PoiInfo poi) {
        return MapTypeEnum.GOOGLE.ordinal() == poi.mapType;
    }

}

新增MapTypeEnum枚举类来维护地图类型,在接口中新加supportMapType(PoiInfo poi)用来标识实现类是否支持当前地图。

接下来可以去实现环境角色类Context。根据定义,环境角色类持有策略类的引用来设计。

/**
 * 环境角色类,持有对具体策略类实现的集合引用
 * @author chunxingzhang
 *
 */
public class MapServiceContext implements IMapService {
    /**
     * 使用spring管理bean,可以这样用,
     * 自动装配所有实现类到集合中
     */
    //@Autowired
    //private List<IMapService> mapServiceList = new ArrayList<>();

    private List<IMapService> mapServiceList = new ArrayList<>();

    /**
     * 初始化的时候将所有已有实现类装配到集合中。
     */
    {
        mapServiceList.add(new BaiduMapServiceImpl());
        mapServiceList.add(new GoogleMapServiceImpl());
    }


    /**
     * 遍历地图实现类集合,是否有支持当前poi的地图类型
     */
    @Override
    public boolean supportMapType(PoiInfo poi) {
        for (IMapService mapService : mapServiceList) {
            if (mapService.supportMapType(poi)) {
                return true;
            }
        }

        return false;
    }

    @Override
    public void queryPlaceOfInterest(PoiInfo poi) {
        // TODO Auto-generated method stub
        getSupportedMapServiceImpl(poi).queryPlaceOfInterest(poi);
    }

    /**
     * 选择具体的地图实现类
     * @param poi
     * @return
     */
    private IMapService getSupportedMapServiceImpl(PoiInfo poi) {
        for (IMapService mapService : mapServiceList) {
            if (mapService.supportMapType(poi)) {
                return mapService;
            }
        }

        throw new UnsupportedOperationException("不支持当前poi的地图类型!");
    }
}

于是客户端代码就只要引用这个context类即可。

public class Client {

    public static void main(String[] args) {
        /**
         * 客户端持有这个环境角色类context即可
         */
        IMapService  MapServiceContext = new MapServiceContext ();

        // 获取一个待查询的兴趣点,使用谷歌地图
        PoiInfo poi1 = new PoiInfo();
        poi1.mapType = 0;

        MapServiceContext.queryPlaceOfInterest(poi1);

        // 获取一个待查询的兴趣点,使用百度地图
        PoiInfo poi2 = new PoiInfo();
        poi2.mapType = 1;
        MapServiceContext.queryPlaceOfInterest(poi2);
    }
}

结果如下:

google地图兴趣点查询!
baidu地图兴趣点查询!

这样就达到了我的预期目标,客户端不需要关系底层地图的具体的实现类型,他只要调用我提供的context类下面的方法就好了,因为context类也实现了IMapService的接口,这样就保持了它和具体实现类的统一性。客户端使用起来和直接使用地图具体实现类没有任何区别。当接口有变化,或者新的地图类型加入后,不会再影响到客户端的逻辑,只要维护地图服务层的逻辑即可。

总结

这就是我对该功能需求实现的最终代码结果了。后来在反复思考的过程中,我发现我的代码形式跟网上的策略模式的定义有点出入(尴尬)。比如:

  • 我的环境角色类context实现了IMapService接口。

    实现同一个接口有助于让环境角色类和策略类统一规范,这样使客户端用起来感觉不到任何差异性。

  • 环境角色类context类是对具体策略类集合的引用,而不是直接持有一个策略类引用。

    造成差别的原因是,策略模式中context类持有一个策略类引用,然后客户端再使用context类时要传一个具体的策略类给他,也就是说客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这恰恰是我不愿意看到的。所以我对其做了一定的修改。

参考

策略模式
代理模式
单一职责原则
开闭原则

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值