设计模式实践-工厂模式
什么是工厂模式?什么时候用?
工厂模式,一般用于一系列类的创建,只要提供需要的参数给工厂对象,无需关注类实例的创建和配置,将类的实现隐藏。工厂模式又分为简单工厂模式、工厂方法模式和抽象工厂模式。 其中简单工厂模式不是23种设计模式之中,只是它比较容易理解,理解它会更加容易理解工厂方法和抽象工厂模式。
怎么实现简单工厂模式?
使用简单工厂模式,一般会这几个类
-
Factory,工厂类,负责创建所有产品实例,提供创建方法给外界调用获取产品实例。
-
IProduct,抽象产品的接口或抽象类。包含产品中公共的Api。
-
Product,具体产品实现类,实现了IProduct接口或继承IProduct抽象类。
简单工厂模式实践,多样式红点配置
红点样式
平时开发中,大部分的App都会有未读红点,而红点的样式并不止一种,列举常见的2种:
-
只有红点,没有内容的,小红点,表示有未读内容。
-
红点内有内容,内容可以是未读数量,也可以起是其他文字,例如NEW。
红点位置
- 上下左右,偏移多少等等
红点附着的View
-
底部BottomBar的Tab上
-
顶部TabLayout的Tab上
-
设置页面的Item上
-
还有一些什么+号的View上都可能有...这里不是重点,不扯远了
可能的问题
由于红点的样式多样和附着的地方也多样,那么如果每种都写一套,就会有些繁杂,而且配置的方式不一定一致,可能会有差异。
所以最好就是统一一套方案,具体方案还没研究出来,不过样式管理这里还是可以用工厂模式来管理的,下面一起来实践一下。
举一个TabLayout上红点的例子,项目中用的TabLayout库是MagicIndicator,用这个库原因是这个库拓展样式会比较方便,也提供了一些常见的样式,库不是重点,库只是提供的样式附着的Api,方便我们添加红点而已,具体样式库是不会管的。
实现步骤
大概流程:
通过红点样式工厂,传入样式类型,生产出对应的样式配置器,调用配置器的配置方法,将红点配置到依附View上。后续如果需要添加样式,需要在工厂中添加样式判断,增加样式配置类。
-
红点样式,一般用常量或枚举标识。这里我使用枚举。
-
红点样式工厂,根据红点样式枚举生产不同的样式配置器实例。
-
红点样式配置器接口和抽象基类,定义共性方法。
-
具体样式的配置器类,复写配置器方法,内部进行配置红点到依附View。
开始实践
- 红点样式枚举
public enum DotStyle {
/**
* 单个红点
*/
SINGLE_DOT,
/**
* 带内容红点
*/
WITH_CONTENT_DOT
}
- 红点样式工厂
/**
* 红点样式工厂
*/
private static class SimpleDotStyleFactory {
private DotStyle mStyle;
/**
* @param style 红点样式
*/
public SimpleDotStyleFactory(DotStyle style) {
mStyle = style;
}
/**
* 创建样式配置
*/
public DotStyleConfigurator create() {
//根据不同样式,实例对应的样式配置器
DotStyleConfigurator configurator = null;
if (mStyle == DotStyle.SINGLE_DOT) {
configurator = new SingleDotStyleConfigurator();
} else if (mStyle == DotStyle.WITH_CONTENT_DOT) {
configurator = new WithContentDotStyleConfigurator();
}
return configurator;
}
}
- 红点样式接口和抽象基类
/**
* 红点样式配置器接口,不同配置器实现该接口
*/
private interface DotStyleConfigurator {
/**
* 实现类在该方法中配置自己特有的样式
*
* @param badgeView 红点包裹View
* @param dotContent 红点内容
*/
void configuration(BadgePagerTitleView badgeView, String dotContent);
}
/**
* 基础配置器
*/
private static abstract static class BaseDotStyleConfigurator implements DotStyleConfigurator {
/**
* 设置红点在右边、中间居中
*/
protected void setEndBadgeRule(BadgePagerTitleView badgeView) {
Context context = badgeView.getContext();
//红点,左边、顶部的偏移量
int leftOffset = MMCUtil.dipTopx(context, 7f);
int topOffset = MMCUtil.dipTopx(context, 3f);
badgeView.setXBadgeRule(new BadgeRule(BadgeAnchor.CONTENT_RIGHT, leftOffset));
badgeView.setYBadgeRule(new BadgeRule(BadgeAnchor.CONTENT_TOP, topOffset));
}
/**
* 是否有红点
*/
protected boolean hasDot(String dotContent) {
return !(TextUtils.isEmpty(dotContent) || "0".equals(dotContent));
}
}
- 具体红点策略配置类
/**
* 单个红点、无数量的样式配置器
*/
private static class SingleDotStyleConfigurator extends BaseDotStyleConfigurator {
@Override
public void configuration(BadgePagerTitleView badgeView, String dotContent) {
if (hasDot(dotContent)) {
Context context = badgeView.getContext();
View dotView = LayoutInflater.from(context).inflate(R.layout.message_single_dot_view, null);
badgeView.setBadgeView(dotView);
//配置红点位置
setEndBadgeRule(badgeView);
//不自动去除红点
badgeView.setAutoCancelBadge(false);
} else {
badgeView.setBadgeView(null);
}
}
}
/**
* 带数量的红点样式配置器
*/
private static class WithContentDotStyleConfigurator extends BaseDotStyleConfigurator {
@Override
public void configuration(BadgePagerTitleView badgeView, String dotContent) {
if (hasDot(dotContent)) {
Context context = badgeView.getContext();
TextView dotView = (TextView) LayoutInflater.from(context).inflate(R.layout.message_with_content_dot_view, null);
dotView.setText(dotContent);
badgeView.setBadgeView(dotView);
//配置红点位置
setEndBadgeRule(badgeView);
//不自动去除红点
badgeView.setAutoCancelBadge(false);
} else {
badgeView.setBadgeView(null);
}
}
}
- 具体使用
//带红点的Layout,相当于包裹红点View的布局
BadgePagerTitleView badgeView = new BadgePagerTitleView(context);
//根据模型取出,红点内文字
String title = models.get(index).getTitle();
//根据模型取出,红点内容,如果为空,则为没有红点,红点是不显示的
String dotContent = models.get(index).getContent();
//红点样式
DotStyle dotStyle = models.get(index).getDotStyle();
//创建工厂,生产对应的红点样式策略配置
SimpleDotStyleFactory factory = new SimpleDotStyleFactory(dotStyle);
DotStyleConfigurator configurator = factory.create();
if (configurator != null) {
//开始配置红点
configurator.configuration(badgeView, dotContent);
}
简单工厂的优缺点
优点:外界调用方,无需了解产品的构建流程和逻辑,避免了直接创建实例的耦合。
缺点:工厂可以实例化的产品类型,在工厂中写死了,如果需要增加产品类型,就需要修改工厂类,违背了开放封闭原则。如果产品类型过多,会让工厂类膨胀。
简单工厂的使用场景:
产品不多,并且构建流程并不是特别复杂的情况。
工厂方法模式的改造
既然简单工厂对于红点样式过多而会修改工厂类,那么另外一个工厂方法模式,就可以解决这个问题。
如果使用工厂方法模式,相对于简单工厂模式有以下2点不同:
-
首先样式判断是必不可少的了,只是不放在工厂类中判断(挪个位置,分拆职责,工厂类不再管样式了)。
-
那么工厂类不管样式了,管什么呢?产品类型!只要给我一个具体产品类型,我就生产给你。那么怎么做呢?其实就是传入类型的Class,再使用反射,创建实例。
后面都是和简单工厂一样的了,就是获取到配置器实例,调用配置方法。
工厂方法的类结构
相比简单工厂,工厂类也被抽象了一层接口,不过也未必一定需要,如果工厂类比较复杂,适当将Api抽取到接口会比较清晰。
- 工厂类接口
/**
* 工厂接口
*/
private interface IDotStyleFactory {
/**
* 创建方法,传入配置器类的Class,返回配置器类实例
*
* @param clazz 配置器类的Class
*/
<T extends DotStyleConfigurator> T create(Class<T> clazz);
}
- 具体工厂类
private static class DotStyleFactory implements IDotStyleFactory {
@Override
public <T extends DotStyleConfigurator> T create(Class<T> clazz) {
try {
//反射创建子类
return clazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
- 最后使用
//带红点的Layout,相当于包裹红点View的布局
BadgePagerTitleView badgeView = new BadgePagerTitleView(context);
//根据模型取出,红点内文字
String title = models.get(index).getTitle();
//根据模型取出,红点内容,如果为空,则为没有红点,红点是不显示的
String dotContent = models.get(index).getContent();
//红点样式
DotStyle dotStyle = models.get(index).getDotStyle();
//创建工厂,根据不同的红点样式,创建配置器
DotStyleFactory factory = new DotStyleFactory();
DotStyleConfigurator configurator = null;
if (dotStyle == DotStyle.SINGLE_DOT) {
configurator = factory.create(SingleDotStyleConfigurator.class);
} else if (dotStyle == DotStyle.WITH_CONTENT_DOT) {
configurator = factory.create(WithContentDotStyleConfigurator.class);
}
if (configurator != null) {
configurator.configuration(badgeView, dotContent);
}
工厂方法模式的优缺点
优点:工厂类在拓展产品时,无需改动,外部增加if-else判断时,传入具体产品实现的class即可。这里我们的例子就是样式配置器工厂和样式配置器的子类。
缺点:
-
在调用处就暴露出了子类,而且只有一个,如果需要对产品进行装饰时,会将具体装饰逻辑暴露。
-
工厂方法和简单工厂模式是一对多的关系,就是一个工厂,对应多个产品,如果产品是一个类系,而类系中的产品有部分是不一样的。例如车系,车的加工厂好比是工厂类,车系A中,有A1,A2,A3,但是A1的引擎是普通的,A3则是性能比较高的引擎。
抽象工厂模式继续改造
由于工厂方法可能会将复杂的逻辑暴露,所以还是要将逻辑收回到工厂类中。对外依旧输出一个配置器类。
相比工厂方法模式,抽象工厂将工厂抽象,将一对多的关系转为一对一,就是一个工厂对应一个产品,这里我们就是一个红点样式工厂对应一个红点配置器。有以下2点需要改动:
-
抽象工厂类,不同的红点配置器一个工厂,继承该抽象工厂类。
-
声明不同样式的具体工厂类。
抽象工厂模式的类结构
- 抽象工厂
/**
* 抽象工厂
*/
private static abstract class AbsDotStyleFactory {
/**
* 创建配置器
*/
public abstract DotStyleConfigurator create();
}
- 具体工厂类实现
/**
* 单个红点的Tab红点样式工厂
*/
private static class SingleDotDotStyleFactory extends AbsDotStyleFactory {
@Override
public DotStyleConfigurator create() {
return new SingleDotStyleConfigurator();
}
}
/**
* 有内容的Tab红点样式工厂
*/
private static class WithContentDotDotStyleFactory extends AbsDotStyleFactory {
@Override
public DotStyleConfigurator create() {
return new WithContentDotStyleConfigurator();
}
}
- 具体使用
//带红点的Layout,相当于包裹红点View的布局
BadgePagerTitleView badgeView = new BadgePagerTitleView(context);
//根据模型取出,红点内文字
String title = models.get(index).getTitle();
//根据模型取出,红点内容,如果为空,则为没有红点,红点是不显示的
String dotContent = models.get(index).getContent();
//红点样式
DotStyle dotStyle = models.get(index).getDotStyle();
//根据红点样式,配置红点
AbsDotStyleFactory factory = null;
if (DotStyle.SINGLE_DOT == dotStyle) {
factory = new SingleDotDotStyleFactory();
} else if (DotStyle.WITH_CONTENT_DOT == dotStyle) {
factory = new WithContentDotDotStyleFactory();
}
if (factory != null) {
DotStyleConfigurator configurator = factory.create();
configurator.configuration(badgeView, dotContent);
}
总结
3种工厂模式,简单工厂和工厂方法、抽象工厂模式,越抽象,声明的子类越多,所以容易造成类很多的情况,所以具体使用哪种,需要按情况而定,如果很简单并不是很复杂,可以使用简单工厂和工厂方法模式。如果比较复杂则使用抽象工厂模式。