Android设计模式(2)——工厂方法模式

580 阅读10分钟

工厂方法模式定义

定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。 工厂方法模式的通用类图如下

在工厂方法模式中,抽象产品类Product负责定义产品的共性,事项对事物最抽象的定义,Creator为抽象创建类,也就是抽象工厂,具体如何创建产品类是由具体的实现工厂ConcreteCreator完成的。

代码示例:

抽象产品类

/**
 * 抽象产品类
 */
public abstract class Product {
    //产品类的公共方法
    public void method1(){}

    //抽象方法
    public abstract void method2();
}

具体产品类 具体的产品类可以有多个,都继承于抽象产品。

/**
 * 具体产品类1
 */
public class ConcreteProduct1 extends Product {
    @Override
    public void method2() {
       //业务逻辑处理
    }
}
/**
 * 具体产品类2
 */
public class ConcreteProduct2 extends Product {
    @Override
    public void method2() {
       //业务逻辑处理
    }
}

抽象工厂类负责定义产品对象的产生。

/**
 * 抽象工厂类
 */
public abstract class Creator {
    // 创建一个产品对象,其输入参数类型可以自行设置
    public abstract <T extends Product> T createProduct(Class<T> tClass) ;
}

具体如何产生一个产品的对象,是由具体产的工厂类实现的。

/**
 * 具体工厂类
 */
public class ConcreteCreator extends Creator {
    @Override
    public <T extends Product> T createProduct(Class<T> tClass) {
        Product product = null;
        try {
            product = (Product) Class.forName(tClass.getName()).newInstance();
        } catch (Exception e) {
            //异常处理
        }
        return (T)product;
    }
}

具体场景类

/**
 * 具体场景类
 */
public class FactoryClient {
    public static void main(String[] args){
        Creator creator = new ConcreteCreator();
        creator.createProduct(ConcreteProduct1.class);
    }
}

上诉代码是工厂方法模式通用代码,是一个比较实用、易扩展的框架。

工厂方法模式的优点

  • 良好的封装性,代码结构清晰。一个对象创建是由条件约束的,如一个调用者需要一个具体产品对象,只要知道这个产品的类名(或者约束字符串)就可以了,不用知道创建对象的过程,降低了模块间的耦合。
  • 具有很好的扩展性,在增加产品类的情况下,只要适当的修改具体的工厂类或扩展一个工厂类,就可以完成拥抱变化,
  • 屏蔽产品类。产品类的实现如何变化,调用者都不需要关心,它只需要关系产品的接口,只要接口保持不变,系统中的上层模块就不要发生变化。
  • 工厂方法是典型的解耦框架,高层模块只需要知道产品的抽象类,其他的实现都不需要关心,符合迪米特法则,我不需要就不要去交流,也符合依赖倒置原则,只依赖产品的抽象类,也符合里氏替换原则,使用子类替换父类。

工厂方法模式具体实现

女娲造人,类图如下

类图比较简单,AbstractHumanFactory是一个抽象类,定义了八卦炉具有的整体功能,HumanFactory为实现类,完成具体的任务---创造人类,Human接口人类的总称,其三个实现类分别为三类人,NvWa类是一个场景类,负责模拟这个场景,执行相关任务。

具体实现代码如下: 人类接口

/**
 * 人类接口
 */
public interface Human {
    /**
     * 不同的肤色
     */
    public void getColor();

    /**
     * 不同的语言
     */
    public void getLanguage();
}

接口Human是对人类的总称,每个人种都至少具有两个方法

黄种人

/**
 * 黄种人
 */
public class YellowHuman implements Human {
    @Override
    public void getColor() {
        System.out.println("黄种人皮肤是黄色的");
    }

    @Override
    public void getLanguage() {
        System.out.println("黄种人说汉语");
    }
}

白种人

/**
 * 白种人
 */
public class WhiteHuman implements Human {
    @Override
    public void getColor() {
        System.out.println("白种人皮肤是白色的");
    }

    @Override
    public void getLanguage() {
        System.out.println("白种人说英语");
    }
}

黑种人

/**
 * 黑种人
 */
public class BlackHuman implements Human {
    @Override
    public void getColor() {
        System.out.println("黑种人皮肤是黑色的");
    }

    @Override
    public void getLanguage() {
        System.out.println("黑种人说南非语");
    }
}

定义八卦炉,抽象人类创建工厂

/**
 * 抽象人类创建工厂
 */
public abstract class AbstractHumanFactory {
    public abstract <T extends Human> T createHuman(Class<T> tClass) ;
}

上述代码采用了泛型,通过定义泛型对createHuman的输入参数,产生了俩层限制

  • 必须是Class类型
  • 必须是Human的实现类

定义具体人类创建工厂

/**
 * 人类创建工厂类
 */
