模式系列之工厂模式

   简介:工厂模式和抽象工厂模式是在日常开发中使用非常广泛的设计模式。主要用于实现将对象的实例化部分取出来,进而优化系统架构,增强系统的扩展性。本课程即将讲解Java中的工厂模式和抽象工厂模式的应用。

1. 工厂模式概述

1.1   工厂模式概念

   实例化对象,用工厂方法替代new操作

   工厂模式包括工厂方法模式和抽象工厂模式

   抽象工厂模式是工厂方法模式的扩展

1.2 工厂模式的意图

定义一个接口来创建对象,但是让子类来决定哪些类需要被实例化。

工厂方法把实例化的工作推迟到子类中去实现。

1.3 什么情况下适合工厂模式?

有一组类似的对象需要创建。

在编码时不能预见需要创建哪种类的实例。

系统需要考虑扩展,不应依赖于产品实例如何被创建、组合和表达的细节。

1.4 工厂模式的动机

项目中的现状:

在软件系统中经常面临着“对象”的创建工作,由于需求的变化,这个对象可能随之也会发生变化,但它却拥有比较稳定的接口。

为此,我们需要提供一种封装机制来隔离出这个易变对象的变化,从而保持系统中其他依赖该对象的对象不随着需求变化而变化。

基于项目现状将代码进行如下设计:

①尽量松耦合,一个对象的依赖对象的变化与本身无关

②具体产品与客户端剥离,责任分割

1.5 分类

工厂模式主要是为创建对象提供过渡接口

以便将创建对象的具体过程品比隔离起来,达到提高灵活性的目的

· 工厂模式在《Java与模式》中分为三类: 

00001. 简单工厂模式(Simple Factory)

00002. 工厂方法模式(Factory Method)

00003. 抽象工厂模式(Abstract Factory)

· 这三种模式从上到下,逐步抽象,并且更具一般性

· GOF在《设计模式》一书中将工厂模式分为两类: 

00001. 工厂方法模式(Factory Method)

00002. 抽象工厂模式(Abstract Factory)

· 将简单工厂模式(Simple Factory)看为工厂方法模式的一种特例,两者归为一类。

1.5.1 简单工厂模式

· 简单工厂模式又称静态工厂方法模式

· 从命名上就可以看出这个模式一定很简单

· 它存在的目的很简单:定义一个用于创建对象的接口

· 简单工厂模式的组成: 

·工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑,在Java中它往往由一个具体类实现

·抽象产品角色:它一般是具体产品继承的父类或者实现的接口。在Java中由接口或者抽象类实现

·具体产品角色:工厂类所创建的对象就是此角色的实例。在java中由一个具体类实现


1.5.2 工厂方法模式

· 工厂方法模式去掉了简单工厂模式中工厂方法的静态属性,使得它可以被子类继承。

· 这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的工厂子类来分担。

· 下面,看看它的组成 

·抽象工厂角色:这是工厂方式模式的核心,它与应用程序无关,是具体工厂角色必须实现的接口或者必须继承的父类。Java中它由抽象类或者接口实现

·具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品对象

·抽象产品角色:它是具体产品继承的父类或者是实现的接口。 java 中一般有抽象类或者接口来实现。

·具体产品角色:具体工厂角色所创建的对象就是此角色的实例。 java 中由具体的类来实现。 


1.5.3 抽象工厂模式

    先来认识下什么是产品族: 位于不同产品等级结构中,功能相关联的产品组成的家族。还是让我们用一个例子来形象地说明一下吧。

·图中的 BmwCar 和 BenzCar 就是两个产品树(产品层次结构);而如图所示的BenzSportsCar 和 BmwSportsCar 就是一个产品族。他们都可以放到跑车家族中,因此功能有所关联。同理 BmwBussinessCar 和 BenzSportsCar 也是一个产品族。

·可以说,抽象工厂模式和工厂方法模式的区别就在于需要创建对象的复杂程度上。而且抽象工厂模式是三个里面最为抽象、最具一般性的。

