简单工厂(Java)

研磨设计模式 同时被 2 个专栏收录
6 篇文章 0 订阅

简单工厂不是一个标准的设计模式,但是因其常用,简单而又神奇,故把它放到设计模式中。

一:接口的回顾

1:Java中接口的概念

在Java中接口是一种特殊的抽象类,根一般的抽象类相比,接口里面的所有方法都是抽象方法,接口里面的所有属性都是常量。即接口里面只有方法定义而没有任何方法实现。

2:接口的用途

通常用接口来定义实现类的外观,也就是实现类的行为定义,用来约束实现类的行为。接口就相当于一份契约,根据外部应用需要的功能,约定了实现类应该要实现的功能,但是具体的实现类除了实现接口约定的功能外,还可以根据需要实现其他一些功能,这是允许的,也就是说实现类的功能包含但不仅限于接口约束的功能。

通过使用接口,可以实现不相关的相同行为,而不需要考虑这些类之间的层次关系,接口就是实现类对外的外观。

3:接口的思想

根据接口的作用和用途,浓缩下来,接口的思想就是“封装隔离”

通常提到的封装是指对数据的封装,但是这里的封装是指“对被隔离体的行为的封装”,或者是“对被隔离体的职责的封装”;而隔离指的是外部调用和内部实现,外部调用只能通过接口进行调用,外部调用是不知道内部具体实现的,也就是说外部调用和内部实现是被接口隔离开的。

4:使用接口的好处

由于外部调用和内部实现被接口隔离开了,那么只要接口不变,内部实现的变化就不会影响到外部应用,从而使得系统更灵活,具有更好的扩展性和可维护性,这也就是所谓“接口是系统可插拔性的保证”这句话的意思。

5:接口和抽象类的选择

优先选用接口;

在既要定义子类的行为,又要为子类提供公共的功能时应选择抽象类。

二:面向接口编程

面向接口编程是Java编程中一个重要的原则。

在Java程序设计里面,非常讲究层的划分和模块的划分。通常按照三层来划分Java程序,即表现层、逻辑层、数据层,它们之间都要通过接口来通信。

在每一个层里面,又有很多小模块,每个小模块对外则是一个整体,所以一个模块对外应该提供接口,其他地方需要使用到这个模块的功能时,可以通过此接口来进行调用。这也就是常说的“接口是被其隔离部分的外观”。基本的三层结构如下图:


在一个层内部的各个模块间的交互要通过接口。


组件:从设计上讲,组件就是完成一定功能的封装体。小到一个类,大到一个系统,都可以称为组件(因为一个小系统放到一个更大的系统里面去,也就当个组件而已)。事实上,从设计的角度看,系统、子系统、模块、组件等说的其实是同一回事,都是完成一定功能的封装体(只不过功能多少不同而已)。

三:实现下面功能(不用模式)


1:代码实现:

/**
 * 某个接口(通用的、抽象的、非具体的功能)
 * @author Peter
 */
public interface Api {
	
	//某个具体功能的方法定义,用test1演示
	public void test1(String s);
}
/**
 * 对接口的实现
 * @author Peter
 */
public class Impl implements Api {

	public void test1(String s) {

		System.out.println("Now In Impl. The Input s == " + s);
	}
}
/**
 * 客户端;测试使用Api接口
 * @author Peter
 */
public class Client {

	public static void main(String[] args) {
		Api api = new Impl();
		api.test1("别紧张只是个测试而已!");
	}
}

2:对实现的代码小思

在Api api = new Impl( );这行在客户端的代码使得客户端不但知道了Api接口还知道了其实现类Impl。接口的思想是“封装隔离”,而实现类Impl应该是被接口Api封装并同客户端隔离开的,即客户端根本就应该不知道具体的实现类是Impl。

于是乎我们就拿走new Impl( );但是我们却无法得到Api接口对象。就像现在的Client,它知道要使用Api接口,但是不知道由谁实现,也不知道如何实现,从而得不到接口对象,就无法使用接口,那该怎么办?于是乎简单工厂就来了。

四:简单工厂来了

用来解决上述问题的一个合理的解决方案就是简单工厂。

1:简单工厂的定义

提供一个创建对象实例的功能,而无须关心其具体实现。被创建实例的类型可以是接口、抽象类,也可以是具体的类。

2:用简单工厂解决问题思路

分析上面的问题,虽然不能让模块外部知道模块内部的具体实现,但是模块内部是可以知道实现类的,而且创建接口是需要具体实现类的。

那么,我们在模块内部新建一个类,在这个类里面来创建接口,然后把创建好的接口返回给客户端,这样,外部应用就只需要根据这个类来获取相应的接口对象,然后就可以操作接口定义的方法了。把这样的对象称为简单工厂,就叫它Factory。

这样一来,客户端就可以通过Factory来获取需要的接口对象,然后调用接口的方法来实现需要的功能,而且客户端也不用再关心具体的实现了。

3:简单工厂结构和说明


