工厂模式

工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

工厂模式可以将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到依赖关系的解耦,提高扩展和可维护性;

简单来说,对象不再通过new 进行创建,而是工厂来创建对象,这样做的好处是可以屏蔽创建对象的细节(例如需要大量传参),对外提供一个创建对象的统一方法,实现了创建者和调用者的分离

工厂模式可以分为两类:

  • 简单工厂模式(也叫静态工厂模式)
  • 工厂方法模式

【抽象工厂模式,归属于另一种设计模式;】

简单工厂模式

简单工厂模式,工厂类提供创建对象的静态方法,因此也叫静态工厂模式;

代码实现

有一个Car(车)的接口和WuLing(五菱),Tesla(特斯拉)两个实现类;

  1. Car的接口类
//接口类
public interface Car {
	void name();
}
  1. WuLing 实现类
public class WuLing implements Car{

	@Override
	public void drive () {
		System.out.println("五菱宏光");
	}
}
  1. Tesla 实现类
public class Tesla implements Car {
	@Override
	public void drive () {
		System.out.println("特斯拉");
	}
}
  1. 客户端
public class Client {

	public static void main(String[] args) {

		Car car1 = new WuLing();
		Car car2 = new Tesla();

		car1.drive ();
		car2.drive ();

	}
}

没有用工厂模式之前,我们创建对象需要通过new 来创建,如果创建对象的构造器比较复杂,需要传入很多数,创建对象的操作就会变得复杂,所以有了工厂模式。工厂模式可以屏蔽创建对象的细节,不会对客户端暴露创建对象的逻辑,解耦等优点;

简单工厂的代码实现

  1. Car的工厂类:
public class CarFactory {
	
    //根据条件创建对应的对象
	public static Car getCar(String car){
		if(car.equals("五菱")){
			return new WuLing();
		} else if (car.equals("特斯拉")) {
			return new Tesla();
		}else {
			return null;
		}
	}
}
  1. 客户端
public class Client {

	public static void main(String[] args) {
		
        //通过工厂方法创建对象
		Car car1 = CarFactory.getCar("五菱");
		Car car2 = CarFactory.getCar("特斯拉");

		car1.drive ();
		car2.drive ();

	}
}

简单工厂模式的实现就是这样,那么现在如果新增一个车的类,CarFactory 工厂类就需要修改代码;

如果新增一个车的类

  1. 新增一个大众的车
public class Dazhong implements Car {
	@Override
	public void drive () {
		System.out.println("大众");
	}
}
  1. 修改后的工厂方法
public class CarFactory {
	public static Car getCar(String car){
		if(car.equals("五菱")){
			return new WuLing();
		} else if (car.equals("特斯拉")) {
			return new Tesla();
		}else if (car.equals("大众")){
			return new Dazhong();
		}else {
			return null;
		}
	}
}

所以简单工厂模式是违背开闭原则(OCP)的,新增一个车,需要修改原来的代码;但在实际开发中,很多时候都用到了简单工厂模式,因为它简单,容易理解,所以很实用;

那有没有不违背OCP原则的方法,工厂方法模式可以做到,但要付出一定代价,会使类变多;

工厂方法模式

工厂方法模式需要定义一个工厂类的接口,为每一个车类创建一个实现工厂类接口的工厂类;

以上面的例子为例,还是有Car接口,WuLing和Tesla实现类;

工厂方法模式的代码实现

  1. car的接口和工厂接口
public interface Car {
	void name();
}

public interface CarFactory {
	Car getCar();
}
  1. WuLing 类和工厂类
//WuLing 对象
public class WuLing implements Car {

	@Override
	public void name() {
		System.out.println("五菱宏光");
	}
}

//WuLing工厂类
public class WuLingFactory implements CarFactory{

	@Override
	public Car getCar() {
		return new WuLing();
	}
}
  1. Tesla 类和工厂类
//Tesla 对象
public class Tesla implements Car {
	@Override
	public void name() {
		System.out.println("特斯拉");
	}
}

