likes
comments
collection
share

工厂模式 Factory

作者站长头像
站长
· 阅读数 4

工厂模式 Factory

工厂模式(Factory Design Pattern)分为三种更加细分的类型:简单工厂、工厂方法和抽象工厂。

简单工厂模式:定义了一个创建对象的类,由这个类来封装实例化对象的行为。

The Simple Factory Pattern defines a class that is responsible for creating objects based on provided parameters or conditions.

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

The Factory Method Pattern defines an interface for creating an object, but leaves the choice of its type to the subclasses, creation being deferred at run-time.

抽象工厂模式:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

The Abstract Factory Pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes.

这里抓住重点,工厂模式毕竟是属于创建型模式,创建型模式的目的是为了让对象的创建和使用解耦

而工厂模式是通过将产品的创建过程封装到工厂,我们仅通过调用工厂的方法即可获取产品的实例,从而隐藏了产品对象创建的具体细节。

工厂模式的定义单看字面描述可能不好理解,可以结合着下述代码理解。

简单工厂 Simple Factory

简单工厂模式:定义了一个创建对象的类,由这个类来封装实例化对象的行为。

工厂模式 Factory

简单工厂模式包含如下角色:

Factory(工厂):工厂角色即工厂类,它是简单工厂模式的核心,负责实现创建所有实例的内部逻辑;

Product(抽象产品):产品角色是简单工厂模式所创建的所有对象的父类,负责描述所有实例所共有的公共接口

Concrete Product(具体产品):具体产品角色是简单工厂模式的创建目标,所有创建的对象都充当这个角色的某个具体类的实例。

抽象产品 Product

将产品的通用行为抽离出来形成接口,才是写好工厂模式的重点。

public interface Product {
    void doSomeThing();
}

具体产品 Concrete Product

不同的产品有不同的实现。

▪ 产品A

public class ConcreteProductA implements Product{
    @Override
    public void doSomeThing() {
        System.out.println("我是具体产品 A!");
    }
}

▪ 产品B

public class ConcreteProductB implements Product{
    @Override
    public void doSomeThing() {
        System.out.println("我是具体产品 B!");
    }
}

▪ 产品C

public class ConcreteProductC implements Product{
    @Override
    public void doSomeThing() {
        System.out.println("我是具体产品 C!");
    }
}

工厂 Simple Factory

简单工厂之所以简单,就因为它只有一个工厂类,这一个工厂负责所有具体产品的创建。

public class Factory {

    public static Product createProduct(String type){
        switch(type){
            case "A": return new ConcreteProductA();
            case "B": return new ConcreteProductB();
            case "C": return new ConcreteProductC();
            default: return null;
        }
    }
}

实际上,简单工厂模式还叫作静态工厂方法模式(Static Factory Method Pattern)。因为其中创建对象的方法是静态的。

简单工厂模式肯定是不符合开闭原则的,因为每增加一种产品都要修改工厂类。

有什么方法,能让工厂类少改动呢?很简单,利用反射创建对象即可。

public static Product createProduct(Class 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 product;
}

这样新加产品也不能改工厂类了,真是个偷懒的好方法。

客户端 Client

客户端需要哪种产品,就从工厂中拿。

Product product = Factory.createProduct("A");
product.doSomeThing();
product = Factory.createProduct("B");
product.doSomeThing();
product = Factory.createProduct("C");
product.doSomeThing();

简单工厂很好理解,这里需要注意的是,抽象产品可以是接口,可以是抽象类,无论它是什么,都必须是具体产品的父类。只有这样,才能利用面向对象的多态特性,使得产品的定义与实现分离,从而简化客户端开发。

如上述代码,客户端是使用接口 Product 进行方法调用,而不是使用具体的产品。

工厂方法 Factory Method

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

工厂模式 Factory

工厂方法和简单方法的区别就在于,工厂方法将工厂角色拆分成抽象工厂具体工厂两种角色,每一种具体工厂只负责创建一种具体的产品。

在 GoF 的《设计模式》一书中,简单工厂模式被视为工厂方法模式的一种特例。

抽象工厂 Factory

public interface IFactory {
    Product createProduct();
}

具体工厂 Concrete Factory

简单工厂模式的代码实现中,有多处 if 分支判断逻辑,违背开闭原则,而工厂方法针对每一种产品都会有对应生产它的具体工厂。

▪ 工厂A,专门生产产品A

public class ConcreteFactoryA implements IFactory {

    @Override
    public Product createProduct(){
        return new ConcreteProductA();
    }
}

▪ 工厂B,专门生产产品B

public class ConcreteFactoryB implements IFactory {

    @Override
    public Product createProduct(){
        return new ConcreteProductB();
    }
}

这样当我们新增一种产品的时候,只需要新增一个实现了 IFactory 接口的具体工厂类即可。所以,工厂方法模式比起简单工厂模式更加符合开闭原则。

如果对象的创建逻辑很简单,如示例一样简单 new 一下就可以创建产品,就没必要使用工厂方法模式了,毕竟每引入一种产品就要创建一个对应的工厂,太冗余了。