Api:定义客户所需要的功能接口;

Impl:具体实现Api的实现类,可能会有多个

Factory:工厂,选择合适的实现类来创建Api接口对象。

Client:客户端,通过Factory来获取Api接口对象,然后面向Api接口编程。

4:简单工厂示例代码

/**
 * 接口的定义,该接口可以通过简单工厂来创建
 * @author Peter
 */
public interface Api {
	
	//具体功能方法的定义
	public void operation(String str);
}
/**
 * 接口的具体实现对象A
 * @author Peter
 */
public class ImplA implements Api {

	public void operation(String str) {
		System.out.println("ImplA s == " + str);
	}
}
/**
 * 接口的具体实现对象B
 * @author Peter
 */
public class ImplB implements Api {

	public void operation(String str) {
		System.out.println("ImplB s == " + str);
	}
}
/**
 * 工厂类用于创建Api对象
 * @author Peter
 */
public class Factory {

	//具体创建Api对象的方法,int condition示意,从外部传入的选择条件
	public static Api createApi(int condition){
		/**
		 * 应该根据某些条件去选择究竟创建哪一个具体的实现对象
		 * 这些条件可以从外部传入,也可以从其他途径获取
		 * 如果只有一个实现,可以省略条件,因为没有选择的必要
		 */
		Api api = null;
		if(condition == 1){
			api = new ImplA();
		}else if(condition == 2){
			api = new ImplB();
		}
		return api;
	}
}
/**
 * 客户端,使用Api接口
 * @author Peter
 */
public class Client {

	public static void main(String[] args) {
		//通过简单工厂来获取接口对象
		Api api = Factory.createApi(1);
		api.operation("正在使用简单工厂,选择条件为1");
	}
}

五:模式讲解

表面上看起来在客户端的new Impl( )放到了Factory中,这没有什么区别啊,可是特殊就在Factory的位置,Factory是位于封装体内部的,即简单工厂是跟接口和实现类在一块的,算是封装体内部的一个类,所以Factory知道实现类是没有关系的。


上图的虚线框,就好比是一个组件的包装边界,表示接口、实现类和工厂类组合成了一个组件。在这个封装体里面,只有接口和工厂是对外的,即让外部知道并使用,故意泄露了一些在虚线框的外面,而具体的实现类是不对外的,被完全包含在虚线框内。

对于客户端而言,只是知道了接口Api和简单工厂Factory,通过Factory就可以获得Api了,这样就达到了让Client在不知道具体实现类的情况下获取接口Api。

所以看似简单地将new Impl()这句话从客户端里面移动到了简单工厂里面,其实是有了实质的变化的。

1:简单工厂的功能

利用简单工厂创建接口,但是也可以利用简单工厂创建抽象类或者普通类的实例。

2:静态工厂

使用简单工厂的时候,通常不用创建简单工厂类的类实例,没有创建实例的必要。因此可以把简单工厂类实现成一个工具类,直接使用静态方法就可以了。即简单工厂的方法通常是静态的,所以也被称为静态工厂。如果要防止客户端无谓地创造简单工厂实例,还可以把简单工厂的构造方法私有化了。

3:万能工厂

一个简单工厂可以包含很多用来构造东西的方法,这些方法可以创建不同的接口、抽象类或者是类实例。一个简单工厂理论上可以构造任何东西,所以又称之为“万能工厂”。

4:简单工厂创建对象的范围

理论上,简单工厂什么都可以创建,但对于简单工厂可创建对象的范围,通常不要太大。建议控制在一个独立的组件级别或一个模块级别,也就是一个组件或模块简单工厂。否则这个简单工厂类会职责不明,有点大杂烩的感觉。

5:简单工厂调用的顺序示意图


6:简单工厂命名

类名称建议为“模块名称+Factory”。比如,用户模块的工厂就称为UserFactory。

方法名称通常称为“get+接口名称”或者是“create+接口名称”。比如,有一个接口名为UserEbi,那么方法名称通常为getUserEbi或者是createUserEbi。

六:简单工厂中方法的写法

虽然说简单工厂的方法大多是用来创建接口的,但是仔细分析就会发现,真正能实现功能的是具体的实现类,这些实现类是已经做好的,并不是真的要靠简单工厂来创造出来的,简单工厂的方法无外乎就是:实现了选择一个合适的实现类来使用。

故简单工厂方法的内部主要实现的功能是“选择合适的实现类”来创建实例对象。既然要实现选择,那么就需要选择的条件或者是选择的参数,选择条件或者是参数来源有以下几种:

1:来源于客户端,有Client来传入参数

/**
 * 接口的定义,该接口可以通过简单工厂来创建
 * @author Peter
 */
public interface Api {
	
	//具体功能方法的定义
	public void operation(String str);
}
/**
 * 接口的具体实现对象A
 * @author Peter
 */
public class ImplA implements Api {

	public void operation(String str) {
		System.out.println("ImplA s == " + str);
	}
}
/**
 * 接口的具体实现对象B
 * @author Peter
 */
