设计模式剖析——抽象工厂模式Abstract Factory Pattern
含义
- 与工厂方法模式相比,概念有些不一样。有抽象零件、抽象产品、抽象工厂。用零件来组装出产品,组装出的产品才是完整的产品。
- 既然一个完整产品需要多个零件,那么每种具体工厂自然也需要可以同时生产出多种零件(指工厂里会有几种
createXXX
方法)。 - 强调什么抽象产品族类、抽象产品类是没有意义的。这只是因为继承树中,有一种抽象基类分化出两种更具体的抽象基类罢了。
- 也许你会用到泛型或者反射,但记住,用这些技术的目的是为了不让客户端了解到具体零件类或者具体产品类,而不是为了炫技。
UML图
还是觉得给出实际例子的UML图更加易懂吧。这里解释一下这些类的含义:
- nonConcrete包里有抽象的类;concrete包中有具体的类。
- 零件有手机phone、手机壳phoneShell、手机膜phoneFilm。当给手机套上壳、贴上膜的时候,才算这个一个完整的产品。
- phoneShell和phoneFilm继承了同一个接口item,因为它们都属于是配件一类的东西。
- 假定与oppo手机配套的是软壳softShell、非全屏膜almostFilm;与vivo手机配套的是硬壳hardShell、全屏膜fullFilm。
- 这三种零件的具体类都可以有更具体的说明,通过构造函数给出。
示例代码
首先是nonConcrete包中的抽象类:
package nonConcrete;
//item.java file
interface item {
String attribute();
}
//这是另一个文件的内容,但此处省略了package nonConcrete。后面将作同样处理,即省略package语句。
//phoneShell.java file
public abstract class phoneShell implements item{
protected String whichShell;
protected phoneShell(String in){
whichShell = in;
}
}
//phoneFilm.java file
public abstract class phoneFilm implements item{
protected String whichFilm;
protected phoneFilm(String in){
whichFilm = in;
}
}
//phone.java file
public abstract class phone {
public String whichVision;
public phoneShell myShell;
public phoneFilm myFilm;
protected phone(String in){
whichVision = in;
}
public phone addShell(phoneShell shell){
myShell = shell;
return this;
}
public phone addFilm(phoneFilm film){
myFilm = film;
return this;
}
public abstract void boot();//开机
}
//factory.java file
public abstract class factory {
public static factory getFactory(String classname){
factory f = null;
try {
f = (factory)Class.forName(classname).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return f;
}
public abstract phoneShell createShell(String shellType);
public abstract phoneFilm createFilm(String filmType);
public abstract phone createPhone(String phoneType);
}
- 注意到抽象手机类phone有三个成员变量,但构造函数只会初始化其中一个。其余两个成员交给其另外两个成员函数来添加,且为了方便流式操作,所以这两个函数都
return this
。 - 抽象工厂类factory定义好了三种抽象方法,分别返回三种零件。还有一个静态方法
getFactory
,用来反射出具体工厂类的对象。这样便可以避免客户端了解到具体工厂类。 - 上一条如果不用反射,我想只能通过内部类和静态成员来解决:用静态内部类来继承并实现factory,因为从逻辑上来讲这里不应该持有外部类对象的引用(或者使用在static context语境下的匿名内部类,这种情况也不会持有外部类对象的引用);然后实例化静态内部类,并将实例向上转型赋值给一个factory类型的静态成员变量。
然后是concrete包中的具体类:
package concrete;
//softShell.java file
import nonConcrete.phoneShell;
class softShell extends phoneShell {
softShell(String type){
super(type);
}
@Override
public String attribute(){//实现了item接口的方法
return this.whichShell+"软壳";
}
}
//hardShell.java file
import nonConcrete.phoneShell;
class hardShell extends phoneShell {
hardShell(String type){
super(type);
}
@Override
public String attribute(){
return this.whichShell+"硬壳";
}
}
//almostFilm.java file
import nonConcrete.phoneFilm;
class almostFilm extends phoneFilm {
almostFilm(String type){
super(type);
}
public String attribute(){
return this.whichFilm+"非全屏膜";
}
}
//fullFilm.java file
import nonConcrete.phoneFilm;
class fullFilm extends phoneFilm {
fullFilm(String type){
super(type);
}
public String attribute(){
return this.whichFilm+"全屏膜";
}
}
//oppoPhone.java file
import nonConcrete.phone;
class oppoPhone extends phone {
oppoPhone(String in){
super(in);
}
@Override
public void boot(){
System.out.println("oppo"+this.whichVision+"手机:带有"+this.myShell.attribute()+
"和"+this.myFilm.attribute());
}
}
//vivoPhone.java file
import nonConcrete.phone;
class vivoPhone extends phone {
vivoPhone(String in){
super(in);
}
@Override
public void boot(){
System.out.println("vivo"+this.whichVision+"手机:带有"+this.myShell.attribute()+
"和"+this.myFilm.attribute());
}
}
//oppoFactory.java file
import nonConcrete.*;
public class oppoFactory extends factory {
public phoneShell createShell(String shellType){
return new softShell(shellType);
}
public phoneFilm createFilm(String filmType){
return new almostFilm(filmType);
}
public phone createPhone(String phoneType){
return new oppoPhone(phoneType);
}
}
//vivoFactory.java file
import nonConcrete.*;
public class vivoFactory extends factory {
public phoneShell createShell(String shellType){
return new hardShell(shellType);
}
public phoneFilm createFilm(String filmType){
return new fullFilm(filmType);
}
public phone createPhone(String phoneType){
return new vivoPhone(phoneType);
}
}
注意两个具体工厂类的权限修饰符必须是public,如果不是,那么下面的factory oppoFac = factory.getFactory("concrete.oppoFactory");
将会报错java.lang.IllegalAccessException: Class nonConcrete.factory can not access a member of class concrete.oppoFactory with modifiers ""
,因为反射是在nonConcrete.factory的静态方法中进行的,需要在此处可以访问到concrete.oppoFactory。但那些具体零件类的权限可以是默认,因为它们只需要被具体工厂类访问到就好。
顺便看一下报错的调用栈,追一下源码:
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
at java.lang.Class.newInstance(Class.java:436)
//Reflection.java
public static void ensureMemberAccess(Class<?> var0, Class<?> var1, Object var2, int var3) throws IllegalAccessException {
if (var0 != null && var1 != null) {
if (!verifyMemberAccess(var0, var1, var2, var3)) {//调用此函数返回了假,说明没有通过验证
throw new IllegalAccessException("Class " + var0.getName() + " can not access a member of class " + var1.getName() + " with modifiers \"" + Modifier.toString(var3) + "\"");
}
} else {
throw new InternalError();
}
}
public static boolean verifyMemberAccess(Class<?> var0, Class<?> var1, Object var2, int var3) {
//var0是调用者,var1是被实例化者
boolean var4 = false;
boolean var5 = false;
if (var0 == var1) {
return true;
} else {
if (!Modifier.isPublic(getClassAccessFlags(var1))) {//先检查被实例者是不是public的
var5 = isSameClassPackage(var0, var1);//再检查是不是同一个包
var4 = true;
if (!var5) {
return false;
}
}
//后面还有,不贴上来了
}
然后是在默认包中的测试类:
import nonConcrete.*;
public class Main {
public static void main(String[] args) {
factory oppoFac = factory.getFactory("concrete.oppoFactory");
phoneShell oppoS = oppoFac.createShell("透明的");
phoneFilm oppoF = oppoFac.createFilm("抗蓝光的");
phone oppo = oppoFac.createPhone("R15").addShell(oppoS).addFilm(oppoF);//组装产品
oppo.boot();
factory vivoFac = factory.getFactory("concrete.vivoFactory");
phoneShell vivoS = vivoFac.createShell("抗摔的");
phoneFilm vivoF = vivoFac.createFilm("高清的");
phone vivo = vivoFac.createPhone("X27").addShell(vivoS).addFilm(vivoF);
vivo.boot();
}
}/*output:
oppoR15手机:带有透明的软壳和抗蓝光的非全屏膜
vivoX27手机:带有抗摔的硬壳和高清的全屏膜
*/
从测试类导入的包中可以看出,客户端不需要了解到任何具体类,当然这里也是多亏了反射的功劳。
优缺点
- 当抽象零件类不需要增加时符合【开闭原则】,只需要增加具体零件类和具体工厂类即可。
- 当抽象零件类需要增加时则不符合【开闭原则】,此时已存在的具体工厂类需要增加函数。对于本文例子,情况则可以是手机还需要镜头膜才能出厂。
- 上面两点总结为:开闭原则的倾斜性。
应用场景
- 在产品类中还需要有零件类的概念时。
- 工厂类中需要有多种创建不同种类产品的方法时。