泛型与模式
模式是面向对象技术深入应用的产物,它给了我们代码极大的动态性和灵活性,保证了系统的可扩展性。这是我们在面向对象分析和编码过程中广泛的使用模式的最重要的原因。但是实际的系统的需求是复杂的,复杂到我们使用了成熟的面向对象技术和模式后,仍然还有很多动态性能无法解决。
当我们的模式在一定程度上仍然解决不了我们的应用对技术的要求的时候,我们的一个直观解决思路就是在模式的基础上再加入其他的动态编程技术,使得我们的代码比在应用模式的基础上更加灵活,来满足我们的应用的要求。
这种解决思路的一个方案是:反射+模式。由于反射能在运行期内获取对象的类的内部信息,运用这些内部信息,能实现代码的极大动态性。其中的典型例子有:反射+工厂模式,使得系统在增加产品后,连工厂类也无需作修改。其二是:反射+代理模式,使得代理类不再依赖某一特定的接口,最重要的是可以在运行期内确定代理类的功能。关于“反射+代码”的详细分析和例子,这里不再给出。请大家查询相关资料。
这种思路的另外一个方案就是本文要阐述的:泛型+模式。其实泛型的概念挺广泛的,课题也比较大,不是一两句话能说明白的。但可以简单的理解,泛型就是类型的参数化,即把类型作为参数交给类的使用者来确定,由使用者给定;还有一种理解就是类型的多态,我们平常所说得多态包括参数的多态和方法的多态,而泛型则将多态扩展到类型的多态。
对本文的阅读要求读者有模式和Java泛型的基础。文中所涉及到的模式和泛型的知识,都不会详细地说明它们的来龙去脉,请大家查阅相关文档。
工厂模式是我们用得最为普遍的一种模式,其中最为简单易用的还是简单工厂模式。假设我们有如下一个接口:
public interface Intf1 {
public void do1();
}
有一个对象实现了该接口,如下:
public class Cls1 implements Intf1 {
public void do1() {
// TODO Auto-generated method stub
System.out.println("cls1:do1");
}
}
那么我们的工厂类为:
public class Factory {
public static Intf1 getIntance(String type)
{
if(type.equals("1"))
{
return new Cls1();
}
else
{
return null;
}
}
}
这就是简单工厂模式,如果还有其他的产品,则需要修改工厂类,以便它能够生产其他的产品对象。这种简单工厂模式的想法十分的简单,很容易实现,但对于复杂的实际需要来说,往往是不够用的。
现在假设我们有一个新的需求,需要扩展Intf1接口,以便实现更多的功能,如下:
public interface Intf2 extends Intf1 {
public void do2();
}
现在同样有一个Cls2类,来实现Intf2接口,如下:
public class Cls2 implements Intf2 {
public void do2() {
// TODO Auto-generated method stub
System.out.println("cls2:do2");
}
public void do1() {
// TODO Auto-generated method stub
System.out.println("cls2:do1");
}
}
现在我们的Factory类能够生产Cls2类的产品对象吗?我们将该工厂类作如下修改:
public class Factory {
public static Intf1 getIntance(String type)
{
if(type.equals("1"))
{
return new Cls1();
}
else if(type.equals("2"))
{
return new Cls2();
}
else
{
return null;
}
}
}
这样做当然可以,但不过在使用工厂的时候,要注意工厂生产的是Intf1接口的实例,要想Intf2接口的实例得对该实例进行强制类型转化。如下:
Intf2 obj = (Intf2)Factory.getInstance(“2”);
如果我们不想进行这种枯燥的强制类型转化呢?我们知道,我们可以使用泛型来解决这个问题。
public class Factory{
public static U getInstance(String clsName)
{
if(clsName.equals("1"))
{
return (U) new Cls1();
}
else if(type.equals("2"))
{
return (U)new Cls2();
}
else
{
return null;
}
}
}
现在我们来为这个工厂类写测试代码,如下:
Intf1 i1 = Factory.getInstance("2");
i1.do1();
System.out.println("---------------");
Intf2 i = Factory.getInstance("2");
i.do1();
i.do2();
测试结果为:
cls1:do1
---------------
cls1:do1
cls1:do2
可以看出,在使用了泛型的简单工厂类里,的确没有了强制类型转化的烦恼。
如果说上面的例子只是对泛型应用到模式的小试牛刀而已,那么下面的这个例子则可以让我们深刻的体会到泛型技术的精妙之处。
在我前面的
Blog
:
动态的工厂模式中,我提到了将反射和工厂模式结合起来,如下:
public class Factory{
public static Animal getInstance(String clsName)
{
try
{
Class cls = Class.forName(clsName);
return (Animal) cls.newInstance();
}
catch(Exception e)
{
e.printStackTrace();
return null;
}
}
}
这是一个简单的可扩展的工厂模式,它比简单工厂模式优越的地方在于,不论有多少产品,都无需修改工厂类。
但是,它也有一个要求,就是所有的产品都必须实现同一个接口,对于不同的接口的实现类,如下:
package fanxing.factory.dynaFactory;
public interface Intf1 {
public void do1();
}
下面是它的实现类:
package fanxing.factory.dynaFactory;
public class Cls1 implements Intf1 {
public void do1() {
// TODO Auto-generated method stub
System.out.println("cls1...");
}
}
另外一个接口:
package fanxing.factory.dynaFactory;
public interface Intf2 {
public void do2();
}
其实现类:
package fanxing.factory.dynaFactory;
public class Cls2 implements Intf2 {
public void do2() {
// TODO Auto-generated method stub
System.out.println("cls2...");
}
}
对于这样的两个接口:Intf1和Intf2,我们最直接的解决办法是提供两个工厂:一个工厂类来生产实现了Intf1接口的类的产品;另外一个生产实现了Intf2接口的类的产品。
面对这样的解决方法,我们多少有些心不甘,这些工厂类都是代码相同,功能相同,只是生产的产品的类型不同,难道我们就不能放在一个工厂类里吗?
学过泛型以后,我们知道,这些只有返回值的类型不同的方法不正是类型的多态吗?而这种多态不正是泛型要解决的问题?
public class Factory{
public static U getInstance(String clsName)
{
try
{
Class> cls = Class.forName(clsName);
return (U) cls.newInstance();
}
catch(Exception e)
{
e.printStackTrace();
return null;
}
}
}
可以看出,泛型正是解决这种类型多态的最好的工具,其解决方案也非常的简单。测试代码如下:
Intf1 i1 = Factory.getInstance("fanxing.factory.dynaFactory.Cls1");
i1.do1();
Intf2 i2 = Factory.getInstance("fanxing.factory.dynaFactory.Cls2");
i2.do2();
测试结果为:
cls1...
cls2...
可以看出,正是我们在这种工厂模式基础上增加了泛型,使得原来的工厂类突破了对接口的依赖,应用了泛型的这种工厂模式可以生产任意接口的产品。
Visitor模式的使用有一个要求,就是要求Visitor接口是不变的,即不希望Visitor接口的功能被扩展,Visitor接口的实现类要访问的内容是固定的。因为该模式会变化的是Visitor接口的实现类,可以让Visitor接口的实现类实现多样的功能,Visitor接口的实现类可以对Visitor接口的实现类要访问的固定内容作各种各样的动作。下面给出一个例子:
public interface Visitable {
public void accept(Visitor visitor);
}
这是Visitable接口,为实现该模式而必须的接口。
public interface Visitor {
public void visitString(StringElement stringE);
public void visitFloat(FloatElement floatE);
}
Visitor接口,定义要访问的内容,这里有两个:StringElement和FloatElement。
public class FloatElement implements Visitable {
private Float value;
public FloatElement(Float value){
this.value = value;
}
public Float getValue(){
return value;
}
//定义accept的具体内容 这里是很简单的一句调用
public void accept(Visitor visitor) {
visitor.visitFloat(this);
}
}
public class StringElement implements Visitable {
private String value;
public StringElement(String string) {
value = string;
}
public String getValue(){
return value;
}
//定义accept的具体内容 这里是很简单的一句调用
public void accept(Visitor visitor) {
visitor.visitString(this);
}
}
上面的两个类是对要访问的内容的定义。
下面我们来看Visitor接口的实现类:
public class ConcreteVisitor implements Visitor {
public void visitString(StringElement stringE) {
System.out.println("'"+stringE.getValue()+"'");
}
public void visitFloat(FloatElement floatE){
System.out.println(floatE.getValue().toString()+"f");
}
}
这个实现类道出了Visitor模式灵活的原因,可以不用修改以上任何类,就可以对访问内容作任何其他的处理,如我想把StringElement的值跟”Hello”比较,把FloatElement的值除以10。那么我们可以另外写一个类来实现Visitor接口,如下:
public class ConcreteVisitor1 implements Visitor {
public void visitString(StringElement stringE) {
System.out.println(stringE.getValue().equals(“Hello”));
}
public void visitFloat(FloatElement floatE){
System.out.println(floatE.getValue()/10);
}
}
但是,如果我想突破Visitor模式的限制呢?我想让被访问的内容变得可以扩展,例如,我想让上面的例子可以访问一个int类型的数据。怎么办?
泛型同样可以帮我们的大忙!
首先,我们来扩展Visitor接口:
public interface SonVisitor extends Visitor {
public void visitInt(IntElement intE);
}
然后,我们前面的Visitable接口只能接受Visitor类型的对象,我们需要对它作一定的扩展,所用的技术当然是泛型技术:
public interface Visitable {
public void accept(U visitor);
}
这样,我们的Visitable接口的accept方法就能接受任何的扩展自Visitor接口的其他接口的实现。
然后我们来实现那个在SonVisitor里面扩展的内容:
public class IntElement implements Visitable {
private int value;
public IntElement(int value){
this.value = value;
}
public int getValue(){
return value;
}
//定义accept的具体内容 这里是很简单的一句调用
public void accept(SonVisitor visitor) {
visitor.visitInt(this);
}
}
最后是SonVisitor接口的实现类:
public class SonVisitorImpl implements SonVisitor {
public void visitInt(IntElement intE) {
// TODO Auto-generated method stub
System.out.println(String.valueOf(intE.getValue())+":int");
}
public void visitString(StringElement stringE) {
// TODO Auto-generated method stub
System.out.println("'"+stringE.getValue()+"'");
}
public void visitFloat(FloatElement floatE) {
// TODO Auto-generated method stub
System.out.println(floatE.getValue().toString()+"f");
}
}
我们可以看到,的确是扩展了一个对int数据的操作。
下面我们来看测试类:
Visitor visitor = new ConcreteVisitor();
StringElement stringE = new StringElement("I am a String");
visitor.visitString(stringE);
System.out.println("---------------------------");
SonVisitor v = new SonVisitorImpl();
IntElement intE = new IntElement(12345);
v.visitInt(intE);
在这个测试代码中,前面一个我们实现的是Visitor接口,它只是访问两个内容;后面实现的是SonVisitor接口,而它能够访问三个内容。
测试结果如下:
'I am a String'
---------------------------
12345:int
可以看到,的确是实现了对Visitor模式的扩展。从以上的例子中,我们看到了结合了泛型的模式的强大的功能。
说了这么多,本文到此该结束了。再多说两句作为本文的结束语吧!,我们在使用模式的过程中,如果遇到了因为类型的多态而影响了系统的扩展,不妨考虑一下结合泛型来使用吧!