为什么我们需要对象系统?
你是不是遇到这样的情况,当要保存对象时,通常会有一个基类,类似:
interface IAchive
{
void save(OutputStream out);
void load(InputStream in);
}
然后为每一个需要保存的类写重载save跟load方法,如果当你系统比较庞大时,你就需要为每个类重载一次。
还有我们在做GUI程序的时候,常常要把对象的属性导出到GUI界面查看或者编辑,这时你在初始化你的Property
界面的时候是不是会为每一个要导出的类写exportToGUIComponent(Component comp)这样的方法呢;
类似的还有很多,比如要导出接口给脚本等等。
如果我们用上面的方法,那会让这些工作变成枯燥的体力活,那怎么样才能简化这些操作呢?
这就是Object System存在的理由
假如我们的对象有这种能力:
registProperty(String propertyName, Class<?> type, String getterName, String setterName, int usage)
把对象的属性注册到某个地方,然后要用的时候通过接口把这些要用的对象一次性拿出来,这样我们就需要把属性抽象化
public interface IMetaProperty {
int USAGE_SAVE_TO_DB = 1 << 1;
int USAGE_GUI_BROSWER = 1 << 2;
int USAGE_SCRIPT_EXPORT = 1 << 3;
int USAGE_DEFAULT = USAGE_SAVE_TO_DB | USAGE_GUI_BROSWER | USAGE_SCRIPT_EXPORT;
void init(String name, Object owner, Method getter, Method setter, int usage);
void fromStream(PropertyInStream stream) throws Exception;
void toStream(PropertyOutStream stream) throws Exception;
String getName();
String getDesc();
void setDesc(String desc);
Object getValue();
void setValue(Object value);
boolean isWriteable();
boolean isReadable();
int getUsage();
}
稍微解释一下,我们通过反射拿到属性的getter和setter以便我们操作属性,usage是指属性的用途,
比如说:USAGE_SCRIPT_EXPORT说明这个属性是导出到脚本用的。
那我们的属性被注册到什么地方去了呢?我们每个对象都会带有一个叫MetaData的类,该类封装在我们的
顶级对象MetaObject中,该类存放所有注册进去的属性。也许你觉得一个对象一个MetaData太浪费空间了
,static不更好吗?当然,确实很好,这里只是讨论思想,而且由于这种方法在Java中不大好实现(其实也没多想^_^)
, 在C++的话用个模板就搞定了(像很多c++软件系统自带的RTTI一样),这里只作简单处理
public class MetaData {
private Map<String, MetaProperty> propertyList = new HashMap<String, MetaProperty>();
//...
public void registerProperty(String name, Class<?> type, Object owner, String getterName, String setterName, int usage)
public List<MetaProperty> getProperties(int usage)
public MetaProperty getProperty(String name)
}
这样我们就通过getProperties方法把所有属性拿出来,该干什么就干什么,比如要保存对象时只要调用toStream
方法就可以了,所有属性都一样处理。这是怎么做到的呢,如果自定义的类属性怎么办,PropertyInStream、PropertyOutStream 又是什么?
其实关键是我们在调用registerProperty的时候为每一种Class<?> type生成特定的IMetaProperty,
我们简单看下registerProperty的实现方法
public void registerProperty(String name, Class<?> type, Object owner, String getterName,
String setterName, int usage) throws SecurityException, NoSuchMethodException {
Method _getter = null != getterName ? owner.getClass().getMethod(getterName) : null;
Method _setter = null != setterName ? owner.getClass().getMethod(setterName, type) : null;
IMetaProperty property = MetaDataManager.getInstance().createProperty(type, name, owner, _getter, _setter, usage);
propertyList.put(name, property);
}
前面两行是初始化函数指针,创建实例的是MetaDataManager,MetaDataManager的存在就是帮助我们创建各种各样的IMetaProperty
,而且这些IMetaProperty都是可以定制的,这样我们的IMetaProperty就可以交给用户来扩展
public class MetaDataManager {
private Map<Class<?>, PropertyCreator> factory = new HashMap<Class<?>, PropertyCreator>();
private static MetaDataManager instance = new MetaDataManager();
private MetaDataManager() {
// int
registPropertyFactory(int.class, new PropertyCreator() {
@Override
public MetaProperty create() {
return new IntProperty();
}
});
// String
registPropertyFactory(String.class, new PropertyCreator() {
@Override
public MetaProperty create() {
return new StringProperty();
}
});
//...
}
public static MetaDataManager getInstance() {
return instance;
}
public void registPropertyFactory(Class<?> _class, PropertyCreator creator) {
factory.put(_class, creator);
}
public MetaProperty createProperty(Class<?> _class, String name, Object owner, Method getter, Method setter, int usage) {
if (null == _class || null == name)
return null;
PropertyCreator creator = factory.get(_class);
if (null == creator) {
throw new RuntimeException("Unsupport property type = " + _class.getName());
}
MetaProperty property = creator.create();
if (null != property) {
property.init(name, owner, getter, setter, usage);
}
return property;
}
}
系统在初始化的时候我们放入了最基本的boolean、int、String等Property,如果我们要定制属性
,可以通过registPropertyFactory(Class<?> _class, PropertyCreator creator)把类型跟对象关联起来
,就像我们在构造函数里做的那样。
我们简单看下boolean属性是怎么实现的
public class BooleanProperty extends CommonProperty {
@Override
public void fromStream(PropertyInStream stream) throws Exception {
setValue(stream.readBoolean().getValue());
}
@Override
public void toStream(PropertyOutStream stream) throws Exception{
stream.writeBoolean(getName(), (Boolean) getValue());
}
}
再来介绍一下PropertyInStream,该接口是我们序列化属性时用的,你可以扩展PropertyInStream为
BytesArrayInStream、XmlInStream或者时JsonInStream等等各种各样的存储格式
我们看看使用该系统的例子
class MyBaseObject extends MetaObject {
private int z;
boolean flag;
long time;
short shortNum;
float pi;
public MyBaseObject() {
try {
registerProperty("z", int.class, "getZ", "setZ", MetaProperty.USAGE_DEFAULT);
registerProperty("flag", boolean.class, "isFlag", "setFlag", MetaProperty.USAGE_DEFAULT);
registerProperty("time", long.class, "getTime", "setTime", MetaProperty.USAGE_DEFAULT);
registerProperty("shortNum", short.class, "getShortNum", "setShortNum", MetaProperty.USAGE_DEFAULT);
registerProperty("pi", float.class, "getPi", "setPi", MetaProperty.USAGE_DEFAULT);
} catch (Exception e) {
e.printStackTrace();
}
}
//...getters && setters
}
public class MyObject extends MyBaseObject {
private int x;
private int y;
private String name;
private byte color;
private int[] ary;
public MyObject() {
try {
registerProperty("x", int.class, "getX", "setX", MetaProperty.USAGE_SAVE_TO_DB);
registerProperty("y", int.class, "getY", "setY", MetaProperty.USAGE_DEFAULT);
registerProperty("name", String.class, "getName", "setName", MetaProperty.USAGE_DEFAULT);
registerProperty("color", byte.class, "getColor", "setColor", MetaProperty.USAGE_DEFAULT);
registerProperty("ary", int[].class, "getAry", "setAry", MetaProperty.USAGE_DEFAULT);
} catch (Exception e) {
e.printStackTrace();
}
}
//...getters && setters
}
public class Test {
public static void main(String[] args) {
MyObject obj = new MyObject();
obj.setX(10000);
obj.setY(10000);
obj.setZ(10000);
obj.setName("Hello");
obj.setFlag(true);
obj.setPi(3.1415f);
obj.setShortNum((short) 277);
obj.setTime(99999999999L);
obj.setColor((byte) 127);
obj.setAry(new int[]{1,2,3,4,5});
try {
obj.getMetaData().save(new XmlOutStream(obj.getMetaData().getName(), "c:\\test.xml"));
} catch (Exception e) {
e.printStackTrace();
}
}
}
最后我们的MyObject对象被写入一个像下面那样的XML文件中
<?xml version="1.0" encoding="gb2312"?>
<MyObject>
<property name="time" value="99999999999"/>
<property name="flag" value="true"/>
<property name="color" value="127"/>
<property name="shortNum" value="277"/>
<property name="name" value="Hello"/>
<property name="ary length" value="5"/>
<property name="ary[0]" value="1"/>
<property name="ary[1]" value="2"/>
<property name="ary[2]" value="3"/>
<property name="ary[3]" value="4"/>
<property name="ary[4]" value="5"/>
<property name="pi" value="3.1415"/>
<property name="z" value="10000"/>
<property name="y" value="10000"/>
<property name="x" value="10000"/>
</MyObject>
这样的系统对于数据驱动的系统是非常合适的,而且我觉得在C++中实现会更加优雅一些^_^。
当然其中还会有很多问题,比如效率等,不过写了这么多实在有点累^_^,下次再说吧。。。
思路很乱,写得更乱,见谅!O(∩_∩)O
万恶的格式!!!