【设计模式实战】策略模式:原理篇

前言

小明在公司负责开发网约车应用,应用可以呼叫出租车和私家车两款车。乘客打车时要显示预计价格,而价格通过里程来计算。

小明是这样写的:

public class PriceCalculator {
    public static final int TAXI = 1;
    public static final int PRIVATE_CAR = 2;

    private static float taxibusPrice(int km) {
        //业务逻辑
        return km * 1.28f;
    }

    private static float privateCarPrice(int km) {
        //业务逻辑
        return km * 1.23f;
    }

    public static float calculatePrice(int km, int type) {
        if (type == TAXI) {
            return taxibusPrice(km);
        } else if (type == PRIVATE_CAR) {
            return privateCarPrice(km);
        }
        return 0;
    }
}
    public void test() {
        //缺点:增加方案要改代码,不符合开闭原则。代码臃肿,耦合太高。
        float taxiPrice = PriceCalculator.calculatePrice(5, PriceCalculator.TAXI);
        System.out.println("出租车价格:" + taxiPrice);

        float privateCarPrice = PriceCalculator.calculatePrice(5, PriceCalculator.PRIVATE_CAR);
        System.out.println("私家车价格:" + privateCarPrice);
    }
出租车价格:6.3999996
私家车价格:6.15

小明的领导一看,说这样写不行,如果要增加专车、商务车,岂不是要修改PriceCalculator类?这就违反了开闭原则,有新变化时,需要改变旧事物。随着业务逻辑的增加,PriceCalculator类也会变得越来越臃肿不堪,难以扩展。

领导建议面对这种因为使用不同方式,会改变结果的情景时,可以使用策略模式代替。

使用策略模式改造

首先我们需要把策略抽象出来,将它标准化,做成接口类。实现类实现接口时,需要实现计算价格的函数

public interface CalculateStrategy {
    float calculatePrice(int km);
}
public class TaxiStragety implements CalculateStrategy {
    @Override
    public float calculatePrice(int km) {
        //业务逻辑
        return km * 1.28f;
    }
}
public class PrivateCarStragety implements CalculateStrategy {
    @Override
    public float calculatePrice(int km) {
        //业务逻辑
        return km * 1.23f;
    }
}

然后我们需要一个系统环境,可以切换策略和执行策略

public class TranficCalculator {

    public TranficCalculator() {
    }

    private CalculateStrategy mStrategy;

    public void setStrategy(CalculateStrategy strategy) {
        mStrategy = strategy;
    }

    public float calculatePrice(int km) {
        if (mStrategy != null) {
            return mStrategy.calculatePrice(km);
        }
        return 0;
    }

}

开始测试

        TranficCalculator calculator = new TranficCalculator();
        calculator.setStrategy(new TaxiStragety());
        calculator.calculatePrice(5);

        calculator.setStrategy(new PrivateCarStragety());
        calculator.calculatePrice(5);
出租车策略价格:6.3999996
私家车策略价格:6.15

假如我们要添加一辆商务车,只需要增加一个商务车策略

public class BusinessStragety implements CalculateStrategy {
    @Override
    public float calculatePrice(int km) {
        //业务逻辑
        return km * 3.18f;
    }
}
    public void test3() {
        TranficCalculator calculator = new TranficCalculator();
        calculator.setStrategy(new BusinessStragety());
        System.out.println("商务车策略价格:" + calculator.calculatePrice(5));
    }
商务车策略价格:15.900001

策略模式讲解

看一下策略模式的UML图
在这里插入图片描述
Strategy,抽象策略角色:抽象角色,通常使用接口或者抽象类实现。
ConcreteStrategy,具体策略角色:实现了抽象角色的类,包含具体的算法和行为。
Context,环境角色,持有抽象角色的引用,给客户端调用。

意图:封装一系列的算法,使它们可互相替换
主要解决:在相似算法情况下,可替换由if…else…带来的复杂

看到这里,去检查自己写的代码,那些有if…else…或者switch…case…的地方,并且算法类似,是不是适合用策略模式替代一下?

Android动画插值器

Android给我们提供了很多动画,比如透明动画、平移动画、选择动画、缩放动画等。
比如说平移动画:

			 //平移动画TranslateAnimation 从(0,0)平移到(300,200) 
        TranslateAnimation translateAnimation = new TranslateAnimation(0,
                300.f, 0, 200.f);
        //动画时间为2秒
        translateAnimation.setDuration(2000);
        //变化率开始和结束缓慢但在中间加速
        Interpolator interpolator = new AccelerateDecelerateInterpolator();
        translateAnimation.setInterpolator(interpolator);
        //开始动画
        mImageView.startAnimation(translateAnimation);

