前言
六大设计原则中有一条叫“开闭原则”,意思就是修改对外关闭,扩展对外开放,是啥意思呢?就是我写的产品功能,我不希望在扩展功能还要修改内部逻辑,而是可以通过扩展外部来实现功能的扩展,这么说可能并不是很好理解,举个例子吧,你实现了一个图片滤镜功能,这个滤镜可以把图片处理成黑白,你把图片处理的算法写在了功能内部,然后有一天,你又写了另一个滤镜可以把图片处理成素描样式,于是你想把这个滤镜算法加进去,那怎么区分是哪个滤镜呢,因为你算法都写在功能类内部,所以你只能通过if else or switch case来处理不同的滤镜,开始你感觉很好,我可以切换两种滤镜,慢慢的你发现,你写了几十个滤镜,你要维护一个有几十个if else的代码,很显然这不是一个好的方案,而这个时候,合理的使用设计模式去设计你的代码结构就显得非常重要,那应该怎么办呢,你可以使用策略模式或者工厂模式来提供不同的算法,这篇文章就介绍一下工厂方法模式如何在不修改内部的情况下对功能进行扩展,当然这也是简物中的项目实践
简物中的工厂方法模式
工厂模式分好几类,基础的有简单工厂模式/静态工厂模式,接着就是工厂方法模式,再应用更复杂的场景还有抽象工厂模式,不同的模式对于不同的应用场景是不一样的,那这里就暂时不对各种工厂模式进行介绍,重点聊聊简物中的工厂方法模式
简物中的应用场景
在前面我写了一篇简物中高仿Pinterest交互的实现思路的文章,在高仿Pinterest交互中,展开的图标大小是有共同属性的,图标都是具有icon、标题、like图标还有收藏和未收藏的值,后面甚至有考虑给图标加选中动画,而这一切我不希望把它捆绑在Pinterest交互内部去做,因为我要考虑以后更好的去扩展,所以我决定用工厂方法模式,因为这里就单一一个产品功能,不需要用到抽象工厂模式
那我们来看一下,菜单图标需要哪些属性呢?
从图上可以看到一些基本属性选中前图标、选中后图标、图标文字、还有图标的摆放顺序,当然还有动画,不过现在动画都是统一,内部调用暂未使用,那我们按照这些属性来给它设计一个接口模型
public interface IPinterestView {
int ITEM_SHOPPING_CART = 0x00010;
int ITEM_LIKE = 0x00020;
int LIKE = 1;
int CART = 2;
/**
* 菜单id
* @return
*/
int getItemId();
/**
* 菜单坐标
* @return
*/
int getImageIndex();
/**
* 正常图标资源
* @return
*/
int getImageResNormal();
/**
* 按下后图标资源
* @return
*/
int getImageResPress();
/**
* 菜单标题
* @return
*/
String getImageTitle();
/**
* 图标动画
* @return
*/
Animation getAnimation();
}
这个接口模型带有这些菜单的基本属性,当然也可以提供一些行为方法,供实现类去实现。我们现在有两个菜单,一个Like一个Cart,我们让这两个具体菜单实现这个产品模型接口,然后返回具体菜单参数,不过这里我先写一个菜单父类实现这个接口模型,把一些暂时不用待实现方法的或者需要封装的方法写上去,以减少子类复写的内容
public class PinterestView implements IPinterestView {
/**
* 主要用于Like菜单返回图标
*/
boolean like;
@Override
public int getItemId() {
return 0;
}
@Override
public int getImageIndex() {
return 0;
}
@Override
public int getImageResNormal() {
return 0;
}
@Override
public int getImageResPress() {
return 0;
}
@Override
public String getImageTitle() {
return null;
}
public boolean isLike() {
return like;
}
@Override
public Animation getAnimation() {
return null;
}
public PinterestView withLike(boolean like) {
this.like = like;
return this;
}
}
那我们可以来写Like菜单了
public class LikePinterestView extends PinterestView {
@Override
public int getItemId() {
return ITEM_LIKE;
}
@Override
public int getImageIndex() {
return IPinterestView.LIKE;
}
@Override
public int getImageResNormal() {
return isLike() ? R.mipmap.ic_unlike : R.mipmap.ic_like;
}
@Override
public int getImageResPress() {
return isLike() ? R.mipmap.ic_unlike_press : R.mipmap.ic_like_press;
}
@Override
public String getImageTitle() {
return isLike() ? "Unlike" : "Like";
}
}
Like菜单好了,那我们应该要有一个能生产这类菜单模型的工具,而可能生产这类模型的工具中不一定能完全用同一个工具,比如我们已经设计好了一个主板模型,已经可以按照这类主板模型生产的不同的主板,可是造主板的材料不一定是木质,可能是别的材料,那我们怎么办呢,我们可以设计一个造主板的抽象工具,让造不同的主板用不同的工具材料就好了
public interface PinterestViewFactory {
IPinterestView create();
}
那我们现在要生产Like菜单,可以写一个具体的生产类,而Like菜单是需要传参数的,所以我们可以在工厂里面设置这些参数
public class LikePinterestViewFactory implements PinterestViewFactory {
private boolean mIsLike;
@Override
public IPinterestView create() {
return new LikePinterestView().withLike(mIsLike);
}
public LikePinterestViewFactory withLike(boolean like){
this.mIsLike = like;
return this;
}
}
那我们Pinterest交互功能怎么用呢,我们只需要传入一个IPinterestView接口模型,然后内部功能调用接口方法就好了,不用操心具体实现类,你要改这个方法的参数,你在外面改,不要动我里面,你只要按照这个接口模型给我提供参数和具体方法执行就行,我给你修改扩展的权利,这就是“开闭原则”
new PinterestSelector.Builder()
...
.addITouchView(new LikePinterestViewFactory().withLike(getAdapter().getItem(position).isLike()).create())
...
.create()
.onTouch(v, event);
内部接收到了IPinterestView之后,接收参数,或者在功能运行过程中调用接口方法
IPinterestView mITouchView;
public void initParams(){
setVisibility(View.GONE);
mBaseDegree = 0;
mItemId = mITouchView.getItemId()
mNormalResId = mITouchView.getImageResNormal();
mPressResId = mITouchView.getImageResPress();
mTitle = mITouchView.getImageTitle();
mIndex = mITouchView.getImageIndex();
setImageResource(getNormalResId());
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(getIconWidth(),getIconHeight());
/**
* 高
*/
mHeight = Math.abs((int)(Math.sin(getAngle(getAngle())) * getR()));
/**
* 宽
*/
mWidth = Math.abs((int)(Math.cos(getAngle(getAngle())) * getR()));
mIndexX = getTouchX() - mWidth - getIconWidth() / 2;
mIndexY = getTouchY() - mHeight - getIconHeight() / 2 - getStatusBarHeight();
params.setMargins(getIndexX(), getIndexY(), 0, 0);
setLayoutParams(params);
}
看到没,内部功能的运行并没有直接和菜单的具体参数和运行方法挂钩,外部只要按照这个接口模型生产对应的菜单就好了,这就是多态的妙处
过了不久,我上线了一个购物车功能,我需要弹出的菜单有购物车,那怎么办?继续在外部生产呗,内部不做任何变动,Cart菜单来了
public class CartPinterestView extends PinterestView {
public CartPinterestView(){
super();
}
@Override
public int getItemId() {
return ITEM_SHOPPING_CART;
}
@Override
public int getImageIndex() {
return IPinterestView.CART;
}
@Override
public int getImageResNormal() {
return R.mipmap.ic_shopping_cart;
}
@Override
public int getImageResPress() {
return R.mipmap.ic_shopping_cart_press;
}
@Override
public String getImageTitle() {
return "Cart";
}
}
生产Cart
public class CartPinterestViewFactory implements PinterestViewFactory {
@Override
public IPinterestView create() {
return new CartPinterestView();
}
}
投入运转
@Override
public boolean onTouch(View v, MotionEvent event) {
if(!v.isClickable()){
return false;
}
new PinterestSelector.Builder()
.show((View)v.getParent())
.scroll(getBindId())
.backgroundColor("#f0ffffff")
.dialogMode()
/**
* 我在这里
*/
.addITouchView(new CartPinterestViewFactory().create())
.addITouchView(new LikePinterestViewFactory().withLike(getAdapter().getItem(position).isLike()).create())
.setOnLongClickListener(new PinterestSelector.OnLongClickListener() {
@Override
public void onLongClick(View v) {
handleLongClick();
}
})
.setOnCancelListener(new PinterestSelector.OnCancelListener() {
@Override
public void onCancel() {
}
@Override
public void onSyncCancel() {
handleSyncCancel();
}
})
.setOnItemSelectListener(new PinterestSelector.OnItemSelectListener() {
@Override
public void onItemSelect(int index) {
switch (index){
case IPinterestView.LIKE:
handleCollectSelect();
break;
case IPinterestView.CART:
handleSlideToCart();
break;
}
}
})
.create()
.onTouch(v, event);
return super.onTouch(v, event);
}
好了,杀青