设计模式学习02—工厂模式

1、动机与定义

     我们在程序中使用一个对象时,需要new一下,如果需要设置其他值就再初始化一下。比如我要使用一个按钮,手动new一个矩形按钮,然后初始化一些值,如显示文字,背景色等。
        // 矩形按钮
        IButton btn = new RecButton();
        // 初始化其他值
        btn.setText("提交");
        btn.setBackgroundColor("#00aaff");
        // 其他初始化省略

        // 圆形按钮
        IButton btn2 = new RoundButton();
        btn.setText("关于");
        btn.setBackgroundColor("#00aaff");
        // 其他初始化省略
     这样写有几个缺点:
     1、写一次没有问题,如果需要100个,就要写100次,太麻烦了。
     2、很多设置是重复的,比如例子中的背景色,一个系统可能风格统一,只有几种背景色,不需要每次都手动设置。
     3、耦合性太强,客户端必须知道具体的按钮创建过程,必须会创建按钮才行,后续按钮的方法改变,客户端也可要跟着修改,如以后按钮必须设置大小了,所有客户端代码都要变动。
     4、重复对象没有控制,比如btn2的关于,可能每个页面都有,但是每个页面的这个按钮都是一模一样的,没必要每次都创建一遍。
     5、没有封装变化,假如写了100个new RoundButton,后续这个按钮发生改变了,我们要改100处代码。
     等等,如果你再仔细想想,各种各样的情况下都有各种各样的缺点(当然这么写也有优点的,至少简单嘛,如何设计没有最好,只有合适的),那么我们有没有其他方式来规避这些问题呢?其实我们需要一个对象时,除了自己new之外,还有就是从其他地方获取,我们完全可以把这些按钮的创建过程放到一起,客户端使用的时候直接获取就行了。比如下面代码:
public class RoundButtonFactory implements Creater {

    public static IButton createButton(String text) {
        // 圆形按钮
        IButton btn = new RoundButton();
        btn.setText(text);
        btn.setBackgroundColor("#00aaff");
        return btn;
    }
}
      工厂模式定义:定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂方法使一个类的实例化延迟到其子类。
     客户 端使用时,只需要调用createButton就行,屏蔽了底层的具体实现,后续实现类变化了,只需要修改这个方法就行了;也使客户端和具体的按钮实现类解耦开来,其实这就是最基本的工厂模式。
     简单说就是将要使用的对象抽象出一个接口(产品),还有一个接口的创建工厂,每个具体实现类(产品的实现类)的创建由工厂的实现类创建。

2、结构与类图

     工厂模式通用类图如下:

     上面举的例子的类图如下:

     工厂方法包含四个角色:
     1、抽象产品(Product):负责定义产品公有属性;
     2、具体产品(ConcreteProduct):具体的产品实现类;
     3、抽象工厂(Creater):抽象的创建类,也就是抽象工厂;
     4、具体工厂(ConcreteCreater):具体创建者,也就是具体工厂,负责具体产品实现类的创建。