我们可以看到Animation可以设置一个Interpolator,Interpolator是插值器的意思,可以改变动画变化的速率。并且可以有多种插值器供我们选择:
AccelerateDecelerateInterpolator :变化率开始和结束缓慢但在中间加速
AccelerateInterpolator :变化率开始缓慢然后加速
AnticipateInterpolator :变化开始向后然后向前飞行
AnticipateOvershootInterpolator :变化开始向后然后向前飞行并超过目标值,最后返回到最终值
BaseInterpolator :插值器扩展的抽象类
BounceInterpolator :更改在结束时反弹
CycleInterpolator :重复动画指定的周期数
DecelerateInterpolator :变化率开始和结束缓慢但在中间加速。
LinearInterpolator :变化率是恒定的
OvershootInterpolator :变化向前晃动并超过最后一个值然后返回

这些插值器其实就是一个个策略,一个个算法,它的抽象策略是TimeInterpolator

public interface TimeInterpolator {
    float getInterpolation(float var1);
}

具体策略实现了getInterpolation方法

@HasNativeInterpolator
public class AccelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
    private final float mFactor;
    private final double mDoubleFactor;
 
    public AccelerateInterpolator() {
        mFactor = 1.0f;
        mDoubleFactor = 2.0;
    }
 
    /**
     * Constructor
     */
    public AccelerateInterpolator(float factor) {
        mFactor = factor;
        mDoubleFactor = 2 * mFactor;
    }
 
    public AccelerateInterpolator(Context context, AttributeSet attrs) {
        this(context.getResources(), context.getTheme(), attrs);
    }
 
    public float getInterpolation(float t) {
        if (mFactor == 1.0f) {
            return t * t;
        } else {
            return (float)Math.pow(t, mDoubleFactor);
        }
    }
}

这就是Android源码中很典型的策略模式。

还有其它常见的:

  1. 通过设置不同的Adapter(即不同的策略),我们就可以写出符合我们需求的ListView布局
  2. RecyclerView设置不同的LayoutManager,可以决定RV的显示风格。(线性布局管理器(LinearLayoutManager)、网格布局管理器(GridLayoutManager)、瀑布流布局管理器(StaggeredGridLayoutManager))

举个例子:图片加载器

加载图片有很多第三方库,如Glide、Picasso、Fresco等,我们可以根据自己的选择去切换不同的加载工具。

首先是抽象类,只有一个显示图片的接口

public interface ImageLoaderProvider {
    void loadImage(Context ctx, ImageConfig img);
}

//ImageConfig是加载的一些配置,比如有些在wifi下才加载,或者加载大图,中图,缩略图等等,简单的代码如下:
public class ImageConfig {
    private int type;
    private String url;
    private int placeHolder;
    private ImageView imageView;
    private int strategy;//加载策略,是否在wifi下才加载或者其他的策略,比如加载大图,中图,或者是小图等等

    private ImageConfig(ImageConfig imageConfig) {
        this.type = imageConfig.type;
        this.url = imageConfig.url;
        this.placeHolder = imageConfig.placeHolder;
        this.imageView = imageConfig.imageView;
        this.strategy = imageConfig.strategy;
    }

    private ImageConfig(){

    }

    public int getType() {
        return type;
    }

    public String getUrl() {
        return url;
    }

    public int getPlaceHolder() {
        return placeHolder;
    }

    public ImageView getImageView() {
        return imageView;
    }

    public int getStrategy() {
        return strategy;
    }


    public static class Builder {
        private ImageConfig imageConfig;

        public Builder() {
            this.imageConfig = new ImageConfig();
        }
        public Builder type(int type) {
            imageConfig.type = type;
            return this;
        }

        public Builder url(String url) {
            imageConfig.url = url;
            return this;
        }

        public Builder placeHolder(int placeHolder) {
            imageConfig.placeHolder = placeHolder;
            return this;
        }

        public Builder imgView(ImageView imgView) {
            imageConfig.imageView = imgView;
            return this;
        }

        public Builder strategy(int strategy) {
            imageConfig.strategy = strategy;
            return this;
        }

        public ImageConfig build(){
            return new ImageConfig(imageConfig);
        }

    }
}
//可以看到目前仅仅是判断是否在wifi下才加载,当然实际情况可以适当扩展,但原理不变。

Picasso策略

public class PicassoImageLoaderProvider implements ImageLoaderProvider {
    private ImageView imageView;

    @Override
    public void loadImage(Context ctx, ImageConfig config) {

        // 一般实现,或者根据具体的一些另外的策略加载,比如是否在wifi下自动加载等,根据业务具体决定
        Picasso
                .with(ctx)
                .load(config.getUrl())
                .tag("Picasso") //参数为 Object
                .placeholder(config.getPlaceHolder())
                .into(target);
        imageView = config.getImageView();
    }

    private Target target = new Target() {
        @Override
        public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
            //加载成功后会得到一个bitmap,可以自定义操作
            imageView.setImageBitmap(bitmap);
        }

        @Override
        public void onBitmapFailed(Drawable errorDrawable) {
            // 加载失败进行相应处理
        }

        @Override
        public void onPrepareLoad(Drawable placeHolderDrawable) {

        }
    };
}

Glide策略

