java/android 设计模式学习笔记(3):工厂方法模式

这篇来介绍一下工厂方法模式(Factory Method Pattern),在实际开发过程中我们都习惯于直接使用 new 关键字用来创建一个对象,可是有时候对象的创造需要一系列的步骤:你可能需要计算或取得对象的初始设置;选择生成哪个子对象实例;或在生成你需要的对象之前必须先生成一些辅助功能的对象,这个时候就需要了解该对象创建的细节,也就是说使用的地方与该对象的实现耦合在了一起,不利于扩展,为了解决这个问题就需要用到我们的工厂方法模式,它适合那些创建复杂的对象的场景,工厂方法模式也是一个使用频率很高的设计模式
PS:对技术感兴趣的同鞋加群544645972一起交流。

设计模式总目录

java/android 设计模式学习笔记目录

特点

工厂方法模式(Factory Method Pattern)定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个,工厂方法让类把实例化推迟到子类,这样的设计将对象的创建封装其来,以便于得到更松耦合,更有弹性的设计。
工厂方法模式是创建型设计模式之一,是结构较为简单的一种模式,在我们平时的开发过程中应用也是非常的广泛,比如 ArrayList,HashSet,与 Iterator 之间就能算是一种工厂方法。
简单工厂模式(Simple Factory)是工厂方法模式的一种,工厂方法模式的特点总结一下:

  • 简单工厂模式从某种意义上来说不算是真正的设计模式,但仍不失为一个简单的方法,可以将客户程序从具体类中解耦;
  • 工厂方法模式使用继承,把对象的创建委托给子类,子类实现工厂方法来创建对象,也就是说允许将实例化延迟到子类进行;;
  • 工厂方法模式是一个非常典型的“针对抽象编程,而不是具体类编程”例子。

UML类图

这里写图片描述
上图为工厂方法模式的uml类图,几个角色的分工也很明确,主要分为四大模块:

  • 一是抽象工厂接口,其为工厂方法模式的核心,它定义了一个工厂类所具备的基本行为;
  • 二是具体工厂,其实现了具体的业务逻辑;
  • 三是抽象产品接口,它定义了所有产品的公共行为;
  • 四是具体产品,为实现抽象产品的某个具体产品的对象。

简单工厂模式和工厂方法模式的区别就在于简单工厂模式将抽象工厂接口这个角色给精简掉了,是工厂方法模式的一个弱化版本。
从这种设计的角度来思考,工厂方法模式是完全符合设计原则的,它将对象的创建封装起来,以便于得到更松耦合,更有弹性的设计,而且工厂方法模式依赖于抽象的接口,将实例化的任务交给子类去完成,有非常好的可扩充性。

示例与源码

我们以一个简单的玩具工厂为例,工厂中生产小孩的玩具,女生的玩具和男生的玩具,先写一个 IToy 的抽象产品接口用来定义玩具的基本行为模式,然后实现该接口生成几个玩具的具体产品类 ChildrenToy,MenToy 和 WomenToy 类:
IToy.class

public interface IToy {
    /**
     * 名字
     */
    String getName();

    /**
     * 价格
     */
    float price();

    /**
     * 玩
     */
    void play();
}

ChildrenToy.class

public class ChildrenToy implements IToy{
    @Override
    public String getName() {
        return "toy car";
    }

    @Override
    public float price() {
        return 10.5f;
    }

    @Override
    public void play() {
        Log.e("play", "a child is playing a toy car");
    }
}

MenToy.class

public class MenToy implements IToy{
    @Override
    public String getName() {
        return "PS4";
    }

    @Override
    public float price() {
        return 2300;
    }

    @Override
    public void play() {
        Log.e("play", "a man is playing GTA5 on ps4");
    }
}

WomenToy.class

public class WomenToy implements IToy{
    @Override
    public String getName() {
        return "plush toy";
    }

    @Override
    public float price() {
        return 200;
    }

    @Override
    public void play() {
        Log.e("play", "a woman is playing with a plush toy");
    }
}

完成产品的两个角色之后,接下来要定义工厂类的两个角色,根据工厂方法模式和简单工厂模式的不同,可以有两种不同的写法:

工厂方法

工厂方法模式需要先写出一个工厂类的抽象接口来定义行为,这个时候根据实际情况我们可以分为两种实现方式,第一种写法会有多个 ConcreteFactory 的角色;第二种写法只会有一个 ConcreteFactory 的角色,根据传入的参数不同而返回不同的产品对象:

multi ConcreateFactory

IToyCreator.class

public interface IToyCreator {
    /**
     * 生产玩具
     */
    IToy createToy();
}

ChildrenToyCreator.class

public class ChildrenToyCreator implements IToyCreator {
    private static final String TAG = "ChildrenToyCreator";

    @Override
    public IToy createToy() {
        IToy toy = new ChildrenToy();
        Log.e(TAG, "buy a/an " + toy.getName()+" for " + toy.price() + " yuan, and then ---");
        toy.play();
        return toy;
    }
}

MenToyCreator.class

public class MenToyCreator implements IToyCreator  {
    private static final String TAG = "MenToyCreator";

    @Override
    public IToy createToy() {
        IToy toy = new MenToy();
        Log.e(TAG, "buy a/an " + toy.getName()+" for " + toy.price() + " yuan, and then ---");
        toy.play();
        return toy;
    }
}

WomenToyCreator.class

public class WomenToyCreator implements IToyCreator  {
    private static final String TAG = "WomenToyCreator";

    @Override
    public IToy createToy() {
        IToy toy = new WomenToy();
        Log.e(TAG, "buy a/an " + toy.getName()+" for " + toy.price() + " yuan, and then ---");
        toy.play();
        return toy;
    }
}

