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

Posted mo_weifeng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式实战策略模式:原理篇相关的知识,希望对你有一定的参考价值。

前言

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

小明是这样写的:

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();
        simpleDrawee

以上是关于设计模式实战策略模式:原理篇的主要内容,如果未能解决你的问题,请参考以下文章

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

策略(Strategy)模式

设计模式之策略模式

Java实战应用50篇-SSM框架中的设计模式:动态代理

Java实战应用50篇-SSM框架中的设计模式:动态代理

深入理解设计模式-策略模式(结合简单工厂反射Spring详细讲解)