如果对象的创建逻辑很复杂,引入工厂方法模式后,可以让将复杂的创建逻辑拆分到多个工厂类中,让每个工厂类都不至于过于复杂,更容易维护。

客户端 Client

客户端需要哪种产品,就从对应的工厂中拿。

// 工厂 A 专门生产 产品A; 
IFactory factory = new ConcreteFactoryA(); 
Product product = factory.createProduct();
product.doSomeThing();

// 工厂 B 专门生产 产品B
factory = new ConcreteFactoryB();
product = factory.createProduct();
product.doSomeThing();

抽象工厂 Abstract Factory

抽象工厂模式:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

工厂模式 Factory

抽象工厂模式在产品角色上做出来细分,使得一个产品族和一个产品等级结构唯一确定一个具体产品:

产品等级表示不同类别的产品,产品等级可以类比商品品类,如手机、汽车、电视代表不同的产品等级;有几个产品等级就有几个抽象产品,上 UML图中的 ProductXProductY 分别是不同的产品等级。

产品族是相互关联、不同产品等级结构的产品集合,可以类比品牌,如小米品牌拥有小米电视、小米手机、小米汽车...不同产品等级的产品。

抽象工厂模式在工厂角色做的变化是,同一个工厂只生产同一产品族,不同等级结构的产品,如小米工厂只生产小米电视机和小米洗衣机,不会生成其他牌子(产品族)的产品。

抽象产品 Product

▪ X 等级产品,可以做 xxxxx

public interface ProductX {
    void xxxxx();
}

▪ Y 等级产品,可以做 xxxxx

public interface ProductY {
    void yyyyy();
}

具体产品 Concrete Product

▪ A 产品 和 B产品 的 X 等级产品,可以做 xxxxx

public class ConcreteProductAX implements ProductX{
    @Override
    public void xxxxx() {
        System.out.println("我是 X 产品族 ,属于 A 产品等级");
    }
}
public class ConcreteProductBX implements ProductX{
    @Override
    public void xxxxx() {
        System.out.println("我是 X 产品族 ,属于 B 产品等级");    
    }
}

▪ A 产品 和 B产品 的 Y 等级产品,可以做 yyyy

public class ConcreteProductAY implements ProductY{
    @Override
    public void yyyyy() {
        System.out.println("我是 Y 产品族 ,属于 A 产品等级");    
    }
}
public class ConcreteProductBY implements ProductY{
    @Override
    public void yyyyy() {
       System.out.println("我是 Y 产品族 ,属于 B 产品等级");    
    }
}

抽象工厂 Factory

当前产品族有几个产品等级,抽象工厂类中就应该有几个创建对应产品的方法:

public interface IFactory {

    ProductX createProductX();
    ProductY createProductY();
}

具体工厂 Concrete Factory

同一个工厂只生产同一产品族,不同等级结构的产品:

▪ A 产品族工厂

public class ConcreteFactoryA implements IFactory{
    @Override
    public ProductX createProductX() {
        return new ConcreteProductAX();
    }

    @Override
    public ProductY createProductY() {
        return new ConcreteProductAY();
    }
}

▪ B 产品族工厂

public class ConcreteFactoryB implements IFactory{
    @Override
    public ProductX createProductX() {
        return new ConcreteProductBX();
    }

    @Override
    public ProductY createProductY() {
        return new ConcreteProductBY();
    }
}

客户端 Client

客户端想使用什么产品族的产品,创建对应的工厂即可:

IFactory factory = new ConcreteFactoryA(); // new ConcreteFactoryB();
ProductX productX = factory.createProductX();
ProductY productY = factory.createProductY();

工厂方法模式是一个具体产品对应一个具体工厂,很多时候显得并不必要,而抽象工厂模式则是将同属于一个产品族的产品用一个具体工厂进行生产,这样就可以有效地减少工厂类的个数。

源码鉴赏

工厂模式是一种非常常用的设计模式,在很多开源项目、工具类中到处可见,下面就挑几个看看。

JDK 之 Calendar

Calendar 是一个抽象基类,提供了一组对年月日时分秒星期等日期信息的操作的函数,并针对不同国家和地区的日历提供了相应的子类,即本地化。比如公历 GregorianCalendar,佛历 BuddhistCalendar,日本帝国历 JapaneseImperialCalendar

工厂模式 Factory

在这里,Calendar 既是简单工厂,又是抽象产品。

Calendar 作为简单工厂创建日历的方式在 createCalendar 方法中,节选如下:

if (aLocale.getLanguage() == "th" 
        && aLocale.getCountry() == "TH") { 
    cal = new BuddhistCalendar(zone, aLocale); 
} 
else if (aLocale.getVariant() == "JP" 
        && aLocale.getLanguage() == "ja" 
        && aLocale.getCountry() == "JP") { 
    cal = new JapaneseImperialCalendar(zone, aLocale); 
} 
else { 
    cal = new GregorianCalendar(zone, aLocale); 
}


