在 Java 物件導向的世界中,開發者定義領域物件對應真實世界的概念,並且依據實際的業務流程來設計服務來操作領域物件。領域物件代表領域概念的真實資料,但對於詮釋資料的抽象概念卻會受到時空環境的影響而變化。當領域物件不能面對現實而調整,那麼系統的設計將變得愈來愈繁複,但改變領域物件需要變動資料庫又往往是工程浩大要承擔巨大的風險,到底有沒有兩全其美的辦法呢?在此同人分享領域物件的概念性表層設計,它提供開發者自行定義領域物件概念性表層界面,並透過動態代理,以領域物件的資料自動產生領域物界概念性表層的實作。
這個設計由 Domain Facade Factory 的實作取得對應 Domain Object 的 Domain Concept Facade 物件,它實現 Domain Concept Interface 的方法。Domain Concept Facade 是動態代理物件,它使用 Facade invocation Handler 來控制 Domain Concept Interface 方法的運作。其程式核心控制如下所示:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Method mappingDomainObjectMethod = null;
try {
mappingDomainObjectMethod = domainObject.getClass().getMethod(methodName, method.getParameterTypes());
result = mappingDomainObjectMethod.invoke(mappingDomainObject, args);
} catch (NoSuchMethodException e1) {
if (memberMappers != null) {
CompositeMemberMapper mapper = memberMappers.get(methodName);
Class> returnType = method.getReturnType();
try {
result = mapper.getMember(mappingDomainObject);
} catch (NoSuchFieldException e2) {
result = null;
}
if (result != null && !returnType.isInstance(result)) {
throw new ClassCastException();
}
} else {
throw new NoSuchMethodException();
}
}
}
Facade invocation Handler 擁有 Domain Object 的物件實體、並且設定了複合成員對應表來對應 Domain Object 複合屬性的存取對應。如果 Domain Concept Interface 定義的方法是 Domain Object 實際存在屬性的存取方法,那麼就會透過 Java 反射機制存取該屬性,不然就會從複合成員對應表中找到符合的複合成員的對應,以對應複合屬性的資料存取。複合成員的對應必須實作以下界面:
public interface CompositeMemberMapper {
public Object getMember(Object dataOwner) throws Throwable;
public void setMember(Object dataOwner, Object value) throws Throwable;
}
Facade invocation Handler 透過 CompositeMemberMapper 存取 Domain Facade 的屬性,同人實作了三個基本的 CompositeMemberMapper:
- ComplexMemberMapper
- SimpleMemberMapper
- MappingValuesMemberMapper
ComplexMemberMappers 對應的屬性是聚合多個屬性的值物件(Value Object),它需要指定 memberInterface 來定義該值物件的界面、以及指定 memberMappers 對應該值物件的屬性對應。從下面這段程式碼的片段可以知道 ComplexMemberMappers 也是利用動態代理機制來自動產生值物件,此外還加入了 Null Object 的處理。所謂 Null Object 的處理是指當 memberMappers 指定的屬性有任何一個找不到、或是判斷 Null Object 的 memberPredicate(未指定則不檢查)的評估值為真就會當成 Null Object,可以存取它的方法不會產生 Null Assignment 的異常,但其包含有字串及原生屬性的值皆會回傳 null。
private void checkSubMembersExists(Object dataOwner) throws Throwable {
Iterator fieldIterator = memberMappers.keySet().iterator();
while (fieldIterator.hasNext()) {
memberMappers.get(fieldIterator.next()).getMember(dataOwner);
}
}
private Method findGetter(Class> objType, String fieldName) throws Throwable {
Method result = null;
if (fieldName != null) {
StringBuffer methodName = new StringBuffer(GETTER_PREFIX);
if (Character.isLowerCase(fieldName.charAt(FIRST_CHAR_INDEX)) && Character.isLowerCase(fieldName.charAt(SECOND_CHAR_INDEX))) {
methodName.append(fieldName.substring(FIRST_CHAR_INDEX, FIRST_CHAR_INDEX).toUpperCase());
methodName.append(fieldName.substring(SECOND_CHAR_INDEX));
} else {
methodName.append(fieldName);
}
result = objType.getMethod(methodName.toString(), NONE_PARAMETER_TYPES);
}
return result;
}
@Override
public Object getMember(Object dataOwner) throws Throwable {
Object result = null;
boolean isNull = false;
try {
checkSubMembersExists(dataOwner);
if (memberPredicate != null) {
if (!memberPredicate.evaluate(dataOwner)) {
throw new Exception();
}
}
} catch (Exception e) {
isNull = true;
}
InvocationHandler handler = new CompositeMemberInvocationHandler(dataOwner, memberMappers, isNull);
result = Proxy.newProxyInstance(memberInterface.getClassLoader(), new Class[] { memberInterface, NullCheckable.class }, handler);
return result;
}
@Override
public void setMember(Object dataOwner, Object value) throws Throwable {
Iterator fieldIterator = memberMappers.keySet().iterator();
while (fieldIterator.hasNext()) {
String fieldName = fieldIterator.next();
Method fieldGetter = findGetter(value.getClass(), fieldName);
CompositeMemberMapper mapper = memberMappers.get(fieldName);
mapper.setMember(dataOwner, fieldGetter.invoke(value, NONE_ARGS));
}
}
CompositeMemberMapper 使用 CompositeMemberInvocationHandler 生成動態代理物件,從下列程式片段可以看到動態代理物件如何執行動態代理物件存取屬性的方法,值得注意的是當回傳值為 null 時,需要判斷屬性為原生包覆型別的數值轉換。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
String accessType = null;
String fieldName = null;
// isNull method
if (IS_NULL.equals(method.getName())) {
result = nullObject;
} else {
// find accessor & fieldName
Pattern p = Pattern.compile(METHOD_NAME_REGEX);
Matcher m = p.matcher(method.getName());
if (m.matches()) {
accessType = m.group(FIRST_GROUP_INDEX);
fieldName = m.group(SECOND_GROUP_INDEX);
p = Pattern.compile(FIRST_UPPER_CASE_REGEX);
m = p.matcher(fieldName);
if (m.matches()) {
fieldName = m.group(FIRST_GROUP_INDEX).toLowerCase() + m.group(SECOND_GROUP_INDEX);
}
}
CompositeMemberMapper memberMapper = memberMappers.get(fieldName);
if (GETTER.equals(accessType) || BOOLEAN_GETTER.equals(accessType)) {
result = (nullObject ? null : memberMapper.getMember(dataOwner));
} else if (SETTER.equals(accessType)) {
if (!nullObject) {
memberMapper.setMember(dataOwner, args[FIRST_ARG_INDEX]);
} else {
// set any member to null object
// it will happen nothing
}
} else {
throw new UnsupportedOperationException(method.getDeclaringClass().getName() + DOT + method.getName());
}
}
if (result == null) {
Class> returnType = method.getReturnType();
if (BOOLEAN_TYPE.equals(returnType.getName())) {
result = Boolean.FALSE.booleanValue();
} else if (INTEGER_TYPE.equals(returnType.getName())) {
result = Integer.MIN_VALUE;
} else if (LONG_TYPE.equals(returnType.getName())) {
result = Long.MIN_VALUE;
} else if (DOUBLE_TYPE.equals(returnType.getName())) {
result = Double.MIN_VALUE;
} else if (FLOAT_TYPE.equals(returnType.getName())) {
result = Float.MIN_VALUE;
}
}
return result;
}
SimpleMemberMapper 對應簡單屬性,也就是為原生型別或相對應的包裝類別、以及字串屬性。一般而言,SimpleMemberMapper 通常是在 ComplexMemberMapper 的 memberMappers 定義其組成屬性,除非 Domain Object 的屬性名稱和 Domain Facade Interface 定義的存取屬性方法不同,否則 SimpleMemberMapper 不會有任何作用,因為當 Domain Object 的屬性存在,會直接存取屬性的資料而不會透過 SimpleMemberMapper 存取屬性。
@Override
public Object getMember(Object dataOwner) throws Throwable {
Object result = null;
Field field = dataOwner.getClass().getDeclaredField(dataOwnerFieldName);
field.setAccessible(Constants.ACCESSIBLE);
result = field.get(dataOwner);
return result;
}
@Override
public void setMember(Object dataOwner, Object value) throws Throwable {
Field field = dataOwner.getClass().getDeclaredField(dataOwnerFieldName);
field.setAccessible(Constants.ACCESSIBLE);
field.set(dataOwner, value);
}
MappingValuesMemberMapper 則是 SimpleMemberMapper 的特例,它需要指定一個 mappingValues 用來對應 Domain Object 的屬性值會對應成不同型態的欄位值,比如說要從字串型態 “Y” 變成布林型態的 true。
@Override
public Object getMember(Object dataOwner) throws Throwable {
Object result = super.getMember(dataOwner);
return mappingValues.get(result);
}
@Override
public void setMember(Object dataOwner, Object value) throws Throwable {
Set> entrySet = mappingValues.entrySet();
Iterator> iterator = entrySet.iterator();
Object data = null;
while (iterator.hasNext()) {
Entry
以上是領域物件的概念性表層設計的大致情形,這個設計其實只是再透過一層轉換讓領域物件更為抽象化,當然應該有人會覺得有疑問。如果用 Hibernate 來處理領域物件的持久化,似乎就不用大費周章地再經過一層轉換,因為 Hibernate 的設定本來就很有彈性了呀!這同人當然知道,但有這個疑問的人有所不知,我這個設計就是用在使用 Hibernate 框架的架構下。只是偉大的 DB Service 設計不把 Hibernate 建構操作物件的基礎上,而是以操作資料表的方式來使用 Hibernate,它把 Hibernate 的 Component 的功能自動排除了。既然有人把我們的 Composite Value Objects 偷走了,那我只好利用這個設計試著把它們要回來呀!