五:抽象方法模式
抽象工厂模式算是工厂相关模式的终极形态,如果各位完全理解了上一章的工厂方法模式,那么抽象工厂模式就很好理解了。它与工厂方法唯一的区别就是工厂的接口里是一系列创造抽象产品的方法,而不再是一个,而相应的,抽象产品也不再是一个了,而是一系列相关的产品。这其实是工厂方法模式的一种扩展不是吗?
通常意义来我们谈到扩展,通常有两种方式可以扩展一个接口或者类,就是继承和组合。
通常情况下,我们推荐使用组合扩展一个现有的类或接口,但这并非绝对,如果你扩展的子类或子接口与现有的类或接口明显是“是一个(is a)”的关系,也就是继承的关系,那么使用继承可以获得更多的好处。
下面我们就首先来看一下抽象工厂模式的定义以及类图,全部引自百度百科。
定义:为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类。
定义中说了,我们是要创建一个接口, 而这个接口是干嘛的呢,前面说了,是为了创建一组相关或者相互依赖的对象,而且还有一点就是,我们创建的对象不是具体的类,也就是说我们创建的是一个接口或者一个抽象类。
下面我们来看看抽象工厂模式的类图。
我们对比下刚才的定义,lz给各位分析下上面的类图,首先刚才说了,我们要创建一个接口,这个接口就是指的creator,而一组相关或者相互依赖的对象,就是指的producta和productb以及它们具体的实现类,而上面又提到说不是返回的具体的类,所以我们返回的应该是接口或者抽象类,那么在上述类图当中,则是指的producta和productb接口。
下面lz将上述类图诠释成容易理解的java代码,供各位参考。
首先给出我们的产品族,也就是类图中右半部分。
package net; interface producta { void methoda(); } interface productb { void methodb(); } class producta1 implements producta{ public void methoda() { system.out.println("产品a系列中1型号产品的方法"); } } class producta2 implements producta{ public void methoda() { system.out.println("产品a系列中2型号产品的方法"); } } class productb1 implements productb{ public void methodb() { system.out.println("产品b系列中1型号产品的方法"); } } class productb2 implements productb{ public void methodb() { system.out.println("产品b系列中2型号产品的方法"); } }
结构比较清晰,下面是类图中左半部分,首先给出工厂接口。
package net; public interface creator { producta createproducta(); productb createproductb(); }
下面是两个具体的工厂实现类。
package net; public class concretecreator1 implements creator{ public producta createproducta() { return new producta1(); } public productb createproductb() { return new productb1(); } } package net; public class concretecreator2 implements creator{ public producta createproducta() { return new producta2(); } public productb createproductb() { return new productb2(); } }
这样我们的类图代码就实现完毕,下面我们写一个测试类,去调用一下,感受一下抽象工厂模式的客户端调用方式。
package net; public class client { public static void main(string[] args) throws exception { creator creator = new concretecreator1(); producta producta = creator.createproducta(); productb productb = creator.createproductb(); producta.methoda(); productb.methodb(); creator = new concretecreator2(); producta = creator.createproducta(); productb = creator.createproductb(); producta.methoda(); productb.methodb(); } }
在过程当中,我们切换过一次工厂实现类,而下面的代码是一模一样的,但是我们使用的就是另一套产品实现体系了,我们看运行结果。
上面的代码比较简单,结构很清晰但不太容易理解,因为它全部是抽象的表示,与实际联系不上,所以也会对各位的理解造成阻碍,下面我们就一起讨论一个现有的例子,去加深去抽象工厂模式的理解。
上一章我们介绍了iterable接口,它可以制作iterator,iterator方法是一个工厂方法,用于让子类制作一系列的iterator,不过java集合框架一般都将iterator的实现作为内部类出现,所以我们从未见过lz上章提到的listiterator和keyiterator的实现类,但它们确实存在于java的集合框架,并且它们的实现类被封装在相应的抽象类或者具体的容器实现类中。
oracle公司为何不让我们看到这些iterator的实现类呢?其实原因很简单,一是怕我们在写程序的时候依赖于这些iterator的实现类,二是这些迭代器的实现都要依赖于当前的容器实现,我们假设有一天jdk中的集合框架要升级,要替换掉某个iterator的实现,换做一种更快的迭代方式(假设存在这种方式),那么以前使用特定迭代器的程序可能就无法正常运行了。当然大部分的情况下,oracle不会将现有的类剔除,但是会加上@deprecated注解,来标识这是一个过时的东西,不再推荐你使用。但就算是这样,还是有缺点,就是jdk升级以后,你享受不到jdk集合框架速度上的提升,除非你将所有你使用过具体的iterator的地方全部手动替换掉。
上述大致描述了下集合框架设计时对iterator处理方式的初衷,从中可以看出抽象工厂模式就是为了解决抽象产品不再是一个的时候的问题。因为不管是简单工厂,还是工厂方法,都有一个缺陷,那就是整个模式当中只能有一个抽象产品,所以直观的,你在工厂方法模式中再添加一个创造抽象产品的方法就是抽象工厂模式了,相应的当然还有添加一个抽象产品,还有一系列具体的该抽象产品的实现。
在集合框架里,有一个不太明显的抽象工厂模式,就是list接口,它在iterable的基础上,扩展了一个创建产品的方法,本次以list接口为例,我们来看看list接口的源码。
package java.util; public interface list<e> extends collection<e> { iterator<e> iterator();//一种产品 object[] toarray(); <t> t[] toarray(t[] a); listiterator<e> listiterator();//另外一种产品 listiterator<e> listiterator(int index); }
lz去掉了list接口中的很多方法,一是为了节省版面,另外是为了更清晰,我们主要关注iterator和listiterator方法,lz在上面加了标注。
其中listiterator是iterator的子接口,但归根到底,它其实属于另外一种产品,为什么这么说呢,listiterator不是iterator的子接口吗,怎么能算是另外一种产品呢?这是因为我们listiterator方法的返回类型是listiterator,而不是iterator,所以两者的功能是不同的,比如listiterator还可以向前移动。
我们可以认为这两个方法产生的一个是只能向后移动的迭代器,一个是可以前后移动的迭代器,这算是两种产品,相当于上面的producta和productb。
这个设计可以看做是一个抽象工厂模式,list接口定义了两种生产不同产品的方法,这属于两个系列的产品,不过由于产品接口本身的继承关系,两者的实现类也会被做成继承的关系。下面给出上面提到的接口的uml图。
这个图看起来有点复杂,各位可以和上面标准的抽象工厂模式类图对比一下,下面lz来解释一下在抽象工厂模式当中,上述几个类都代表的什么角色。
1.list,是抽象工厂的角色,它有两个制造产品的方法,iterator和listiterator,相当于creator。
2.listiterator和iterator都是抽象产品,相当于producta和productb。其中listiterator有两个实现类,分别是abstractlist.listitr和linkedlist.listitr,相当于producta1和producta2。iterator的实现类为abstractlist.itr,相当于productb1,但是没有b2。
3.linkedlist是其中一个具体的工厂类,相当于concretecreator1,实现抽象工厂list,它制造的两个具体产品分别是linkedlist.listitr和abstractlist.itr。
4.同样的,arraylist也是一个具体的工厂类,相当于concretecreator2,实现抽象工厂list,它制造的两个具体产品分别是abstractlist.listitr和abstractlist.itr。
结合上一章工厂方法模式,我们来分析一下工厂方法模式和抽象工厂模式二者的关系。
iterable接口是list的父接口,所以它只负责一个产品iterator的制造,所以是工厂方法模式,而list接口扩展了iterable接口,又添加了一个制造产品的方法,即又添加了一个系列的产品,所以就成为了抽象工厂模式。
lz下面给出上述两个类图的对应关系,会让各位看的更加清晰:
1.creator=list
2.concretecreator1=arraylist
3.concretecreator2=linkedlist
4.producta=iterator
5.productb=listiterator
6.producta1=abstractlist.itr
7.producta2=无(具体的a产品2在第一个类图中是没有的,但这并不影响整个体系)
8.productb1=abstractlist.listitr
9.productb2=linkedlist.listitr
arraylist和linkedlist分别是list接口的两种实现,前者是基于数组操作,后者是基于链表。两者都可以产生iterator和listiterator,而iterator的实现都是在abstractlist中实现的,是一样的处理方式,而对于listiterator的实现却不相同,abstractlist.listitr是基于数组的操作,linkedlist.listitr是基于链表的操作方式。
所以抽象工厂模式一般是为了处理抽象产品多于一个的问题,而且这些产品多数情况下是有关系的,像上述java集合框架的例子当中,iterator和listiterator就是继承的关系,大部分情况下,很少会使用抽象工厂模式去创造一批毫无关系的产品。
基于抽象工厂一旦定义,抽象产品的个数就已经固定,所以最好在抽象产品的个数不太会变化的情况下使用抽象工厂模式,当然,我们可以使用继承去弥补抽象工厂模式的这一不足,创造另外一个继承体系去扩展现有的框架。
下面lz给出简单工厂模式,工厂方法模式一直到抽象工厂模式的演变过程,三者是由简到繁的关系。由于三者都已经详细的解释过,所以此处不再多做解释,留给各位读者自己思考它们的进化过程,首先lz给出简单工厂的具体代码。
//抽象产品 interface product{} //具体产品 class producta implements product{} class productb implements product{} //产品工厂(下一步就是它的进化,就变成了工厂方法模式) public class productfactory { private productfactory(){} public static product getproduct(string productname){ if (productname.equals("a")) { return new producta(); }else if (productname.equals("b")) { return new productb(); }else { return null; } } }
lz在上面加了简单的注释,下面lz给出工厂方法模式的代码,注意,前面有关产品的类和接口是不变的。
//抽象产品 interface product{} //具体产品 class producta implements product{} class productb implements product{} //将简单工厂中的工厂给抽象成接口 interface factory{ product getproduct(); } //具体的工厂a,创造产品a class factorya implements factory{ public product getproduct() { return new producta(); } } //具体的工厂b,创造产品b class factoryb implements factory{ public product getproduct() { return new productb(); } }
可以看到,产品部分并没有变化,只是将简单工厂中的工厂类抽象成接口,并给相应产品添加相应的工厂类,就进化成了工厂方法模式。下面我们再看工厂方法如何进化成抽象工厂模式。
//抽象产品 interface product{} //具体产品 class producta implements product{} class productb implements product{} //多了一个抽象产品1 interface product1{} //具体产品1 class product1a implements product1{} class product1b implements product1{} //原有的工厂方法模式的工厂里添加一个方法 interface factory{ product getproduct(); //添加另外一个产品族的创造方法 product1 getproduct1(); } //具体的工厂a,创造产品a class factorya implements factory{ public product getproduct() { return new producta(); } //添加相应的实现 public product1 getproduct1() { return new product1a(); } } //具体的工厂b,创造产品b class factoryb implements factory{ public product getproduct() { return new productb(); } //添加相应的实现 public product1 getproduct1() { return new product1b(); } }
与工厂方法对比下就发现,多了一个产品系列叫product1,工厂接口里多了一个方法,叫getproduct1,所以抽象工厂模式就是工厂方法模式添加了抽象产品所演变而来的。
有关工厂的三个模式到这里就全部介绍完了,三者有着很大的关联和明显的关系,要想灵活运用这三种设计模式,还是要彻底理解它们所针对的问题以及三者的关系。下面罗列下这三种设计模式依次进化的原因。
1,首先从简单工厂进化到工厂方法,是因为工厂方法弥补了简单工厂对修改开放的弊端,即简单工厂违背了开闭原则。
2,从工厂方法进化到抽象工厂,是因为抽象工厂弥补了工厂方法只能创造一个系列的产品的弊端。
各位可以思考下,假设我们不使用抽象工厂模式,改用工厂方法去处理抽象工厂中多产品的问题,如何处理呢?其实很简单,就是有几个产品系列,我们就造几个工厂方法模式就可以了,只不过这样处理未免太不优雅,就像下面这样。
//抽象产品 interface product{} //具体产品 class producta implements product{} class productb implements product{} //工厂接口 interface factory{ product getproduct(); } //具体的工厂a,创造产品a class factorya implements factory{ public product getproduct() { return new producta(); } } //具体的工厂b,创造产品b class factoryb implements factory{ public product getproduct() { return new productb(); } } /* 以上是一个产品的工厂方法 */ //抽象产品1 interface product1{} //具体产品1 class product1a implements product1{} class product1b implements product1{} //工厂接口1 interface factory1{ product1 getproduct1(); } //具体的工厂1a,创造产品1a class factory1a implements factory1{ public product1 getproduct1() { return new product1a(); } } //具体的工厂1b,创造产品1b class factory1b implements factory1{ public product1 getproduct1() { return new product1b(); } }
以上用两个工厂方法模式,代替了抽象工厂模式,那么可想而知,假设又多了一个产品product2,那么我们还需要再建立一套工厂方法模式,这显然会大大增加系统的复杂性,而且也不易于客户端操作。