设计模式-工厂模式(一)

工厂模式其实就是new一个对象,什么情况下应该考虑用工厂模式呢?

  1. new一个对象的时候要做很多的初始化工作,全部写在构造器里面会导致代码很臃肿.
  2. 当很多地方要new这个对象的时候,如果很多地方new了这个对象,突然要改一点,这时候如果用了工厂方法就会很庆幸了.
  3. 当客户端想要new 一个产品,但是这个类似产品很多(比如100个),构造很复杂,客户端压根就不想知道用哪个,怎么构造,反正有个可以用就行,这时客户端如果能根据少数几个参数就能得到想要的产品那就感觉很方便了.
    所以这时工厂干的事就是根据少数几个参数通过一定的逻辑确定返回哪个产品.

工厂模式是设计模式中最基础的,也是比较常用的设计模式,大致可分为3种.

1. 简单工厂模式
2. 工厂方法模式
3. 抽象工厂模式

先分析下简单工厂模式,先上类图有个宏观的了解.

Shape.png

代码如下:

  1. 先定义一个产品接口
/**
 * FileName :Shape
 * Author :zengzhijun
 * Date : 2018/5/18 14:34
 * Description:
 */
package com.byedbl.shape;

/**
 * 抽象方法
 * 定义形状的抽象
 * @author : zengzhijun
 * @date : 2018/5/18 14:34
 **/
public interface Shape {

    void draw();

    void erase();
}

  1. 再有两个实现类,分别为Circle 和 Square
    代码如下:
/**
 * FileName :Circle
 * Author :zengzhijun
 * Date : 2018/5/18 14:38
 * Description:
 */
package com.byedbl.shape;

/**
 * Shape 抽象的一个实现
 * @author : zengzhijun
 * @date : 2018/5/18 14:38
 **/
public class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("Circle draw");
    }

    @Override
    public void erase() {
        System.out.println("Circle erase");
    }
}
/**
 * FileName :Square
 * Author :zengzhijun
 * Date : 2018/5/18 14:36
 * Description:
 */
package com.byedbl.shape;

/**
 * 抽象的一个实现
 * @author : zengzhijun
 * @date : 2018/5/18 14:36
 **/
public class Square implements Shape{
    @Override
    public void draw() {
        System.out.println("Square draw");
    }

    @Override
    public void erase() {
        System.out.println("Square erase");
    }
}

  1. 然后定义一个工厂类负责创建
/**
 * FileName :ShapeStaticFactory
 * Author :zengzhijun
 * Date : 2018/5/18 14:40
 * Description:
 */
package com.byedbl.shape.factory.simple;

import com.byedbl.shape.Circle;
import com.byedbl.shape.Shape;
import com.byedbl.shape.Square;

/**
 * 创建形状的静态工厂
 * @author : zengzhijun
 * @date : 2018/5/18 14:40
 **/
public class ShapeStaticFactory {

    public static Shape create(int no) {
        //一般会改成反射来创建
        Shape shape ;
        switch (no){
            case 1:
                shape = new Square();
                break;
            case 2:
                shape = new Circle();
                break;
            default:
                shape = new Square();
                break;
        }
        return shape;
    }

}

  1. 客户端调用,代码如下:
package com.byedbl.shape;

import com.byedbl.shape.factory.simple.ShapeStaticFactory;
import org.junit.Test;
/**
 * <pre>
 * 本例为静态工厂方法(简单工厂模式)
 * 工厂方法能增加系统的扩展性,实现可插拔的效果
 *
 * 工厂模式其实就相当于创建实例对象的new
 *
 * 当有新的产品加入时,我们需要增加一个产品类继承自 {@link Shape} ,
 * 并且在 {@link ShapeStaticFactory} 中修改 {@link ShapeStaticFactory} 的 create 方法
 * 因为要修改工厂方法的代码,违反了开闭原则,这就是这个模式的缺点.而工厂方法模式就避免了这一点
 * </pre>
 * @author : zengzhijun
 * @date : 2018/5/18 14:48
 **/
public class ShapeStaticFactoryTest {


