工厂模式为创建对象提供了接口,把对象的创建交给工厂来处理,而不是我们主动的去new。一方面可以屏蔽对象创建的逻辑,另一方面当某一个在应用中到处传播的对象需要修改实例化的逻辑时,我们只需要修改其工厂代码。
工厂模式可以分为以下三类
- 简单工厂
- 工厂模式
- 抽象工厂
其中简单工厂并不属于23种设计模式
简单工厂
顾名思义,简单工厂相比其他两种工厂类的模式相对简单不少。它是由一个工厂对象来决定创建出哪一种产品的示例
下面,我们以生产Cpu的例子来进行讲解
- 首先定义一个生产Cpu的接口
public interface Cpu
{
void produce();
}
- 分别定义其不同的实现类
public class KirinCpu implements Cpu
{
@Override
public void produce()
{
System.out.println("华为麒麟处理器");
}
}
public class MtkCpu implements Cpu
{
@Override
public void produce()
{
System.out.println("联发科处理器");
}
}
public class SnapdragonCpu implements Cpu
{
@Override
public void produce()
{
System.out.println("高通骁龙处理器");
}
}
- 创建一个生产Cpu的工厂类,根据传入的参数返回一个对应的对象
public class CpuFactory
{
public static Cpu getCpu(String type)
{
if(type.equals("Kirin"))
{
return new KirinCpu();
}else if(type.equals("Mtk"))
{
return new MtkCpu();
}else if(type.equals("Snapdragon"))
{
return new SnapdragonCpu();
}
return null;
}
}
4.这样,我们在客户端应用层就可以直接通过工厂来获取想要的Cpu
public class Client
{
public static void main(String[] args)
{
Cpu cpu = CpuFactory.getCpu("Mtk");
if(cpu != null)
{
cpu.produce();
}
}
}
如果不采用简单工厂,我们需要在我们的应用层来处理产品类的具体实现,而采用简单工厂后,产品类的具体实现放在了工厂里。这样就对我们的应用层进行了解耦
缺点
- 工厂类的职责相对较重,所有产品的实现都在一个工厂方法里执行
- 不符合开闭原则(即对修改关闭,扩展开放),当我们新增一个产品的实现类的时候,需要对工厂方法进行修改
改进
我们可以通过反射来改进我们的工厂方法
public class CpuFactory
{
public static Cpu getCpu(Class extends Cpu> c)
{
Cpu cpu = null;
try
{
cpu = c.newInstance();
} catch (InstantiationException | IllegalAccessException e)
{
e.printStackTrace();
}
return cpu;
}
}
简单工厂模式的应用
jdk中Calendar类的getInstance()方法
其有以上的重载方法,但最终都会调用createCalendar(TimeZone zone,Locale aLocale)方法来生成一个Calendar对象,如果指定TimeZone和Locale则会把指定的值作为参数传递给createCalendar方法,如果没有则先获得系统默认值然后再传递。
其方法声明如下
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类有以下实现
我们可以发现在
createCalendar
方法内通过对传入的TimeZone
和Locale
的值进行判断,来生成不同的Calendar
对象
jdbc中数据库连接的获取
我们在写jdbc程序时,首先进行的是Class.forName()
注册驱动,然后通过DriverManager.getConnection
方法来获取数据库的连接。
在这里以mysql为例。当执行完Class.forName("com.mysql.jdbc.Driver")
,会完成类的加载,而在类加载初始化过程中会对静态代码块里的内容进行执行,打开其源码,我们发现其在里面完成了驱动的注册
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
在DriverMananger
里有以下一行代码声明
private final static CopyOnWriteArrayList registeredDrivers = new CopyOnWriteArrayList<>();
而DriverManager.registerDriver(new Driver())
方法会生成一个DriverInfo
,然后将其添加到此CopyOnWriteArrayList里面,至此,驱动的注册完成。
而对于DriverManager.getConnection
,虽然其由好几种重载方法,但最终会都是调用getConnection(String url, java.util.Properties info, Class> caller)
方法,打开其源码
private static Connection getConnection(
String url, java.util.Properties info, Class> caller) throws SQLException {
/*
* When callerCl is null, we should check the application's
* (which is invoking this class indirectly)
* classloader, so that the JDBC driver class outside rt.jar
* can be loaded from here.
*/
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
// synchronize loading of the correct classloader.
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
if(url == null) {
throw new SQLException("The url cannot be null", "08001");
}
println("DriverManager.getConnection(\"" + url + "\")");
// Walk through the loaded registeredDrivers attempting to make a connection.
// Remember the first exception that gets raised so we can reraise it.
SQLException reason = null;
for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
// if we got here nobody could connect.
if (reason != null) {
println("getConnection failed: " + reason);
throw reason;
}
println("getConnection: no suitable driver found for "+ url);
throw new SQLException("No suitable driver found for "+ url, "08001");
}
我们会发现其通过遍历已经注册的驱动,如果驱动正常,则直接返回该驱动生成的
Connection。
在这里驱动的注册相当于工厂方法的参数的传递,然后就可以通过getConnection
方法获得该驱动所对应的连接
- 工厂类模式(一)简单工厂
- 工厂类模式(二)工厂模式
- 工厂类模式(三)抽象工厂