设计模式

设计模式实践-工厂模式

2020-10-19  本文已影响0人  h2coder

什么是工厂模式?什么时候用?

工厂模式,一般用于一系列类的创建,只要提供需要的参数给工厂对象,无需关注类实例的创建和配置,将类的实现隐藏。工厂模式又分为简单工厂模式、工厂方法模式和抽象工厂模式。 其中简单工厂模式不是23种设计模式之中,只是它比较容易理解,理解它会更加容易理解工厂方法和抽象工厂模式。

怎么实现简单工厂模式?

使用简单工厂模式,一般会这几个类

  1. Factory,工厂类,负责创建所有产品实例,提供创建方法给外界调用获取产品实例。

  2. IProduct,抽象产品的接口或抽象类。包含产品中公共的Api。

  3. Product,具体产品实现类,实现了IProduct接口或继承IProduct抽象类。

简单工厂模式实践,多样式红点配置

红点样式

平时开发中,大部分的App都会有未读红点,而红点的样式并不止一种,列举常见的2种:

  1. 只有红点,没有内容的,小红点,表示有未读内容。

  2. 红点内有内容,内容可以是未读数量,也可以起是其他文字,例如NEW。

红点位置

红点附着的View

  1. 底部BottomBar的Tab上

  2. 顶部TabLayout的Tab上

  3. 设置页面的Item上

  4. 还有一些什么+号的View上都可能有...这里不是重点,不扯远了

可能的问题

由于红点的样式多样和附着的地方也多样,那么如果每种都写一套,就会有些繁杂,而且配置的方式不一定一致,可能会有差异。

所以最好就是统一一套方案,具体方案还没研究出来,不过样式管理这里还是可以用工厂模式来管理的,下面一起来实践一下。

举一个TabLayout上红点的例子,项目中用的TabLayout库是MagicIndicator,用这个库原因是这个库拓展样式会比较方便,也提供了一些常见的样式,库不是重点,库只是提供的样式附着的Api,方便我们添加红点而已,具体样式库是不会管的。

实现步骤

大概流程:

通过红点样式工厂,传入样式类型,生产出对应的样式配置器,调用配置器的配置方法,将红点配置到依附View上。后续如果需要添加样式,需要在工厂中添加样式判断,增加样式配置类。

  1. 红点样式,一般用常量或枚举标识。这里我使用枚举。

  2. 红点样式工厂,根据红点样式枚举生产不同的样式配置器实例。

  3. 红点样式配置器接口和抽象基类,定义共性方法。

  4. 具体样式的配置器类,复写配置器方法,内部进行配置红点到依附View。

开始实践

  1. 红点样式枚举
public enum DotStyle {
    /**
     * 单个红点
     */
    SINGLE_DOT,
    /**
     * 带内容红点
     */
    WITH_CONTENT_DOT
}
  1. 红点样式工厂
/**
 * 红点样式工厂
 */
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;
    }
}
  1. 红点样式接口和抽象基类
/**
 * 红点样式配置器接口,不同配置器实现该接口
 */
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));
    }
}
  1. 具体红点策略配置类
/**
 * 单个红点、无数量的样式配置器
 */
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);
        }
    }
}
  1. 具体使用
//带红点的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点不同:

  1. 首先样式判断是必不可少的了,只是不放在工厂类中判断(挪个位置,分拆职责,工厂类不再管样式了)。

  2. 那么工厂类不管样式了,管什么呢?产品类型!只要给我一个具体产品类型,我就生产给你。那么怎么做呢?其实就是传入类型的Class,再使用反射,创建实例。

后面都是和简单工厂一样的了,就是获取到配置器实例,调用配置方法。

工厂方法的类结构

相比简单工厂,工厂类也被抽象了一层接口,不过也未必一定需要,如果工厂类比较复杂,适当将Api抽取到接口会比较清晰。

  1. 工厂类接口
/**
 * 工厂接口
 */
private interface IDotStyleFactory {
    /**
     * 创建方法,传入配置器类的Class,返回配置器类实例
     *
     * @param clazz 配置器类的Class
     */
    <T extends DotStyleConfigurator> T create(Class<T> clazz);
}
  1. 具体工厂类
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;
    }
}
  1. 最后使用
//带红点的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即可。这里我们的例子就是样式配置器工厂和样式配置器的子类。

缺点:

  1. 在调用处就暴露出了子类,而且只有一个,如果需要对产品进行装饰时,会将具体装饰逻辑暴露。

  2. 工厂方法和简单工厂模式是一对多的关系,就是一个工厂,对应多个产品,如果产品是一个类系,而类系中的产品有部分是不一样的。例如车系,车的加工厂好比是工厂类,车系A中,有A1,A2,A3,但是A1的引擎是普通的,A3则是性能比较高的引擎。

抽象工厂模式继续改造

由于工厂方法可能会将复杂的逻辑暴露,所以还是要将逻辑收回到工厂类中。对外依旧输出一个配置器类。

相比工厂方法模式,抽象工厂将工厂抽象,将一对多的关系转为一对一,就是一个工厂对应一个产品,这里我们就是一个红点样式工厂对应一个红点配置器。有以下2点需要改动:

  1. 抽象工厂类,不同的红点配置器一个工厂,继承该抽象工厂类。

  2. 声明不同样式的具体工厂类。

抽象工厂模式的类结构

  1. 抽象工厂
/**
 * 抽象工厂
 */
private static abstract class AbsDotStyleFactory {
    /**
     * 创建配置器
     */
    public abstract DotStyleConfigurator create();
}
  1. 具体工厂类实现
/**
 * 单个红点的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();
    }
}
  1. 具体使用
//带红点的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种工厂模式,简单工厂和工厂方法、抽象工厂模式,越抽象,声明的子类越多,所以容易造成类很多的情况,所以具体使用哪种,需要按情况而定,如果很简单并不是很复杂,可以使用简单工厂和工厂方法模式。如果比较复杂则使用抽象工厂模式。

上一篇 下一篇

猜你喜欢

热点阅读