    @Test
    public void create() {
        Shape shape = ShapeStaticFactory.create(1);
        shape.draw();
        shape.erase();
        System.out.println("----------------------");
        shape = ShapeStaticFactory.create(2);
        shape.draw();
        shape.erase();

    }
}

  • 小结

工厂模式 提高了程序的扩展性.

简单工厂模式和其他两种工厂模式的主要区别在于工厂,简单工厂模式中没有对工厂抽象,直接在工厂中创建具体的对象.并且一般是用静态方法创建

该方法有个缺点,工厂违反了开闭原则,在需要新增一个产品的时候需要修改工厂类,工厂方法模式可以避免这一点


  • 工厂方法模式
    先上类图:
    Shape1.png

    工厂方法模式比上面的简单工厂模式多了3个类,分别是抽象工厂Factory,两个工厂实现类CircleFactory 和 SquareFactory
    代码如下:
/**
 * FileName :Factory
 * Author :zengzhijun
 * Date : 2018/5/18 15:14
 * Description:
 */
package com.byedbl.shape.factory.method;


import com.byedbl.shape.Shape;

/**
 * 定义抽象工厂方法
 * @author : zengzhijun
 * @date : 2018/5/18 15:14
 **/
public interface Factory {

    Shape create();

    void eat();
}

/**
 * FileName :CircleFactory
 * Author :zengzhijun
 * Date : 2018/5/18 15:16
 * Description:
 */
package com.byedbl.shape.factory.method;

import com.byedbl.shape.Circle;
import com.byedbl.shape.Shape;

public class CircleFactory implements Factory {
    @Override
    public Shape create() {
        return new Circle();
    }

    @Override
    public void eat() {
        System.out.println("Circle eat");
    }
}

/**
 * FileName :SquareFactory
 * Author :zengzhijun
 * Date : 2018/5/18 15:15
 * Description:
 */
package com.byedbl.shape.factory.method;

import com.byedbl.shape.Shape;
import com.byedbl.shape.Square;

public class SquareFactory implements Factory{
    @Override
    public Shape create() {
        return new Square();
    }

    @Override
    public void eat() {
        System.out.println("Square eat");
    }
}

客户端调用的代码如下:

package com.byedbl.shape.factory.method;

import com.byedbl.shape.Shape;
import org.junit.Test;
import org.springframework.util.ClassUtils;

/**
 * <pre>
 * 工厂方法模式的实现类
 * 工厂方法模式是在简单工厂的基础上将工厂也抽象出一个抽象类
 * 这样创建的工厂也实现了可插拔的效果
 * 同时也解决了简单工厂在增加产品时要修改工厂类的缺点
 *
 * 现在如果要增加一个产品,只需增加一个 {@link Shape} 接口的实现 和一个 {@link Factory}接口的实现
 *
 * 更进一步的,我们可以将 new CircleFactory() 这个过程改成反射获取,
 * 从而做到客户端无需任何修改,只要改环境配置就达到创建不同产品的效果
 *
 * @author : zengzhijun
 * @date : 2018/5/18 15:20
 **/
public class FactoryTest {

    @Test
    public void create() {

        Factory factory = new CircleFactory();
        Shape shape = factory.create();
        shape.draw();
        shape.erase();
        factory.eat();
        System.out.println("--------------");
        factory = new SquareFactory();
        shape = factory.create();
        shape.draw();
        shape.erase();
        factory.eat();
    }

    @Test
    public void createDynamic() throws ReflectiveOperationException {
        //这个值可以配置到外部文件中
        String factoryName = CircleFactory.class.getCanonicalName();
//        factoryName = SquareFactory.class.getCanonicalName();

        Class<?> clazz = ClassUtils.forName(factoryName,ClassUtils.getDefaultClassLoader());
        Factory factory = (Factory) clazz.newInstance();
        Shape shape = factory.create();
        shape.draw();
        shape.erase();
        factory.eat();

    }

}
  • 小结

工厂方法模式是在简单工厂的基础上将工厂也抽象出一个抽象类

更进一步的,我们可以将 new CircleFactory() 这个过程改成反射获取,
从而做到客户端无需任何修改,只要改环境配置就达到创建不同产品的效果

再进一步,实际的应用中我们可能会直接用一个可动态创建不同类的工厂,这样就不用这么多工厂类了,但是这样类图就看起来是简单工厂模式了,只是这个工厂很强大,一个顶n个。