public class GlideImageLoaderProvider implements ImageLoaderProvider {
    @Override
    public void loadImage(Context ctx, ImageConfig config) {
        boolean flag = SettingUtils.getOnlyWifiLoadImg();
        //如果不是在wifi下加载图片,直接加载
        if (!flag) {
            loadNormal(ctx, config);
            return;
        }
        int strategy = config.getStrategy();
        //这里1表示的是wifi加载
        if (strategy == 1) {
            int netType = NetUtils.getNetWorkType(ctx);
            //如果是在wifi下才加载图片,并且当前网络是wifi,直接加载
            if (netType == NetUtils.NETWORKTYPE_WIFI) {
                loadNormal(ctx, config);
            } else {
                //如果是在wifi下才加载图片,并且当前网络不是wifi,加载缓存
                loadCache(ctx, config);
            }
        } else {
            //如果不是在wifi下才加载图片
            loadNormal(ctx, config);
        }

    }

    /**
     * 加载图片,这里需要注意的是默认加载的是全尺寸的,这样的话很容易导致OOM的发生,需要进行适当的压缩
     */
    private void loadNormal(Context ctx, final ImageConfig config) {
        Glide.with(ctx)
                .load(config.getUrl())
                .override(ProjectConfig.IMAGE_WIDTH, ProjectConfig.IMAGE_HEIGHT)
                .placeholder(config.getPlaceHolder())
                .into(new SimpleTarget<GlideDrawable>() {

                    @Override
                    public void onLoadFailed(Exception e, Drawable errorDrawable) {
                        config.getImageView().setImageResource(R.drawable.ic_icon_share_sp);
                    }

                    @Override
                    public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) {
                        config.getImageView().setImageDrawable(resource);
                    }
                });
    }


    /**
     * 加载缓存图片
     */
    private void loadCache(Context ctx, ImageConfig config) {
        Glide.with(ctx).using(new StreamModelLoader<String>() {
            @Override
            public DataFetcher<InputStream> getResourceFetcher(final String model, int i, int i1) {
                return new DataFetcher<InputStream>() {
                    @Override
                    public InputStream loadData(Priority priority) throws Exception {
                        throw new IOException();
                    }


                    @Override
                    public void cleanup() {

                    }

                    @Override
                    public String getId() {
                        return model;
                    }


                    @Override
                    public void cancel() {

                    }
                };
            }
        })
                .load(config.getUrl())
                .override(ProjectConfig.IMAGE_WIDTH, ProjectConfig.IMAGE_HEIGHT)
                .placeholder(config.getPlaceHolder())
                .diskCacheStrategy(DiskCacheStrategy.ALL)
                .into(config.getImageView());
    }
}

Fresco策略

public class FrescoImageLoaderProvider implements ImageLoaderProvider {

    private static volatile FrescoImageLoaderProvider sInstance;

    public static FrescoImageLoaderProvider getInstance() {
        if (sInstance == null) {
            synchronized (FrescoImageLoaderProvider.class) {
                if (sInstance == null) {
                    sInstance = new FrescoImageLoaderProvider();
                }
            }
        }
        return sInstance;
    }

    @Override
    public void loadImage(Context ctx, ImageConfig config) {

        //这里可以根据一些其他策略进行加载,比如wifi环境下加载或者不加载之类,具体业务决定

        DraweeController controller = Fresco.newDraweeControllerBuilder()
                .setUri(config.getUrl())
                .setAutoPlayAnimations(true)
                .setControllerListener(listener)
                .build();
        SimpleDraweeView simpleDraweeView = (SimpleDraweeView) config.getImageView();
        simpleDraweeView.setController(controller);
    }

    //图片加载监听
    ControllerListener listener = new BaseControllerListener() {

        /**
         * 当图片加载成功时会执行的方法
         * @param id
         * @param imageInfo
         * @param animatable
         */
        @Override
        public void onFinalImageSet(String id, Object imageInfo, Animatable animatable) {
            super.onFinalImageSet(id, imageInfo, animatable);
        }


        /**
         * 图片加载失败时调用的方法
         * @param id
         * @param throwable
         */
        @Override
        public void onFailure(String id, Throwable throwable) {
            super.onFailure(id, throwable);
        }


        /**
         *  如果加载的图片使用渐进式,这个方法将会被回调
         */
        @Override
        public void onIntermediateImageFailed(String id, Throwable throwable) {
            super.onIntermediateImageFailed(id, throwable);
        }
    };
}

环境类

public class ImageLoaderContext {
    private ImageLoaderProvider mImageLoaderProvider;

    public void setImageLoaderProvider(ImageLoaderProvider mImageLoaderProvider) {
        this.mImageLoaderProvider = mImageLoaderProvider;
    }

    public void loadImage(Context ctx, ImageConfig img) {
        mImageLoaderProvider.loadImage(ctx, img);
    }

    //一些简单的配置
    public void loadImg(Context context, ImageView imageView, String url) {
        ImageConfig config = new ImageConfig.Builder()
                .imgView(imageView)
                .url(url)
                .strategy(1)//比如说默认是大图
                .placeHolder(R.drawable.ic_back_icon_sp)
                .build();
        loadImage(context, config);
    }
}

网友分享的实战案例

  1. 一个案例搞定策略模式
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值