工厂方法模式(Factory Method Pattern-1)

工厂方法模式(Factory Method Pattern)

  工厂方法模式(Factory Method Pattern)其定义:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。

工厂方法模式的优点

  1. 工厂方法模式可以降低模块间的耦合性,使用工厂方法模式创建一个对象,不再需要知道创建该对象的艰辛过程和必要信息,只需要提供一个产品的约束条件(例如,类名或约束字符串)就可以获取需要的对象。

  2. 工厂方法有良好的扩展性。

  3. 屏蔽了产品类,调用者只需要关系产品类的接口。因为产品类的具体对象实例是由工厂产生的。

工厂方法模式的应用场景

  1. 工厂方法模式是自己new一个对象的替代方案,但不是绝对的。增加工厂会增加系统的复杂性。

  2. 需要灵活、有良好可扩展性。因为工厂方法模式屏蔽了产品,调用者只会关系产品的接口而不是实现。在不改变接口的情况下,增加一种产品仅仅只需要实现产品的接口就可以了,例如,邮件系统有三种协议(POP3、IMPA、HTTP),这三种产品只需要实现一个共同的接口(例如:IMail),若以后出现了新协议,新协议只需要实现这个接口,该邮件系统就完美支持新协议了。

工厂方法的通用类图

Product角色(抽象产品角色): 定义了产品的共性,实现事物最抽象的定义。

Creator角色(抽闲工厂角色): 定义了一个用于创建对象的接口,具体如何创建对象是由其子类(具体工厂)实现的。

ConcreteProduct(具体产品): 实现了抽象产品所定义的接口。

ConcreteCreator(具体工厂): 实现了创建对象的接口。

工厂方法通用代码

抽象工厂:

public interface Factory

{

//定义一个创建对象的接口,如何创建对象由其子类(具体工厂)实现。

public <T extends Product> T createProduct(Class<T> clazz);

}

具体工厂:

复制代码

public class ConcreteFactory implements Factory

{

@Override

@SuppressWarnings("unchecked")

public <T extends Product> T createProduct(Class<T> clazz)

{

Product product = null;

try

{

product = (Product)clazz.newInstance();

}

catch (InstantiationException e)

{

e.printStackTrace();

}

catch (IllegalAccessException e)

{

e.printStackTrace();

}

return (T)product;

}

}

复制代码

抽象产品:

public interface Product

{

//定义了产品类的共性

public void doSomething();

}

具体产品:

复制代码

public class ConcreteProduct implements Product

{

@Override

public void doSomething()

{

System.out.println("doSomething!");

}

}

复制代码

场景类Client使用工厂方法:

复制代码

public class Client

{

public static void main(String[] args)

{

Factory factory = new ConcreteFactory();

/*

* 使用工厂方法创建ConcreteProduct对象,只需要提供ConcreteProduct类的Class对象就可以创建出ConcreteProduct

* 类的对象,我们无需知道创建该对象的过程,只需要提供一个约束条件。并且工厂方法将具体产品对象屏蔽了,高层模块只用关

* 心产品类的接口,不用关系其实现。这样既方便了对象的创建,也降低高层代码对具体产品对象的耦合

*/

Product product = factory.createProduct(ConcreteProduct.class);

product.doSomething();

}

}

复制代码

工厂方法模式的扩展

扩展一:简单工厂方法模式(静态工厂方法模式)

  有些时候工厂类十分简单,而且不太会发生变化,这时我们就不需要在使用工厂的时候去把工厂实例new出来,并且了解到该工厂不太容易改变,我们就不需要抽象工厂类了,直接定义一个工厂类,该工厂类提供一个静态的工厂方法用于生产对象,这样我们在使用工厂的时候直接使用工厂类提供的静态方法就可以了,而不需要去创建该工厂实例。

简单工厂方法模式(静态工厂方法模式)类图

  代码只需要做简单修改:抽象产品角色和具体产品角色与通用工厂方法模式一致,只需要去掉抽象工厂角色,工厂角色提供一个static的工厂方法。

  该变形有个缺点:工厂类难以扩展。

扩展二:升级为多工厂

  考虑到这样一个情况,有多种产品(特别是每种产品的个性属性较多)。如果我们按照以前的方式设计工厂方法模式,则会定义如下一个巨大的工厂方法:

  public <T exetends Product> T createProduct(Class<T> clazz,parameters)

  {

    ...

    if(clazz == ConcreteProductA.class)

    {...}

    else if(clazz == ConcreteProductB.class)

    {...}

    else if(clazz == ConcreteProductC.class)

    {...}

    ...

  }

  光看函数体就头疼,这种情况下产品种类越多这个函数就越臃肿(因为函数体还要有判断),可读性越差。为了解决这样的情况,工厂方法模式出现了多工厂的变形。将这个巨大的函数体拆解成多个小的函数体(多工厂的目的)。