下面分析 抽象工厂模式
还是先贴类图:

FactoryTest.png

简单说下抽象工厂模式解决的问题,上面这个类图据说是该模式创建的场景。
    假设不同操作系统下都有Button和Text控件,比如,Unix和Windows。
Text有两个实现分别是WindowsText和UnixText,同理Button也有两个不同的实现,如果按照上面工厂方法的套路,我们应该给Button和Text分别创建一个抽象工厂接口ButtonFactory和TextFacTory,以及不同环境的ButtonWindowsFactory,ButtonUnitFactory,TextWindowsFactory和 TextUnitFactory。这样工厂的类就有6个,
但是一般来讲我们在一个操作系统环境里面是只会有一套Button和Text,我们就会想可不可以把6个Factory合并呢?合并一下就诞生了 抽象工厂模式

代码如下:

  1. 创建 Button 和 Text 产品接口
/**
 * FileName :Button
 * Author :zengzhijun
 * Date : 2018/5/18 16:24
 * Description:
 */
package com.byedbl.system;

/**
 * 定义一个产品
 * 操作系统中的一个按钮
 * @author : zengzhijun
 * @date : 2018/5/18 16:24
 **/
public interface Button {

    void show();

}

/**
 * FileName :Text
 * Author :zengzhijun
 * Date : 2018/5/18 16:27
 * Description:
 */
package com.byedbl.system;

/**
 * 再定义一个产品
 * 系统的文本框
 * @author : zengzhijun
 * @date : 2018/5/18 16:27
 **/
public interface Text {

    void display();
}

  1. 创建不同环境的Button和Text实现类
/**
 * FileName :UnixButton
 * Author :zengzhijun
 * Date : 2018/5/18 16:25
 * Description:
 */
package com.byedbl.system;

/**
 * 具体的产品
 * Unix系统的按钮
 * @author : zengzhijun
 * @date : 2018/5/18 16:25
 **/
public class UnixButton implements Button{
    @Override
    public void show() {
        System.out.println("UnixButton show");
    }
}

/**
 * FileName :WindowsButton
 * Author :zengzhijun
 * Date : 2018/5/18 16:26
 * Description:
 */
package com.byedbl.system;

/**
 * 定义一个具体的产品
 * Windows操作系统的按钮
 * @author : zengzhijun
 * @date : 2018/5/18 16:26
 **/
public class WindowsButton  implements Button{
    @Override
    public void show() {
        System.out.println("WindowsButton show");
    }
}

/**
 * FileName :WindowsButton
 * Author :zengzhijun
 * Date : 2018/5/18 16:26
 * Description:
 */
package com.byedbl.system;

/**
 * 定义一个具体的产品
 * Windows操作系统的按钮
 * @author : zengzhijun
 * @date : 2018/5/18 16:26
 **/
public class WindowsButton  implements Button{
    @Override
    public void show() {
        System.out.println("WindowsButton show");
    }
}

/**
 * FileName :WindowsText
 * Author :zengzhijun
 * Date : 2018/5/18 16:29
 * Description:
 */
package com.byedbl.system;

public class WindowsText implements Text {
    @Override
    public void display() {
        System.out.println("WindowsText display");
    }
}

  1. 创建工厂接口和工厂实现类
/**
 * FileName :Factory
 * Author :zengzhijun
 * Date : 2018/5/18 16:29
 * Description:
 */
package com.byedbl.system;

/**
 * 定义工厂的抽象
 * 定义创建不同操作系统的按钮和文本框
 * @author : zengzhijun
 * @date : 2018/5/18 16:29
 **/
public interface Factory {

    Button createButton();

    Text createText();
}

/**
 * FileName :UnixFactory
 * Author :zengzhijun
 * Date : 2018/5/18 16:32
 * Description:
 */
package com.byedbl.system;

public class UnixFactory implements Factory {
    @Override
    public Button createButton() {
        return new UnixButton();
    }

    @Override
    public Text createText() {
        return new UnixText();
    }
}

/**
 * FileName :WindowsFactory
 * Author :zengzhijun
 * Date : 2018/5/18 16:33
 * Description:
 */
