This post is an extension of an earlier post entitled "Flex Part 02: Value Objects & Model Objects".
Recap: Model Objects are similar to Value Objects in that they hold the same data, except that the Model Object is responsible for making sure that no 'bad' data gets into the VO. The Model Object enforces business rules like, "phone must be at least ten digits long" which a VO cannot do on its own. A Model Object may even include methods for easily manipulating properties.
In the earlier post I presented some example code for a Model Object. It turns out that what I provided is a pain to maintain over time. There's just too much duplication and poor use of public properties. Continue reading for my attempt at a better, easier Model Object ...
What's better?
This approach should help simplify the use of Model Objects. If you notice any pitfalls with this approach, please let me know. On to the benefits:
- Requires accessor methods instead of public properties = Best practice
- Dynamically create VO's and MO's = Less duplication
- Less duplication = Easier to maintain
- Easy to maintain = More likely to actually be used.
Continue reading to view the code. The example is drawn from School.as in the previous post. The new class is named SimpleSchool.
-
/**
-
* Filename: SimpleSchool.as
-
*/
-
package com.domain.project.model
-
{
-
import com.domain.project.vo.SchoolVO;
-
import mx.utils.ObjectUtil;
-
-
[Bindable]
-
public class SimpleSchool
-
{
-
/**
-
* Private instance of a School Value Object
-
*/
-
private var vo:SchoolVO = new SchoolVO();;
-
-
-
/**
-
* Bindable accessor methods to enforce business logic
-
*/
-
public function set id (value:uint):void { vo.id = value; }
-
public function get id ():uint { return vo.id; }
-
-
public function set name (value:String):void { vo.name = value; }
-
public function get name ():String { return vo.name; }
-
-
public function set phone (value:String):void {
-
(value.length>= 10) ? vo.phone = value : trace('Phone number is too short');
-
}
-
public function get phone ():String {
-
return vo.phone;
-
}
-
-
-
/**
-
* Return a new SchoolVO instance from the private instance 'vo'
-
*/
-
public function toVO():SchoolVO
-
{
-
var newVO:SchoolVO = new SchoolVO;
-
var oldVO:Object = ObjectUtil.copy(vo);
-
for (var property:String in oldVO) {
-
newVO[property] = oldVO[property];
-
}
-
return newVO;
-
}
-
-
-
/**
-
* Return a SimpleSchool instance with values from a SchoolVO instance
-
*/
-
public static function fromVO(vo:SchoolVO):SimpleSchool
-
{
-
var newMO:SimpleSchool = new SimpleSchool();
-
var oldVO:Object = ObjectUtil.copy(vo);
-
for (var property:String in oldVO) {
-
newMO[property] = oldVO[property];
-
}
-
return newMO;
-
}
-
}
-
}
Hey, that is better
Instead of redefining the VO's properties, we use accessor methods to enforce business logic and update the value of a private VO instance. And when it's time to get a VO from the Model Object (or create a Model Object from a VO), it's done automatically without having to list all the VO's properties again.
So, if you add a property to your Value Object ... just add the get and set accessor methods to the Model Object and it's good to go. Plus the structure is already in place to add logic/validation/formatting etc through the setter methods without having to think about it.
Thank you for reading. Your comments are very much appreciated. Remember that these posts are living documents and will be updated over time as input is received and new ideas emerge. So, please, consider your comment a contribution.
Note:
If your Value Objects use
RemoteClassmeta data (to map to a server side VO), thetoVO()method may simply return the result ofObjectUtil.copy(_vo). However, thefromVO()implementation will need to change.fromVO()needs an array of property names which can be either obtained withObjectUtil.getClassInfo(vo).propertiesOR you will need to declare a private static array in the Model Object which holds an array of the VO's property names. ThefromVO()method will loop through the values in the array instead of usingObjectUtil.copy()implementation shown above.When using meta data to map to a server side object,
ObjectUtil.copy()returns a normal class instance instead of a dynamicObjectclass instance. Only dynamically added properties can be looped through in the way shown above. If you would like to see an example, let me know! I still very much favor this solution.Thanks thor this post.
i'm interested in the solution for the issue about RemoteClass meta data usage.
Can you post some sample code for the toVO and fromVO methods ?
Yes. I actually have updated code for this post. I'll post it as soon as I can - (on vacation for a couple days).
I was bugged by the inability to enumerate static Value Objects too. I found a good solution here:
http://www.lynchconsulting.com.au/blog/index.cfm/2008/2/6/AS3--Looping-over-properties-of-a-class
flash.util.describeType() returns an XML Object with static members and can be nicely enumerated.
Hi.
I think I follow the logic of this "better" method. And your example at
http://blog.tsclausing.com/post/9 was really, really, really useful to me as a flex noobie. However, I don't quite see how this "better" way of doing models would work in actual code. Maybe it is that I am still fuzzy on setters and getters. Can you provide an example of the model locator code where you use the SimpleSchool model instead of the School model? Just something that shows the setters and getters working and value object properties emerging in the code.
Everything else has been great in this series. Reading your first example of Value Objects and the table row analogy was a total "Aha" moment for me. Keep up the good work.
Hi,
Can't the Model Object and Value Object be the same Object? We can define say the phone accessor in VO as well. Is there a reasoning to use VO and convert it to Model object?
Sure. And in most cases that is probably the quickest way to get up and running and may be entirely sufficient for your project. I just like to keep the VO's as light as possible for remoting.
Value Object: Represents only the data (values) that are persisted or retrieved.
Model Object: Enforces business rules through accessor methods, handles copying and comparing custom object equality, and may contain convenience methods for various tasks (like a getter for "fullName" that returns a concatenated first and last name).
There's no right or wrong way to do it - just personal preference.