@SuppressWarnings("unchecked")
public class HumanFactory extends AbstractHumanFactory {
    @Override
    public <T extends Human> T createHuman(Class<T> tClass) {
        //定义一个生产的人中
        T human = null;
        try {
            human = (T) Class.forName(tClass.getName()).newInstance();
        } catch (Exception e) {
            System.out.println("人类创建失败");
        }
        return human;
    }
}

场景类

/**
 * 场景类
 */
public class NvWa {
    public static void main(String[] args){
        AbstractHumanFactory factory = new HumanFactory();
        
        //女娲第一次造人,火候不足,于是白种人诞生了
        System.out.println("--造出的第一批人是白种人--");
        Human whiteHuman = factory.createHuman(WhiteHuman.class) ;
        whiteHuman.getColor();
        whiteHuman.getLanguage();
        
        //女娲第二次造人,火候过足,于是黑种人诞生了
        System.out.println("--造出的第二批人是黑种人--");
        Human blackHuman = factory.createHuman(BlackHuman.class) ;
        blackHuman.getColor();
        blackHuman.getLanguage();
        
        //女娲第三次造人,火候刚刚好,于是黄种人诞生了
        System.out.println("--造出的第三批人是黄种人--");
        Human yellowHuman = factory.createHuman(YellowHuman.class) ;
        yellowHuman.getColor();
        yellowHuman.getLanguage();


    }
}
结果:
--造出的第一批人是白种人--
白种人皮肤是白色的
白种人说英语
--造出的第二批人是黑种人--
黑种人皮肤是黑色的
黑种人说南非语
--造出的第三批人是黄种人--
黄种人皮肤是黄色的
黄种人说汉语

人类的产生过程展现出来了,黑种人,白种人,黄种人都通过八卦炉创建出来,以上运用的就是工厂方法模式。

工厂方法模式的扩展

简单工厂模式

一个模块仅需要一个工厂类,没有必要把它生产处理啊,使用就静态方法就可以了。 根据这一要求,我们修改上面的例子。

修改之后的工厂类

/**
 * 人类创建工厂类
 */
@SuppressWarnings("unchecked")
public class HumanFactory {
    public static <T extends Human> T createHuman(Class<T> tClass) {
        //定义一个生产的人中
        T human = null;
        try {
            human = (T) Class.forName(tClass.getName()).newInstance();
        } catch (Exception e) {
            System.out.println("人类创建失败");
        }
        return human;
    }
}

修改之后的场景类

/**
 * 场景类
 */
public class NvWa {
    public static void main(String[] args){
        //女娲第一次造人,火候不足,于是白种人诞生了
        System.out.println("--造出的第一批人是白种人--");
        Human whiteHuman = HumanFactory.createHuman(WhiteHuman.class) ;
        whiteHuman.getColor();
        whiteHuman.getLanguage();
        
        //女娲第二次造人,火候过足,于是黑种人诞生了
        System.out.println("--造出的第二批人是黑种人--");
        Human blackHuman = HumanFactory.createHuman(BlackHuman.class) ;
        blackHuman.getColor();
        blackHuman.getLanguage();
        
        //女娲第三次造人,火候刚刚好,于是黄种人诞生了
        System.out.println("--造出的第三批人是黄种人--");
        Human yellowHuman = HumanFactory.createHuman(YellowHuman.class) ;
        yellowHuman.getColor();
        yellowHuman.getLanguage();
    }
}

运行结果没有发生变化,但是我们的类图变得简单了,而且调用者也比较简单,该模式是工厂方法模式的弱化,因为简单,所以称为简单工厂模式,也叫静态工厂模式,在实际项目中,采用该方法的案例还是比较多的,其缺点是工厂类的扩展比较困难,不符合开闭原则。

升级为多个工厂类

当我们在做一个比较复杂的项目时,经常会遇到初始化一个对象很耗费精力的情况,所有的产品类都放到一个工厂方法中进行初始化就变得结构不清晰,例如:一个产品类有五个具体实现,每个实现类的初始化的方法都不同,如果写在一个工厂方法中,势必会导致该方法巨大无比。考虑到需要结构清晰,我们就为每个产品定义一个创造者,然后由调用者自己去选择与哪个工厂方法关联。修改上面的例子如下:

每个人种都对应了一个创建者,每个创建者都独立负责对应的产品对象,非常符合单一职责原则。 代码示例:

抽象工厂类

/**
 * 抽象人工创建工厂
 */
public abstract class AbstractHumanFactory {
    public abstract Human createHuman() ;
}

白种人创建工厂实现类

/**
 * 白种人创建工厂实现类
 */
public class WhiteHumanFactory extends AbstractHumanFactory {
    @Override
    public Human createHuman() {
        return new WhiteHuman();
    }
}

黑种人创建工厂实现类

/**
 * 黑种人创建工厂实现类
 */
public class BlackHumanFactory extends AbstractHumanFactory {
    @Override
    public Human createHuman() {
        return new BlackHuman();
    }
}

黄种人创建工厂实现类