//Tesla工厂类
public class TeslaFactory implements CarFactory{

	@Override
	public Car getCar() {
		return new Tesla();
	}
}
  1. 客户端,通过不同的工厂方法创建对应的类
//简单工厂模式
public class Client {

	public static void main(String[] args) {
		Car car1 = new WuLingFactory().getCar();
		Car car2 = new TeslaFactory().getCar();

		car1.name();
		car2.name();
	}
}

工厂方法模式为每一个车类创建对应的工厂类,如果新增一个车的类,原来的代码不需要改动,但需要新增对应车工厂;

新增一个车的类

//Dazhong 对象
public class Dazhong implements Car{

	@Override
	public void name() {
		System.out.println("大众");
	}
}

//Dazhong工厂类
public class DazhongFactory implements CarFactory {

	@Override
	public Car getCar() {
		return new Dazhong();
	}
}

工厂方法模式遵守开闭原则(OCP原则),但每新增一个对象,都需要新增对应的工厂类方法,可能造成类爆炸;

工厂模式和工厂方法模式的优缺点

  • 简单工厂模式:虽然某种程度上不符合OCP原则,但简单,容易理解,实际使用最多;
  • 工厂方法模式:遵守OCP原则,不修改已有类的前提下,通过增加新的工厂类实现扩展;可能造成类爆炸;

工厂模式在 Calendar 应用的源码分析

JDK 中的 Calendar 日历类中,就使用了简单工厂模式

代码分析

  1. 日期实例
public class CalendarDemo {

	public static void main(String[] args) {
		// getInstance 是 Calendar 静态方法
		Calendar cal = Calendar.getInstance();
		// 注意月份下标从 0 开始,所以取月份要+1
		System.out.println("年:" + cal.get(Calendar.YEAR));
		System.out.println("月:" + (cal.get(Calendar.MONTH) + 1));
		System.out.println("日:" + cal.get(Calendar.DAY_OF_MONTH));
		System.out.println("时:" + cal.get(Calendar.HOUR_OF_DAY));
		System.out.println("分:" + cal.get(Calendar.MINUTE));
		System.out.println("秒:" + cal.get(Calendar.SECOND));
	}
}
  1. getInstance 实例方法
public static Calendar getInstance() {
    return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
}
  1. createCalendar 方法,根据 TimeZone zone, locale 创建对应的实例对象
private static Calendar createCalendar(TimeZone zone, Locale aLocale){
    
    CalendarProvider provider =
        LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale)
        .getCalendarProvider();
    if (provider != null) {
        try {
            return provider.getInstance(zone, aLocale);
        } catch (IllegalArgumentException iae) {
            // fall back to the default instantiation
        }
    }

    Calendar cal = null;
	
    //根据不同的条件创建对应的实例
    if (aLocale.hasExtensions()) {
        String caltype = aLocale.getUnicodeLocaleType("ca");
        if (caltype != null) {
            switch (caltype) {
                case "buddhist":
                    cal = new BuddhistCalendar(zone, aLocale);
                    break;
                case "japanese":
                    cal = new JapaneseImperialCalendar(zone, aLocale);
                    break;
                case "gregory":
                    cal = new GregorianCalendar(zone, aLocale);
                    break;
            }
        }
    }
    if (cal == null) {
        // If no known calendar type is explicitly specified,
        // perform the traditional way to create a Calendar:
        // create a BuddhistCalendar for th_TH locale,
        // a JapaneseImperialCalendar for ja_JP_JP locale, or
        // a GregorianCalendar for any other locales.
        // NOTE: The language, country and variant strings are interned.
        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);
        }
    }
    return cal;
}

工厂模式的应用场景

除了上面的Calendar 外,工厂模式还有一些常见的应用场景;

应用场景:

  • JDK中Calendar的getlnstance方法
  • JDBC中的Connection对象的获取
  • Spring中IOC容器创建管理bean对象
  • 反射中Class对象的newlnstance方法