前言
小明在公司负责开发网约车应用,应用可以呼叫出租车和私家车两款车。乘客打车时要显示预计价格,而价格通过里程来计算。
小明是这样写的:
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源码中很典型的策略模式。
还有其它常见的:
- 通过设置不同的Adapter(即不同的策略),我们就可以写出符合我们需求的ListView布局
- 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);
}
}