package com.byedbl.system;

public class WindowsFactory implements Factory {
    @Override
    public Button createButton() {
        return new WindowsButton();
    }

    @Override
    public Text createText() {
        return new WindowsText();
    }
}

4 . 客户端调用

package com.byedbl.system;

import org.junit.Test;

/**
 * <pre>
 * 抽象工厂模式
 * 抽象工厂模式解决的问题是:
 * </pre>
 * <ol>
 * <li> 系统中有多于一个的产品族,比如既有 {@link Button}又有 {@link Text}
 * <li> 系统只消费其中某一族产品,比如 Windows 只消费 {@link WindowsButton} 和 {@link WindowsText}
 *    这个是一个很重要的条件,如果不是某一族,比如可能同时消费 {@link WindowsButton} 和 {@link UnixButton} ,这个时候抽象工厂也没辙了,只能用工厂方法了.
 * </ol>
 * <pre>
 * 如果不用抽象工厂方法,而用工厂方法,那么 Button 就要对应有 ButtonFactory 工厂接口,下有
 * WindowsButtonFactory 和 UnixButtonFactory 两个工厂实现类,同理 Text 也要有 TextFactory
 * 工厂接口,下有WindowTextFactory 和 UnixTextFactory 两个工厂实现类;
 *
 * 而根据上面的前提2,消费者只能消费其中一族产品,那么我们就没必要创建这么多工厂了, WindowsButtonFactory 和
 *WindowTextFactory 合并成 WindowFactory; UnixButtonFactory 和 UnixTextFactory 合并成
 *  UnixFactory ;这样就省了一组工厂类,而产品越多,省得工厂类就更多,一套一套的省.
 *
 *
 * 更进一步:
 * <ol>
 * <li> 如果Factory 动态反射获取,那么客户端就可以完全不修改而启用的产品族
 * <li> 如果 DynamicFactory 可以利用反射和配置动态创建不同环境的 {@link Button} 和 {@link Text},那么就不用 {@link WindowsFactory} 和 {@link UnixFactory} 了,这样就可以解决消费者在Windows情况下要
 *      UnixText的情况了,但是还是不能解决在Windows情况下既有{@link WindowsButton}又有 {@link UnixButton} 的问题,所以说
 *      设计模式都是解决特定问题的方案.
 * </pre>
 * @author : zengzhijun
 * @date : 2018/5/18 16:36
 **/
public class FactoryTest {

    @Test
    public void create() {
        Factory factory = new WindowsFactory();
        Button button = factory.createButton();
        Text text = factory.createText();
        button.show();
        text.display();

        System.out.println("-----------");
        factory = new UnixFactory();
        button =factory.createButton();
        text = factory.createText();
        button.show();
        text.display();


    }
}
  • 小结

抽象工厂方法之所以能将本来要两套的工厂合并是因为一个前提:
消费者一般只会消费其中一组产品。


  • 工厂方法在实例化泛型中的应用

我们都知道 Java 的泛型是采用类型擦除来实现的(在 javac 编译过程的中把泛型去掉,加上强制类型转换)。所以我们不能直接 new T()来实例化一个对象。其实可以采用工厂方法模式设计模式来解决。假设我们有一个类,里面要用到了泛型的实例化。

public class Foo<T>(){
    private T t;
    public Foo(){
       t = new T(); // 这个代码是有问题的,我们使用工厂设计模式进行改进
    }
}

我们给出工厂接口如下:

public interface IFactory<T>(){
   T create();
}

进而我们可以采用如下的方法进行改进

public class Foo<T>(){
    private T t;
    public <F extends IFactory<T>> Foo(F factory){
        // t = new T(); 
        factory.create();       
    }
}

这个时候,我们可以采用如下的方式实例化 Foo

new Foo(new Ifactory<Integer>(){
        Integer create(){
            return new Integer(0);
        }
    });

    new Foo(new Ifactory<String>(){
        String create(){
            return "Hello";
        }
    });
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,651评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,468评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,931评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,218评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,234评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,198评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,084评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,926评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,341评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,563评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,731评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,430评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,036评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,676评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,829评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,743评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,629评论 2 354

推荐阅读更多精彩内容