·抽象工厂模式的用意为:给客户端提供一个接口,可以创建多个产品族中的产品对象

·而且使用抽象工厂模式还要满足下条件:

系统中有多个产品族,而系统一次只可能消费其中一族产品。

同属于同一个产品族的产品以其使用。

·来看看抽象工厂模式的各个角色(和工厂方法的如出一辙):

抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。 java 中它由抽象类或者接口来实现

具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。 java 中它由具体的类来实现。

抽象产品角色:它是具体产品继承的父类或者是实现的接口。 java 中一般有抽象类或者接口来实现。

具体产品角色:具体工厂角色所创建的对象就是此角色的实例。 java 中由具体的类来实现。 




2. 工厂模式应用

2.1 初始代码:

/*
 * 发型接口
 */
public interface HairInterface {
   //实现了发型
	public void draw();
}
/*
 * 实现左偏分发型
 */
public class LeftHair implements HairInterface{
	//画了一个左偏分发型
	public void draw() {
		System.out.println("左偏分发型");
	}
}
/*
 * 实现右偏分发型
 */
public class RightHair implements HairInterface {
	//画了一个右偏分发型
	public void draw() {
		System.out.println("右偏分发型");
	}
}
public class Test {
    public static void main(String[] args) {
    	HairInterface left=new LeftHair();
    	left.draw();
    	HairInterface right=new RightHair();
    	right.draw();
    }
}
输出:

左偏分发型

右偏分发型

2.2 改进一:(加入工厂类)

/*
 * 发型工厂
 */
public class HairFactory {
     //根据类型来创建对象
	public HairInterface getHair(String key) {
		if("left".equals(key))
			return new LeftHair();
		else if("right".equals(key))
			return new RightHair();
		return null;
	}
}
//此时测试类即相应发生改变:
public class Test {
    public static void main(String[] args) {
//    	HairInterface left=new LeftHair();
//    	left.draw();
//    	HairInterface right=new RightHair();
//    	right.draw();
    	HairFactory factory=new HairFactory();
    	HairInterface left=factory.getHair("left");
    	left.draw();
    	HairInterface right=factory.getHair("right");
    	right.draw();
    }
}

此时做到了产品实现和客户端相分离。

2.3 改进二:

加入反射,根据类的名称来创建实例对象

package com.imooc.pattern.factory;
/*
 * 发型工厂
 */
public class HairFactory {
	//根据类的名称来创建对象
	public HairInterface getHairByClass(String className) throws Exception{
    	HairInterface hair=(HairInterface)Class.forName(className).newInstance();
    	return hair;
    }
}
//此时的测试类应该做出相应的改变:
public class Test {
    public static void main(String[] args) {	
    	HairFactory factory=new HairFactory();
    	try {
			HairInterface hair=factory.getHairByClass("com.imooc.pattern.factory.LeftHair");
		    hair.draw();
    	} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
    }
}

输出:

左偏分发型

  此时如果增加了一个发型的类,只需要创建该类,并在Test程序(客户端)中调用该类的类名,即可实现,无需修改HairFactory类中的内容。

    此时有个问题就是类名(包括路径名)太长,不直观。可以用映射来解决。

2.4 改进三

  加入Properties文件,进行映射。Properties文件是java特有的属性文件,以key-value的形式来存储数据。用等号连接左右,左边是key,右边是value。如下:


创建一个类PropertiesReader来读取type.properties文件中的内容。


/*
 * Properties文件的读取类
 */
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

public class PropertiesReader {