3、适用场景及效果(优缺点)

     没有工厂的时候,假如我们要做饭,需要用到火,创建火的同时发现需要用到木柴,还要创建一个锯来锯木柴......代码如下:
        // 创建锯
        Saw saw = new Saw ();
        // 使用锯,锯木柴
        FireWood fw = saw.cut();
        // 使用木柴创建火
        Fire fire = new WoodFire(fw);
      可以看到,这样的话,做饭的逻辑就依赖了锯、木柴、火等东西,如果使用工厂呢
        Fire fire = WoodFireFactroy.create();
     1、具有良好的封装性,逻辑代码清晰,不用new了,不用初始化了,只需要简单的get或者create就行了。
     2、耦合性低,工厂模式是典型的解耦框架,屏蔽了具体产品类(都是用产品接口嘛),调用者无需关心底层如何实现,产品类变化时只要接口不变就不影响调用者,使客户端和具体产品解耦,更能屏蔽创建具体产品时需要的其他关联类(如做饭就不需要依赖锯了),符合迪米特法则,只和需要的类交流,也符合依赖导致原则,只依赖抽象,更符合里氏替换原则,使用产品子类代替父类,完全没问题。
     3、扩展非常方便,新加一类产品时(如上面例子新增一种按钮),只需要新增一个工厂实现类即可。无需修改原有代码,达到了“拥抱变化”,符合开闭原则。
     当产品创建简单,比较固定的时候,或者调用者个性化情况太多时,工厂就体现不出他的优势了,比如常用的List,我们就没必要弄个ListFactory.createArrayList(),徒增代码复杂性。还有种类比较固定,不会太多,没必要抽象出接口,那也不必非用工厂模式。最好在下面的情况下才考虑使用工厂模式:
     1、调用者不需要直到具体的产品创建过程时;
     2、调用者使用的对象存在变动的可能,甚至完全不知道使用哪个具体对象时。
     3、需要做出灵活、可扩展的功能时,再考虑工厂模式,其实不一定所有功能都要做到可扩展,谨慎过度设计。
     4、需要解耦时,减少调用者和具体实现类的依赖时。

4、示例和扩展

     1、退化成简单工厂模式,当要创建的产品种类较少时,并且可以预见时,可以把工厂实现类合并到一起,对外提供一个静态工厂方法,比如上面的按钮例子中:
public static IButton crateButton(String type, String text) {
        IButton btn = null;
        if ("round".equals(type)) {
            btn = new RoundButton(); // 圆形按钮
        } else if ("rec".equals(type)) {
            btn = new RecButton(); // 矩形按钮
        } else {
            return null ;
        }

        btn.setText(text);
        btn.setBackgroundColor("#00aaff");
        return btn;
    }
        这种简单工厂模式用起来非常简单,缺点是扩展困难,不符合开闭原则,要注意设计没有最好,只有适不适合,在可预见的变化下,简单工厂模式非常好用。
     2、 约束产品类实例数量,通常和其他模式组合能达到很多效果,比如使用工厂创建好对象后缓存起来,达到单例或多例的目的,比如下面单例工厂:
//单例工厂
public class SingletonFactory {

    private static Map<Class<?>, Object> objCache = new HashMap<Class<?>, Object>();

    public synchronized static Object getInstance(Class<?> clazz) throws Exception {
        Object singleton = objCache.get(clazz);
        if (singleton == null) {
            singleton = createInstance(clazz);
            objCache.put(clazz, singleton);
        }
        return singleton;
    }

    private static Object createInstance(Class<?> clazz) throws Exception {
        Constructor ct = clazz.getDeclaredConstructor();
        ct.setAccessible( true);
        return ct.newInstance();
    }
}
      3、多工厂协调,工厂模式中,一个工厂创建一个产品也行,创建多个产品也行,当产品种类过多时,如果工厂类也较多,此时最好弄一个协调类来协调,方便调用者使用,而不是让调用者逐个去找工厂类。
     单例 可以,多例也就没问题了,比如数据库连接池,设置最大100个,使用工厂模式就很有效,此时需要考虑每个实例的状态,使用中的话不能被获取等等。
     4、延迟实例化,有的时候产品创建和销毁比较耗费资源,可以考虑创建好之后缓存起来,用完之后不销毁,或者使用完毕后将对象改成初始状态,而不是重新创建,方便后续使用,还是连接池的例子,如果用完了,是不销毁的,还会重新使用。
     5、结合反射或配置文件,代替程序new。虽然例子中我们使用的是new创建对象,但是在现实编程中,大部分工厂都是配合反射来使用的,可以考虑将要创建的产品属性,设置工厂属性放到配置文件中,程序启动就将对象创建好,这样当增加一个简单产品时,可以做到修改配置文件即可,就算增加复杂产品,只需要新写一个工厂类,配置配置就行,而不用大量修改源码。
     结束语,工厂方法其实在项目中使用非常非常频繁,这个模式几乎人尽皆知,但却不是每个人都能用好,工厂模式通常和其他模式混合使用,变化出无穷的优秀设计。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值