跨移动平台开发App的时候,要根据IOS和Android(或者其他平台)的各自的API来创建统一的接口,因为平台的差异性,往往需要大量的代码来统一。我们常用的跨平台的(游戏)开发工具,例如Cocos2dx和Unity3d,都帮我们实现了部分常用的接口,让我们大部分时候可以忽略平台的差异性。本文并不是要探究他们的实现方法,而是为这种平台差异带来的问题提供一种可行的解决方案——抽象工厂(Abstract Factory)模式。
抽象工厂(又名Kit)提供一种创建一系列相关或者相互依赖对象的接口,而无需指定它们具体的类。
假设我们需要将IOS和Android的触摸、键盘和加速仪三个功能各自封装成接口。例如:
public interface ITouch
{
float x { get;}
float y { get;}
}
public interface IKeyboard
{
char key { get;}
void Show ();
void Hide ();
}
public interface IAccelerometer
{
float x { get;}
float y { get;}
float z { get;}
}
那么两个平台就需要分别实现这些接口类型。
public class IOSTouch : ITouch
{
public float x
{
get{
//TODO
return 0;
}
}
public float y
{
get{
//TODO
return 0;
}
}
}
public class IOSKeyboard : IKeyboard
{
public char key
{
get{
//TODO
return 'I';
}
}
public void Show ()
{
//TODO
}
public void Hide ()
{
//TODO
}
}
public class IOSAccelerometer : IAccelerometer
{
public float x
{
get{
//TODO
return 0;
}
}
public float y
{
get{
//TODO
return 0;
}
}
public float z
{
get{
//TODO
return 0;
}
}
}
public class AndroidTouch : ITouch
{
public float x
{
get{
//TODO
return 0;
}
}
public float y
{
get{
//TODO
return 0;
}
}
}
public class AndroidKeyboard : IKeyboard
{
public char key
{
get{
//TODO
return 'N';
}
}
public void Show ()
{
//TODO
}
public void Hide ()
{
//TODO
}
}
public class AndroidAccelerometer : IAccelerometer
{
public float x
{
get{
//TODO
return 0;
}
}
public float y
{
get{
//TODO
return 0;
}
}
public float z
{
get{
//TODO
return 0;
}
}
}
接着,我们新建一个工厂接口类型,用于创建这些类:
public interface IFactory
{
ITouch CreateTouch();
IKeyboard CreateKeyboard();
IAccelerometer CreateAccelerometer();
}
分别实现IOS和Android的工厂类:
public class IOSFactory : IFactory
{
public ITouch CreateTouch()
{
return new IOSTouch();
}
public IKeyboard CreateKeyboard()
{
return new IOSKeyboard();
}
public IAccelerometer CreateAccelerometer()
{
return new IOSAccelerometer();
}
}
public class AndroidFactory : IFactory
{
public ITouch CreateTouch()
{
return new AndroidTouch();
}
public IKeyboard CreateKeyboard()
{
return new AndroidKeyboard();
}
public IAccelerometer CreateAccelerometer()
{
return new AndroidAccelerometer();
}
}
当我们使用的时候,我们首先要实现一个获取工厂的方法:
static readonly public string PLATFORM = "IOS";
IFactory GetFactory()
{
IFactory ret = null;
switch (PLATFORM) {
case "IOS":
ret = new IOSFactory();
break;
case "Android":
ret = new AndroidFactory ();
break;
}
return ret;
}
然后调用:
IFactory factory = GetFactory();
IKeyboard keyboard = factory.CreateKeyboard ();
keyboard.Show ();
由此,我们可以看出来,抽象工厂模式非常的重量级。有没有什么方法可以改善?
我们可以考虑使用反射来简化。现在,就是现在,抛弃之前的所有工厂,新建一个类:
public class RFactory
{
#if PLATFORM_ANDROID
static readonly public string PLATFORM = "Android";
#else
static readonly public string PLATFORM = "IOS";
#endif
private static object Create(string className)
{
string name = PLATFORM + className;
Type type = Type.GetType (name);
return Activator.CreateInstance (type);
}
public static ITouch CreateTouch()
{
return (ITouch)Create ("Touch");
}
public static IKeyboard CreateKeyboard()
{
return (IKeyboard)Create ("Keyboard");
}
public static IAccelerometer CreateAccelerometer()
{
return (IAccelerometer)Create ("Accelerometer");
}
}
然后,我们就可以这样使用:
IKeyboard kb = RFactory.CreateKeyboard ();
kb.Hide ();
当然,我们的开发环境一般是windows或者mac,所以PLATFORM需要根据不同的宏来赋值,windows或者mac下也应该有对应的触摸(鼠标)、键盘和加速计(假)的实现。而这些字符串都可以通过配置文件来配置,包括不同实现的命名空间(你可能注意到了,我们根据字符串创建类的时候,没有考虑命名空间),只需要根据不同的平台读取不同的配置文件即可。
最后再说两句:
其实抽象工厂和工厂方法…………………………………………没有区别还是有一些区别的。
我可以将IFactory分别拆成ITouchFactory、IKeyboardFactory和IAccelerometerFactory,并将IOS和Android的工厂也相应的拆掉,这样就变成了工厂方法。然而这样并没有什么意义,因为你不可能同时使用Android的触摸和IOS的键盘。
我们也可以把多个工厂方法合并,例如物品工厂和角色工厂合并在一起,这样就变成了抽象工厂。然而这同样也没有什么意义,也是错误的,因为你没有办法去限定一个情况下只创建道具实例和玩家实例,而另一种情况下只创建武器实例和NPC实例,而且违反了单一责任原则和接口隔离原则。
由此我们可以得出二者的区别:
抽象工厂创建的是多个相关关联的类,而工厂方法创建的是一个单独的类。
抽象工厂往往受限于环境,每个环境只会有一个具体的工厂类型发挥作用,而工厂方法一般不受限于环境,每个工厂类型都有可能发挥作用。
抽象工厂一般用于跨平台或者跨工具(例如不同的DB,不同的UI框架),而工厂方法往往用于程序具体逻辑。
关于工厂方法模式,参考小话设计模式(二)工厂方法模式。