public class ImplB implements Api {

	public void operation(String str) {
		System.out.println("ImplB s == " + str);
	}
}
/**
 * 工厂类用于创建Api对象
 * @author Peter
 */
public class Factory {

	//具体创建Api对象的方法,int condition示意,从外部传入的选择条件
	public static Api createApi(int condition){
		/**
		 * 应该根据某些条件去选择究竟创建哪一个具体的实现对象
		 * 这些条件可以从外部传入,也可以从其他途径获取
		 * 如果只有一个实现,可以省略条件,因为没有选择的必要
		 */
		Api api = null;
		if(condition == 1){
			api = new ImplA();
		}else if(condition == 2){
			api = new ImplB();
		}
		return api;
	}
}
/**
 * 客户端,使用Api接口
 * @author Peter
 */
public class Client {

	public static void main(String[] args) {
		//通过简单工厂来获取接口对象
		Api api = Factory.createApi(1);
		api.operation("正在使用简单工厂,选择条件为1");
	}
}

2:来源于配置文件,从配置文件获取用于判断的值

/**
 * 接口的定义,该接口可以通过简单工厂来创建
 * @author Peter
 */
public interface Api {
	
	//具体功能方法的定义
	public void operation(String str);
}
/**
 * 接口的具体实现对象A
 * @author Peter
 */
public class ImplA implements Api {

	public void operation(String str) {
		System.out.println("ImplA s == " + str);
	}
}
/**
 * 接口的具体实现对象B
 * @author Peter
 */
public class ImplB implements Api {

	public void operation(String str) {
		System.out.println("ImplB s == " + str);
	}
}
Factory.properties
ImplClassA=com.simplefactory.demo03.ImplA
ImplClassB=com.simplefactory.demo03.ImplB
public class Factory {

	/**
	 * 具体创建Api的方法,根据配置文件的参数来创建接口
	 * @return
	 */
	public static Api createApi(){
		//直接读取配置文件来获取需要创建实例的类
		Properties p = new Properties();
		InputStream input = null;
		try {
			input = Factory.class.getResourceAsStream("Factory.properties");
			p.load(input);
		} catch (IOException e) {
			System.out.println("装载配置文件工厂出错,具体的堆栈信息如下:");
			e.printStackTrace();
		}finally{
			try {
				input.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		Api api = null;
		try {
			api = (Api)Class.forName(p.getProperty("ImplClassB")).newInstance();
		} catch (Exception e) {

			e.printStackTrace();
		}
		return api;
	}
}
public class Client {
	public static void main(String[] args) {
		Api api = Factory.createApi();
		api.operation("读取配置文件方式的简单工厂");
	}
}

3:来源于程序运行期的某个值,比如从缓存中获取某个运行期的值。

七:简单工厂的优缺点

1:简单工厂有点:

(1)帮助封装(简单工厂虽然很简单,但是很友好地帮助我们实现了组件的封装,然后让组件外部能真正面向接口编程)

(2)解耦(通过简单工厂,实现了客户端和具体实现类的解耦,如上面例子,客户端根本就不知道具体是由谁来实现,也不知道如何实现,客户端只是通过工厂获取它需要的接口对象)。

2:简单工厂缺点

(1)可能增加了客户端的复杂度

如果通过客户端的参数来选择具体的实现类,那么就必须让客户端能理解各个参数所代表的具体功能和含义,这样会增加客户端使用的难度,也部分暴露了内部实现,这种情况可以选用可配置的方式来实现。

(2)不方便扩展子工厂

私有化简单工厂的构造方法,使用静态方法来创建接口,也就不能通过写简单工厂类的子类来改变创建接口的方法的行为了。不过,通常情况下是不需要为简单工厂创建子类的。

八:小思简单工厂

1:简单工厂的本质是:选择实现

注意简单工厂的重点在选择,实现是已经做好了的。就算实现再简单,也要由具体的实现类来实现,而不是在简单工厂里面来实现。简单工厂的目的在于客户端来选择相应的实现,从而使得客户端和实现之间解耦。这样一来,具体实现发生了变化,就不用变动客户端了,这个变化会被简单工厂吸收和屏蔽掉。

实现简单工厂的难度在于“如何选择”实现,上面说了几种传递参数的方法,那都是静态的参数,还可以实现成为动态的参数。比如,在运行期间,有工厂来读取某个内存的值,或者是去读取数据库中的值,然后根据这个值来选择具体的实现等。

2:何时使用简单工厂

(1)如果想要完全封装隔离具体实现,让外部只能通过接口来操作封装体,那么可以选用简单工厂,让客户端通过工厂来获取相应的接口,而无须关系具体的实现。

(2)如果想要把对外创建对象的职责集中管理和控制,可以选用简单工厂,一个简单工厂可以创建很多的、不相关的对象,可以把对外创建对象的职责集中到一个简单工厂来,从而实现集中管理和控制。



  • 18
    点赞
  • 4
    评论
  • 32
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值