多工厂工厂方法模式类图

  代码修改:抽象产品类和具体产品类没有什么变化,抽象工厂依然定义一个生产对象的接口供子类实现,子类按照自己的需求分别实现生产对象接口

抽象工厂:

public interface Factory

{

/*定义了生产对象的接口*/

public <T extends Product> T createProduct(Class<T> clazz);

}

具体工厂:

ConcreteFactoryA,ConcreteFactoryB....类似:

复制代码

public class ConcreteFactoryA implements Factory

{

//对象属性的默认值,用于填充创建对象的属性

private static final String DEFAULT_VLAVE1 = "default_value1";

private static final String DEFAULT_VLAVE2 = "default_value2";

@Override

public <T extends Product> T createProduct(Class<T> clazz)

{

T product =null;

try

{

//获取创建该对象的构造函数。这里也可以使用默认构造函数,然后使用set方法完成初始化。

Constructor<T> constructor = clazz.getDeclaredConstructor(new Class<?>[]{String.class,String.class});

product = constructor.newInstance(new Object[]{DEFAULT_VLAVE1,DEFAULT_VLAVE2});

}

catch (Exception e)

{

e.printStackTrace();

}

return product;

}

}

复制代码

场景类中调用:

复制代码

public class Client

{

public static void main(String[] args)

{

/*要创建ProductA,先获取工厂A,然后调用创建对象接口创建对象A*/

Factory factory = new ConcreteFactoryA();

Product productA = factory.createProduct(ProductA.class);

productA.doSomething();

}

}

复制代码

  通过这样的变形,将一个臃肿的工厂分成了许多小的工厂,代码的条理性也比较清晰了,这样的变形让工厂的职责也变得清晰起来。但该变形有一个缺点:扩展不容易,麻烦,例如要增加一个ProductD,那也要增加一个对象的工厂,必须要维持产品与工厂的对应关系。一般在使用该变形时,需要一个协调类封装子工厂,对高层提供统一的接口,避免调用者直接与各子工厂交流。

扩展三:代替单例模式

  单例模式一般都没有接口或抽象类,不能面向接口编程,扩展性比较差等一些缺点,使用工厂方法模式可以一定程度上解决它自身的一些缺点。

单例工厂模式类图

代码修改:具体产品类都应该将他们的构造方法设置为private的。每一个具体工厂持有负责的单例属性。

具体产品:

复制代码

public class SingletonA implements SingletonProduct

{

/*防止SingletonA在其他地方被实例化*/

private SingletonA()

{

}

@Override

public void doSomething()

{

System.out.println("singletonA");

}

}

复制代码

具体工厂类:

复制代码

public class ConcreteSingletonFactory implements SingletonFactory

{

private static SingletonA singletonA = null;

static

{

Class<SingletonA> clazz = SingletonA.class;

try

{

Constructor<SingletonA> constructor = clazz.getDeclaredConstructor(new Class<?>[]{});

constructor.setAccessible(true);

singletonA = constructor.newInstance(new Object[]{});

}

catch (Exception e)

{

e.printStackTrace();

}

}

@Override

public SingletonProduct getSingleProduct()

{

return singletonA;

}

}

复制代码

  调用方式与多工厂变形类似,若单例产品不多可以使用一个静态工厂变形(看具体需求),以上变形的工厂模式代替单例模式,可以让原来的单例注重自己的业务逻辑,不再需要负责单例的实现,单例的实现由工厂实现,这样类的职责就更清晰了。并且单例都实现了共有接口,调用者只用关注接口就可以,不必与具体的单例产品交互,降低了耦合。

扩展四:带缓存的工厂方法模式

  如果遇到创建一个对象比较消耗资源的情况下(例如,硬盘访问、涉及多方面交互),可以使用带缓存的工厂方法模式来降低创建和销毁该类对象带来的复杂性。

  类图与通用类图类似。

带缓存的工厂:

复制代码

public class CacheFactory implements Factory

{

/*工厂缓存,用于存放已经生产出来的对象*/

private static final Map<Class<? extends Product>, Product> cache = new HashMap<Class<? extends Product>, Product>();

@Override

@SuppressWarnings("unchecked")

public <T extends Product> T createProduct(Class<T> clazz)

{

Product product = null;

/*若缓存中存在这类型的对象,则从缓存中取出该对象*/

if(cache.containsKey(clazz))

{

product = cache.get(clazz);

}

else

{

try

{

product = clazz.newInstance();

}

catch (Exception e)

{

e.printStackTrace();

}

cache.put(clazz, product);

}

return (T)product;

}

}

复制代码

  通过定义一个Map集合对象,容纳所有产生的对象,如果在Map中已经存在的对象,则直接取出。如果没有,则根据需要产生一个对象放入到Map中,以便下次使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值