设计模式 --工厂方法模式(FACTORY METHOD)


什么是工厂方法模式?
定义:定义一个用于创建对象的接口,让子类决定实例化哪一个类。
工厂方法使一个类的实例化延迟到其子类。

通用类图如下:


工厂模式类图.png

工厂方法模式通用代码:
①产品抽象类:负责定义产品共性

public abstract class Product {
   //产品的公共方法
   public void method1(){
      //业务逻辑处理代码
   }
   //其他抽象方法
   public abstract void method2();
}

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

public class ConcreteProduct1 extends Product {  //具体产品实现类1
   @Override
   public void method2() {
      //业务逻辑处理代码
   }
}

public class ConcreteProduct2 extends Product { //具体产品实现类2
   @Override
   public void method2() {
      //业务逻辑处理代码
   }
}

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

public abstract class AbstractFactory {
   /**
    * 创建一个产品对象,其输入参数可以自行设置
    * 输入参数:通常为String,Enum,Class,也可以为空
    */
   public abstract <T extends Product>T createProduct(Class<T> c);
}

④具体的工厂实现类:定义具体如何产生一个产品对象

public class ConcreteFactory extends AbstractFactory {
   @Override
   public <T extends Product>T createProduct(Class<T> c) {
      Product product= null;
      try {
         product =(Product) Class.forName(c.getName()).newInstance();
      } catch (InstantiationException e) {
         e.printStackTrace();
      } catch (IllegalAccessException e) {
         e.printStackTrace();
      } catch (ClassNotFoundException e) {
         e.printStackTrace();
      }
      return (T)product;
   }
}

⑤场景类:调用相关方法

public class Client {
   public static void main(String[] args) {
      AbstractFactory factory = new ConcreteFactory();
      Product product = factory.createProduct(ConcretepProduct1.class);
   }
}

工厂方法模式的优点:
①良好的封装性,代码结构清晰
②优秀的扩展性
③屏蔽产品类,很强的灵活性
④解耦性强

工厂方法模式的使用场景:
①工厂方法模式是new方法的替代品,在所有需要生成对象的地方都可以使用,但需要谨慎的考虑是否要增加一个工厂类进行管理,防止增加代码复杂度。
②需要灵活的,可扩展的框架时,可以考虑采用工厂方法模式。
③可以用在异构的项目中。
④可以在测试驱动开发的框架下使用。

工厂方法模式的扩展
①缩小为简单工厂模式
问题:一个模块仅需要一个工厂类,没必要把它生产出来,使用静态方法即可。
解决思路:以上面代码为例,去掉AbstractFactory抽象类,同时将createProduct方法设置为静态类型,简化类的创建过程。
那么ConcreteFactory代码修改如下:

public class ConcreteFactory{
   public static <T extends Human> T createProduct(Class<T> c) {
      Product product= null;
      try {
         product = (Product) Class.forName(c.getName()).newInstance();
      } catch (InstantiationException e) {
         e.printStackTrace();
      } catch (IllegalAccessException e) {
         e.printStackTrace();
      } catch (ClassNotFoundException e) {
         e.printStackTrace();
      }
      return (T)product;
   }
}

客户端类修改如下:

public class Client {
   public static void main(String[] args) {
      Product product =ConcreteFactory.createProduct(ConcretepProduct1.class);
   }
}

运行结果没有变化,但是调用者变简单了,该模式是工厂方法模式的简化版,所以称之为:简单工厂模式,也叫静态工厂模式。缺点是:扩展困难,不符合开闭原则。

②升级为多个工厂类
问题:在一个复杂的项目中,将所有产品类放到一个工厂方法中进行初始化会使代码结构不清晰。
解决思路:为每一个产品定义一个创造者,然后由调用者自己选择与哪个工厂方法关联。

AbstractFactory抽象类代码修改如下:

public abstract class AbstractFactory {
   public abstract Product createProduct();
}

抽象方法中已不需要传递参数,每个具体的工厂都已经非常明确各自的职责。

public class Product1Factory extends AbstractFactory {
        public Product createProduct(){
               return new ConcreteProduct1();
        }
}

public class Product2Factory extends AbstractFactory {
        public Product createProduct(){
               return new ConcreteProduct2();
        }
}

客户端类修改如下:

public class Client {
   public static void main(String[] args) {
     Product product1 = (new Product1Factory()).createProduct();
     Product product2 = (new Product2Factory()).createProduct();
   }
}

回顾一下:为每一个产品实现类都创建一个具体的工厂类,职责清晰,结构简单,缺点是:可扩展性与可维护性带来困难。

③替代单例模式
单例模式的核心在于:内存中只能有一个对象,那么可以使用工厂方法模式也只创建一个对象么?答案是肯定的。
首先创建一个Singleton类:

public class Singleton {
   private Singleton(){}
   public void doSomething(){}
}

将构造方法私有化,这样就无法通过正常方式构造一个Singleton对象,那么SingletonFactory应该如何创建一个单例对象呢?答案是:通过反射。

public class SingletonFactory {
   private static Singleton single;
   static {
      try {
         Class c = Class.forName(Singleton.class.getName());
         //获取无参构造器
         Constructor cs = c.getDeclaredConstructor();
         //设置无参构造器是可以访问的
         cs.setAccessible(true);
         //创建一个实例
         single = (Singleton)cs.newInstance();
      } catch (ClassNotFoundException e) {
         e.printStackTrace();
      } catch (NoSuchMethodException e) {
         e.printStackTrace();
      } catch (InstantiationException e) {
         e.printStackTrace();
      } catch (IllegalAccessException e) {
         e.printStackTrace();
      } catch (InvocationTargetException e) {
         e.printStackTrace();
      }
   }
   //获取单例对象的外部访问方法
   public static Singleton getSingleton(){
      return single;
   }
}

在这里主要注意单例模式下的反射影响。

④延迟初始化:一个对象被消费完毕后,并不立即释放,工厂类保持其初始状态,等待再次被使用。
代码重现:

public class ProductFactory {
   private static final Map<String,Product>PRODUCT_MAP = new HashMap<>();
   public static synchronized Product createProduct(String type)throws Exception{
      Product product = null;
      if (PRODUCT_MAP.containsKey(type)){
         product = PRODUCT_MAP.get(type);
      }else {
         if (Objects.equals(type,"Product1")){
            product = new ConcreteProduct1();
         }else {
            product = new ConcreteProduct2();
         }
         PRODUCT_MAP.put(type,product);
      }
      return product;
   }
}

ProductFactory 负责产品类对象的创建工作,并且通过 PRODUCT_MAP 变量产生一个缓存,将需要再次被使用的对象保留。

延迟初始化框架是可以扩展的,比如限制产品的最大数量,可以通过判断map集合的大小来实现;比如JDBC连接数据库,判断最大连接数MaxConnections等

参考书籍:设计模式之禅 --- 秦小波 著

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

推荐阅读更多精彩内容