	public Map<String, String> getProperties(){
		Properties pros=new Properties();
		Map<String, String> map=new HashMap<String,String>();
		
		try {
			InputStream in=getClass().getResourceAsStream("type.properties");
			pros.load(in);
			Enumeration en=pros.propertyNames();
			while(en.hasMoreElements()) {
				String key=(String)en.nextElement();
				String property=pros.getProperty(key);
				map.put(key, property);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}		
		return map;
	}
}
//修改Factory类:
import java.util.Map;
/*
 * 发型工厂
 */
public class HairFactory {
	//根据类的名称来创建对象
	public HairInterface getHairByClassKey(String key) throws Exception{
    	Map map=new PropertiesReader().getProperties();
    	String className=(String)map.get(key);
		HairInterface hair=(HairInterface)Class.forName(className).newInstance();
    	return hair;
    }
}
//Test类修改:
public class Test {
    public static void main(String[] args) {
    	try {
			HairInterface hair=factory.getHairByClassKey("left");
		    hair.draw();
    	} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 	
    }
}

输出:

左偏分发型

如果此时增加一种发型,如InHair

public class InHair implements HairInterface {
	public void draw() {
		System.out.println("中分发型");
	}
}

此时如果要使用这个类,需要进行的修改有:

①在type.properties文件中增加键值对映射:

in=com.imooc.pattern.factory.InHair

②在Test类(客户端)调用该key

     HairInterface hair=factory.getHairByClassKey("in");

输出:

中分发型

此时的扩展不需要改变Factory类,变得容易多了。

此时对下图做对应:

Test类即客户端,HairFactory类对应creater, HairInterface接口对应Iproduct, LeftHair RightHairInHair类对应下面的product1,product2,product3.

3. 抽象工厂模式应用

3.1 人物实现接口(抽象工厂 Factory

package com.imooc.pattern.factory;
/*
 * 人物实现接口
 */
public interface CharacterFactory {
    public Boy getBoy();
    public Girl getGirl();
}

3.2 具体实现工厂(ConcreteFactory)

package com.imooc.pattern.factory;
/*
 * 圣诞系列加工厂
 */
public class ChristmasCharacterFactory implements CharacterFactory {

	public Boy getBoy() {
		return new ChristmasBoy();
	}

	public Girl getGirl() {
		return new ChristmasGirl();
	}
}
package com.imooc.pattern.factory;
/*
 * 新年系列加工厂
 */
public class NewYearCharacterFactory implements CharacterFactory {

	public Boy getBoy() {
		return new NewYearBoy();
	}

	public Girl getGirl() {
		return new NewYearGirl();
	}

}

3.3 人物实现接口(产品族 AbstractProduct Interface

/*
 * 男孩
 */
public interface Boy {
    public void drawMan();
}
/*
 * 女孩
 */
public interface Girl {
    public void drawWoman();
}

3.4 具体人物实现(ConcreteProduct)

/*
 * 圣诞系列男孩
 */
public class ChristmasBoy implements Boy {
	public void drawMan() {
		System.out.println("圣诞系列男孩");
	}
}
/**
 * 圣诞系列女孩
 */
public class ChristmasGirl implements Girl {
	public void drawWoman() {
		System.out.println("圣诞系列女孩");
	}

}
/*
 * 新年系列男孩
 */
public class NewYearBoy implements Boy {
	public void drawMan() {
		System.out.println("新年系列男孩");
	}
}
/*
 * 新年系列女孩
 */
public class NewYearGirl implements Girl {
	public void drawWoman() {
		System.out.println("新年系列女孩");
	}
}

对应以下类图

Test对应ClientcharacterFactory接口对应FactoryChristmasCharacterFactory类和NewYearCharacterFactory类对应ConcreteFactoryBoy接口和Girl接口对应AbstractProduct

NewYearBoyNewYearGirlChristmasBoyChristmasGirl类对应ConcreteProduct

4.总结



工厂方法模式和抽象工厂模式对比

·工厂模式是一种极端情况的抽象工厂模式,而抽象工厂模式可以看成是工厂模式的推广。

·工厂模式用来创建一个产品的等级结构,而抽象工厂模式是用来创建多个产品的等级结构

·工厂模式只有一个抽象产品类,而抽象工厂模式有多个抽象产品类

 




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章