Calendar类作为简单工厂封装了日历的创建过程,使得客户端代码不依赖于具体的日历创建方式。

Mybatis 之 SqlSessionFactory

工厂模式 Factory 这不是典型的工厂方法模式,一个具体工厂创建一种具体产品:

UnpooledDataSourceFactory 生产 UnpooledDataSource

PooledDataSourceFactory 生产 PooledDataSource

JndiDataSourceFactory 生产 JndiDataSource

Mybatis框架用到工厂模式的地方挺多的,如 TransactionFactory 用于生产 TransactionSqlSessionFactory 生产 SqlSession...这里就不赘述了。

Spring 之 ResourceLoader

工厂模式 Factory

ResourceLoaderSpring 框架加载资源的组件,乍一看 UML 类图,看起来挺复杂的,这里只抓重点。

抽象产品:Resource 接口

具体产品:ClassPathResource(代表类路径下的资源)、FileSystemResource(代表文件系统中的资源)、UrlResource(代表本地或网络上的资源)。

抽象工厂:ResourceLoader接口

具体工厂:DefaultResourceLoaderFileSystemResourceLoader

通过 DefaultResourceLoadergetResource方法可以看到 Resource 是怎么加载的。

@Override
public Resource getResource(String location) {
   // ...
   // 根据不同前缀加载不同的Resource
   if (location.startsWith("/")) {
      return getResourceByPath(location);
   }
   // 加载 classpath:/ 开头的资源
   else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
      return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
   }
   else {
      try {
         // 尝试当作URL来处理
         URL url = new URL(location);
         return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
      }
      catch (MalformedURLException ex) {
         // 无效URL,从文件系统加载
         return getResourceByPath(location);
      }
   }
}

从上面的源码可以看出,DefaultResourceLoader 根据不同的资源前缀(如classpath:/file:/xxx:/),加载不同类型的 Resource 对象,如 ClassPathResourceUrlResource 等。

至于 FileSystemResource 呢?FileSystemResourceLoader 继承了 DefaultResourceLoader,重写了 getResourceByPath 方法:

@Override
protected Resource getResourceByPath(String path) {
   if (path.startsWith("/")) {
      path = path.substring(1);
   }
   return new FileSystemContextResource(path);
}

那你说 SpringResourceLoader 属于简单工厂模式还是工厂方法模式,我只能说设计模式是可以混合使用的,而且 FileSystemResourceLoader 继承了 DefaultResourceLoader,重写了 getResourceByPath 方法,是不是也有点模板方法模式的影子呢。

Spring 之 BeanFactory

讲到 Spring 不得不提 Spring 的IOC容器——BeanFactory

BeanFactory 顾名思义是用于生产 Bean 的,但 Bean 是用户自定义的类,没有统一的接口,也不知道用户创建的类是什么,不符合产品的定义,怎么办?

所有类都继承自 Object,那 Bean 的抽象产品就是 Object 喽,Spring 不知道用户创建的类是什么,那就读取配置文件,解析出来全类名通过反射创建喽。

BeanFactory 的实现类 AbstractAutowireCapableBeanFactory 中可以一窥产品创建的过程:

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
      throws BeanCreationException {

   // ...
   if (instanceWrapper == null) {
      // 反射创建Bean 
      instanceWrapper = createBeanInstance(beanName, mbd, args);
   }
   // ...
   Object exposedObject = bean;
   try {
      // 属性赋值+依赖注入
      populateBean(beanName, mbd, instanceWrapper);
      // Bean 对象的初始化
      exposedObject = initializeBean(beanName, exposedObject, mbd);
   }
   // ...

总结

工厂模式的意义在于将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和客户端的解耦。

简单工厂(Simple Factory)因为就一个工厂类,所以适用于产品种类较少且不太可能发生变化的场景。

优点就是结构简单:只需要传入一个正确的参数,就可以获取想要的对象,无须知道其创建细节。

缺点就是不符合开闭原则:工厂类的职责相对过重,增加新的产品需要修改工厂类的判断逻辑,如果产品过多,就不易维护。

工厂方法(Factory Method)是一个具体产品创建一个具体产品。

优点就是符合开闭原则:当需要添加新的产品时,只需创建新的具体产品类和对应的具体工厂类即可,而不需要修改现有的代码。

缺点就是增加了类的数量 :引入工厂方法模式会增加系统中类的数量,因为需要为每个具体产品类创建对应的具体工厂类。

抽象工厂(Abstract Factory)是简单工厂和工厂方法的结合体,每个具体工厂只生产同一产品族不同产品等级的产品。所以适用于创建一组相关或依赖对象的场景。

如果只有一种产品族,那抽象工厂就是简单工厂,如果只有一种产品等级,那抽象工厂就是工厂方法。

所以如果需要增加产品的等级结构,那抽象工厂就不符合开闭原则,如果是增加产品族,那抽象工厂就就符合开闭原则。