概要
- 这里试图描述23个设计模式中的两个工厂(Factory)相关的设计模式:工厂方法(Factorymethod),抽象工厂(Abstract factory)。
- 注意点:
- 这两个都属于创建型设计模式。
- 由于这两个设计模式都是建立在简单工厂(Simple factory)模式之上。我们先介绍简单工厂模式,值得注意的是简单工厂模式并不包含在23个设计模式之中。
- 文章介绍顺序:简单工厂,工厂方法,抽象工厂。
简单工厂
- 定义:由一个工厂对象决定创建出哪一种产品类的实例。
- 类型:创建型,但不属于GoF23种设计模式。
- 适用场景:工厂类负责创建的对象比较少;客户端(应用层)只知道工厂类的传参,对于如何创建对象(逻辑)不关心
- 优点:只需要传入正确参数,就可以获取我们所需的对象,而无须知道其具体细节。
- 缺点:工厂类的职责相对过重,增加新的产品需要修改工厂类的判断逻辑,违背开闭原则。
预设场景:书店现在有三类书籍:IT,Art,Literature。每种书都能被厂家生产。这三种书(三个类)有公共的抽象方法produce。我们现在想要得到这三个类。并且调用它们的公共方法。
首先声明一个抽象类,有一个抽象方法,让这三个类都继承这个类。同时声明一个工厂类,负责接收外部的传参,并根据外部的参数,返回不同的类。于是可以得到如下UML图:
通过图我们很容易地看懂几个类之间的关系,下面是这些类的代码:
/*公用的抽象类*/
public abstract class Book {
public abstract void produce();
}
/*********************三种书类**********************/
//IT类
public class ITBook extends Book {
@Override
public void produce() {
System.out.println("Produce a IT book");
}
}
//Art类
public class ArtBook extends Book {
@Override
public void produce() {
System.out.println("Produce a Art book");
}
}
//Literature类
public class LiteratureBook extends Book {
@Override
public void produce() {
System.out.println("Produce a Literature book");
}
}
/*******************重点:工厂类,负责以上三个类的对象生产****************/
public class BookFactory {
// 通过外部已知类来生产对象实例
public static Book produceBook(Class c){
Book book = null;
try {
book = (Book) Class.forName(c.getName()).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return book;
}
//通过传入字符串来判断生产哪种对象
public static Book produceBook(String bookType){
if( "IT".equalsIgnoreCase(bookType) )
return new ITBook();
else if( "Art".equalsIgnoreCase(bookType) )
return new ArtBook();
else if( "Literature".equalsIgnoreCase(bookType) )
return new LiteratureBook();
return null;
}
}
/*******************测试类************************/
public class Test {
public static void main(String[] args) {
//通过字符串判断对象类型
Book book = BookFactory.produceBook("IT");
book.produce();
//通过Class类来判断类型
Book book1 = BookFactory.produceBook( ArtBook.class );
book1.produce();
//推荐设置factory类内函数为static类型,这样不必像下面那样“生产”前必须要先实例化一个工厂类。
BookFactory factory = new BookFactory();
Book book2 = BookFactory.produceBook( LiteratureBook.class );
book2.produce();
}
}
这里值得注意的是我们让Factory类内部的对外函数为static,这样方便外部直接生成相关类的实例,而不需要先为Factory类实例化。
工厂方法
定义:定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类;工厂方法让类的实例化推迟到子类中进行。
类型:创建型。
适用场景:
1. 创建对象需要大量重复的代码
2. 客户端(应用层)不依赖产品类实例如何被创建,实现等细节。
3. 一个类通过其子类来指定创建哪个对象。
优点:
1. 用户只需要关心所需产品对应的工厂,无须关心创建细节。
2. 加入新产品符合开闭原则,提高可扩展型。
缺点:
1. 类个数过多,增加复杂性。
2. 增加系统的抽象性和理解程度。
工厂方法。工厂就是前面我们简单工厂所说的用于创建对象实例的统一工厂类;方法就是我们把工厂类生产各种对象实例的方法抽离出来,专门放到一个抽象类里面作为并不具体实现的抽象方法,如此,我们需要生产什么类的对象,我们就定义一个专门生产该类对象的工厂类,该类显然应该继承自我们所说的抽象类。
于是我们对于简单工厂的例子场景,可以得到下面的UML图:
上图,重点关注到:第三层的ITBookFactory,ArtBookFactory,LiteratureBookFactory分别与底层的ITBook,ArtBook,LituratureBook有<< create >>关系。而这三个Factory类都继承自底层的BookFactory,使这三个类重写Book produceBook()
这个方法用于生产各自独特的对象。
下面是用例的代码段(Book,ITBook,ArtBook,LiteratureBook类没有在下面列出,具体查看上面):
/*抽象工厂类,并不生产对象。而由具体的子类来生产。
符合开闭原则:如果新增工厂,只需新增一个类并集成自这个类即可
*/
public abstract class BookFactory {
public abstract Book produceBook();
}
/***********三个具体工厂类,用于生产不同类的对象******************/
// 专门生产ITBook对象
public class ITBookFactory extends BookFactory {
@Override
public Book produceBook() {
return new ITBook();
}
}
// 专门生产ArtBook对象
public class ArtBookFactory extends BookFactory {
@Override
public Book produceBook() {
return new ArtBook();
}
}
// 专门生产LiteratureBook对象
public class LiteratureBookFactory extends BookFactory {
@Override
public Book produceBook() {
return new LiteratureBook();
}
}
/********************测试类************************/
public class Test {
public static void main(String[] args) {
BookFactory itFactory = new ITBookFactory();
BookFactory artFactory = new ArtBookFactory();
BookFactory literatureFactory = new LiteratureBookFactory();
Book book1 = itFactory.produceBook();
book1.produce();
Book book2 = artFactory.produceBook();
book2.produce();
Book book3 = literatureFactory.produceBook();
book3.produce();
}
}
代码很容易理解。在外部(Test类),我们只需要知道抽象工厂类即可,并将抽象工厂类的引用指向具体的工厂类。 并调用相应的方法生产对应工厂即可以生产的对象即可。
抽象工厂
定义:抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无须制定它们具体的类。
类型:创建型
适用场景:
1. 客户端(应用层)不依赖产品类实例如何被创建,实现等细节;
2. 强调一系列相关产品对象(同一产品族)一起使用对创建对象需要大量重复的代码
3. 提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。
优点:
1. 具体产品在应用层代码隔离,无须关心创建细节
2. 将一个系列的产品族统一到一起创建
缺点:
1. 规定了所有可能被创建的产品集合,产品族中扩展新的产品困难,需要修改抽象工厂的接口。
2. 增加了系统的抽象性,理解难度。
引入两个概念:产品等级结构与产品族。下面是他人的讲解文章,讲解的很好:
产品等级结构与产品族
预设场景:豆瓣读书场景:现有IT,Art两个种类的书籍(Book),并且每种书籍都会有相
关的用户的读后感或者对这本书的评价,我们统称为Article。
开始设计。一个工厂能生产同一产品族的产品:Book,Article。 比如说:ITBookFactory生产ITBook,ITArticle;而ArtBookFactory生产ArtBook,ArtArticle。
其中,ITBook与ArtBook属于同一产品等级结构,ITArticle与ArtArticle亦如此;
图最容易理解了,下面是类关系UML图:
从图中我们可以看到Test类只需要与ITBookFactory和ArtBookFactory接触并获取相应的类。而 这两个工厂类都实现BookFactory接口,这个接口定义了两个规范:必须能够生产Book和Article。 从图中的ITBookFactory和ArtBookFactory工厂类可以看到他们分别都能创建 (<< create >>) Article,Book的对象实例。
上代码。
定义同一产品族内,不同产品等级结构中,各不同产品所要实现的与之对应的接口:
/****两个接口(Interface)Book,Article分别定义两个实体的公共动作***/
/****为方便起见,接口的逻辑实现仅仅用一个简单函数来占位**********/
// Book 接口:定义Book的统一动作逻辑
public interface Book {
void produce();
}
// Article 接口:定义Article的统一动作逻辑
public interface Article {
void produce();
}
定义实现接口的实体:
/***具体实现接口的实体类****/
/**IT方面的**/
public class ITBook implements Book {
@Override
public void produce() {
System.out.println("Produce a IT book.");
}
}
public class ITArticle implements Article {
@Override
public void produce() {
System.out.println("Produce a IT Article.");
}
}
/**Art方面的**/
public class ArtBook implements Book {
@Override
public void produce() {
System.out.println("Produce a art Book.");
}
}
public class ArtArticle implements Article {
@Override
public void produce() {
System.out.println("Produce a art Article.");
}
}
/**定义每个工厂都需要实现的接口;
*每个工厂生产的产品属于同一个产品族**/
public interface BookFactory {
Book produceBook();
Article produceArticle();
}
定义工厂协议(接口),每个工厂都需要遵循这个协议:每个工厂都能够生产Book,Article,只是不同工厂生产不同类型的Book,Article而已。
/**定义具体的工厂(相当不同的厂商),生产同一产品族(同一厂商)的产品**/、
/**由于这些工厂类(厂商)都遵循同一个协议(实现同一个接口),
*他们都能生产各种相同类型的产品(Book,Article),只是他们生产具体产品的方式不同
*而已(IT工厂生产ITBook,ITArticle,而Art工厂生产ArtBook,ArtArticle。**/
/**属于ITBook工厂(ITBookFactory)生产的产品族有:ITBook,ITArticle**/
public class ITBookFactory implements BookFactory {
@Override
public Book produceBook() {
return new ITBook();
}
@Override
public Article produceArticle() {
return new ITArticle();
}
}
/**属于ArtBook工厂(ArtBookFactory)生产的产品族有:ArtBook,ArtArticle**/
public class ArtBookFactory implements BookFactory {
@Override
public Book produceBook() {
return new ArtBook();
}
@Override
public Article produceArticle() {
return new ArtArticle();
}
}
下面是测试类:
import java.util.Random;
/**
* 两种方法进行测试:
* 1. 普通测试。为了把两个类都覆盖到,是用if判断传参。
* 2. 反射机制。关键点:Class.forName(String className).newInstace();
*/
public class Test {
public static void main(String[] args) {
// 为了把两个类都测试到,我们新定义个两种test方法。
// 根据传入的参数不同进行不同类型的测试
// 5次,随机对一种测试方法进行测试
for(int i = 0 ; i < 5 ; i ++ ) {
double rand = new Random().nextDouble();
System.out.println( "================第"+ (i+1) + "次随机测试" + rand + "================\n" );
if ( rand < 0.5) {
test("IT");
test("Art");
} else {
test(ITBookFactory.class);
test(ArtBookFactory.class);
}
System.out.println( "================第"+ i + "次随机测试END=================================\n" );
}
}
private static void test(String type){
System.out.println("Calling \"void test(String type);\" ");
BookFactory bookFactory = null;
if( "IT".equalsIgnoreCase(type) ){
bookFactory = new ITBookFactory();
} else if( "Art".equalsIgnoreCase(type) ){
bookFactory = new ArtBookFactory();
} else {
return;
}
Book book = bookFactory.produceBook();
Article article = bookFactory.produceArticle();
book.produce();
article.produce();
System.out.println();
}
private static void test(Class c){
System.out.println("Calling \"void test(Class c);\" ");
BookFactory bookFactory = null;
try {
bookFactory = (BookFactory) Class.forName(c.getName()).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Book book = bookFactory.produceBook();
Article article = bookFactory.produceArticle();
book.produce();
article.produce();
System.out.println();
}
}