為什麼我們需要對象系統?
你是不是遇到這樣的情況,當要儲存對象時,通常會有一個基類,類似:
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
萬惡的格式!!!