最后直接可以根据需要创建不同的 IToy 对象了,测试代码如下:

IToyCreator toyCreator;
switch (v.getId()) {
    case R.id.btn_child:
        toyCreator = new ChildrenToyCreator();
        toyCreator.createToy();
        break;
    case R.id.btn_men:
        toyCreator = new MenToyCreator();
        toyCreator.createToy();
        break;
    case R.id.btn_women:
        toyCreator = new WomenToyCreator();
        toyCreator.createToy();
        break;
}

single ConcreteFactory

IToyCreator.class

public interface IToyCreator {
    /**
     * 生产玩具
     */
    <T extends IToy> IToy createToy(Class<T> clazz);
}

ConcreteToyCreator.class

public class ConcreteToyCreator implements IToyCreator{
    private static final String TAG = "ConcreteToyCreator";

    @Override
    public <T extends IToy> IToy createToy(Class<T> clazz) {
        if (clazz == null){
            throw new IllegalArgumentException("argument must not be null");
        }
        try {
            IToy toy = clazz.newInstance();
            Log.e(TAG, "buy a/an " + toy.getName()+" for " + toy.price() + " yuan, and then ---");
            toy.play();
            return toy;
        } catch (Exception e) {
            throw new UnknownError(e.getMessage());
        }
    }
}

这种写法直接传入一个 Class 对象,接着利用反射的方式进行对象的创建,可以说从某种意义上精简了很多的工厂实现类,不用一个具体产品类就对应需要一个具体工厂类了,下面为测试代码:

IToyCreator toyCreator;
switch (v.getId()) {
    case R.id.btn_child:
        toyCreator.createToy(ChildrenToy.class);
        break;
    case R.id.btn_men:
        toyCreator.createToy(MenToy.class);
        break;
    case R.id.btn_women:
        toyCreator.createToy(WomenToy.class);
        break;
}

总结对比

以上的两种方式当然最后都能够成功打印出正确的结果:
这里写图片描述
单个工厂实现类的方法对比前面的多个工厂实现类的方法来说更加的简洁和动态,而且对于以后新增的产品类来说也能够不用修改原来的代码,符合开闭原则,但是这种写法在某些情况下是不适用的,比如不同的 IToy 对象设置了不同的构造函数,参数都不一样,用反射来实现就不适用了,这个时候就只能为每一个具体产品类都定义一个对应的具体工厂类了

简单工厂

同样是上面的代码,具体工厂实现类只有一个的时候,我们还是为工厂提供了一个抽象类,那么,如果将 IToyCreator 这个角色精简掉,只留下 ConcreteToyCreator 的这个角色,将其中的产品生成方法设置为静态应该也是没问题的:

public class ToyCreator{
    private static final String TAG = "ToyCreator";

    public static <T extends IToy> IToy createToy(Class<T> clazz) {
        if (clazz == null){
            throw new IllegalArgumentException("argument must not be null");
        }
        try {
            IToy toy = clazz.newInstance();
            Log.e(TAG, "buy a/an " + toy.getName()+" for " + toy.price() + " yuan, and then ---");
            toy.play();
            return toy;
        } catch (Exception e) {
            throw new UnknownError(e.getMessage());
        }
    }
}

像这样的方式就称为简单工厂模式,上面也说过,是工厂方法模式的一个弱化版本,缺点就是失去了被子类继承的特性,所有的压力都集中在工厂类中,不利于维护。

总结

总的来说,工厂方法模式是一个很好的设计模式,它遵循了一个“尽可能让事情保持抽象”的原则,松耦合的设计原则也能够很好的符合开闭原则,将类的实例化推迟到子类,同时也摈弃了简单工厂模式的缺点。
但是同时工厂方法模式也有一些缺点,每次我们为工厂方法添加新的产品时就要编写一个新的产品类,同时还要引入抽象层,当产品种类非常多时,会出现大量的与之对应的工厂对象,这必然会导致类结构的复杂化,所以对于简单的情况下,使用工厂方法模式就需要考虑是不是有些“重”了。

创建型模式 Rules of thumb

有些时候创建型模式是可以重叠使用的,有一些抽象工厂模式原型模式都可以使用的场景,这个时候使用任一设计模式都是合理的;在其他情况下,他们各自作为彼此的补充:抽象工厂模式可能会使用一些原型类来克隆并且返回产品对象。
抽象工厂模式建造者模式原型模式都能使用单例模式来实现他们自己;抽象工厂模式经常也是通过工厂方法模式实现的,但是他们都能够使用原型模式来实现;
通常情况下,设计模式刚开始会使用工厂方法模式(结构清晰,更容易定制化,子类的数量爆炸),如果设计者发现需要更多的灵活性时,就会慢慢地发展为抽象工厂模式原型模式或者建造者模式(结构更加复杂,使用灵活);
原型模式并不一定需要继承,但是它确实需要一个初始化的操作,工厂方法模式一定需要继承,但是不一定需要初始化操作;
使用装饰者模式或者组合模式的情况通常也可以使用原型模式来获得益处;
单例模式中,只要将构造方法的访问权限设置为 private 型,就可以实现单例。但是原型模式的 clone 方法直接无视构造方法的权限来生成新的对象,所以,单例模式原型模式是冲突的,在使用时要特别注意。

源码下载

https://github.com/zhaozepeng/Design-Patterns/tree/master/FactoryMethodPattern

引用

http://blog.csdn.net/jason0539/article/details/23020989
https://en.wikipedia.org/wiki/Factory_method_pattern
http://news.tuxi.com.cn/to/kf/satmaj/namtst.html

本系列:



相关文章

发表评论

Comment form

(*) 表示必填项

还没有评论。

跳到底部
返回顶部