/**
 * 黄种人创建工厂实现类
 */
public class YellowHumanFactory extends AbstractHumanFactory {
    @Override
    public Human createHuman() {
        return new YellowHuman();
    }
}

场景类

/**
 * 场景类
 */
public class NvWa {
    public static void main(String[] args){
        //女娲第一次造人,火候不足,于是白种人诞生了
        System.out.println("--造出的第一批人是白种人--");
        Human whiteHuman = new WhiteHumanFactory().createHuman() ;
        whiteHuman.getColor();
        whiteHuman.getLanguage();

        //女娲第二次造人,火候过足,于是黑种人诞生了
        System.out.println("--造出的第二批人是黑种人--");
        Human blackHuman = new BlackHumanFactory().createHuman() ;
        blackHuman.getColor();
        blackHuman.getLanguage();

        //女娲第三次造人,火候刚刚好,于是黄种人诞生了
        System.out.println("--造出的第三批人是黄种人--");
        Human yellowHuman = new YellowHumanFactory().createHuman() ;
        yellowHuman.getColor();
        yellowHuman.getLanguage();
    }
}

运行结果还是相同,每一个产品类都对应一个创建类,好处就是创建类的职责清晰,而且结构简单,但是给可扩展性和可维护性带来了一定的影响,如果要扩展一个产品类,就需要建立一个相应的工厂类,这样就增加了扩展的难度,因为工厂类和产品类的数量相同,维护时就需要考虑两个对象之间的关系。当然,在复杂的应用中一般采取多工厂的方法,然后在增加一个协调类,避免调用者与各个子工厂直接交流,协调类的作用是封装工厂子类,对高层模块提供统一的访问接口。

代替单例模式

单例模式的核心要求就是内存中只有一个对象,通过工厂方法模式也可以只在内存中生产一个对象 类图如下:

类图比较简单,Singleton 定义了一个private午餐构造函数,目的是不允许通过new的方式创建一个对象。 代码示例:

单例类

/**
 * 单例类
 */
public class Singleton {
    private Singleton(){}
    public void doSomething(){
        //业务处理
    }
}

单例工厂类

/**
 * 单例工厂类
 */
public class SingletonFactory {
    private static Singleton mSingleton;

    static {
        try {
            Class cl = Class.forName(Singleton.class.getName());
            // 获取无参构造函数
            Constructor constructor = cl.getDeclaredConstructor();
            //设置无参构造是可以访问的
            constructor.setAccessible(true);
            //产生一个实例
            mSingleton = (Singleton) constructor.newInstance();
        } catch (Exception e) {
            //异常处理
        }
    }

    public static Singleton getSingleton() {
        return mSingleton;
    }
}

Singleton的对象是通过反射方式创建的,通过获取类构造器,然后设置访问权限,生成一个对象,然后提供外部访问,保证内存中的对象唯一。以上通过工厂方法模式创建一个单例对象,该框架可以继续扩展,所有需要产生单例的类都遵循一定的规则,然后通过该框架,只要输入一个类型就可以获得唯一的一个实例。

延迟初始化

一个对象被消费完毕后,并不立即释放,工厂类保持其初始化状态,等待被再次使用,延迟初始化是工厂方法模式一个扩展应用。类图如下:

ProductFactory负责产品类对象的创建工作,并且通过mMap变量产生一个缓存,对需要再次被重用的对象保留。代码示例

延迟加载的工厂类

/**
 * 延迟加载工厂类
 */
public class ProductFactory {
    private static final Map<String, Product> mMap = new HashMap<>();

    private static synchronized Product createProduct(String type) {
        Product product = null;
        //如果map中已经有这个对象了,直接赋值
        if (mMap.containsKey(type)) {
            product = mMap.get(type);
        } else {
            if (type.equals("product1")) {
                product = new ConcreteProduct1();
            } else {
                product = new ConcreteProduct2();
            }
            //创建对象之后把对象存到mMap中
            mMap.put(type, product);
        }
        return product;
    }
}

代码比较简单,通过定义一个Map容器,容纳所有产生的对象,如果Map容器已经有的对象,可以直接取出返回,如果没有,则根据需要的类型的产生一个对象并放入Map容器中,以方便下次调用。延迟加载框架是可以扩展的,例如限制某一类产品类的最大实例化数量,可以通过判断Map中已经有的对象数量来实现,这样处理是非常有意义的。

尾声

工厂方法模式在项目中使用的非常频繁,以至于很多代码中都包含工厂方法模式,该模式几乎人尽皆知,但不是每个人都能用的好,熟能生巧,熟练掌握该模式,多思考工厂模式如果应用,而且工厂方法模式还可以与其他模式混合使用,变化出无穷的优秀设计,这也是软件设计和开发的乐趣所在。

特别感谢

《设计模式之禅》

PS:觉得文章不还错的话可以添加公众号关注下,文章都会同步到公众号。