Posted: June 20th, 2008 | Author: tolleiv | Tags: creational, database, factory, pattern, query, specification | Comments Off
Maybe you remember the Specification Pattern I explained some weeks ago. It enabled a easy and intuitive searching within large object-collections. A drawback of my example was that I stored the objects in the memory. This can be really ineffective if you want a single object out of hundreds, because you have to create all of them to see which one fits the specification.
Normally you want to limit the number of objects and you also don’t store large datasets in memory. The idea of the Query Object pattern is that it enables a usage, comparable to the specification pattern, for objects which are persisted in a database. The benefit is that it creates a query to exclude objects which won’t satisfy your needs and therefore you wont mess up the memory anymore.
Once you have a query-object in place you should not get in touch with SQL anymore because it can encapsulate SQL completely, at least if you also have some kind of data mapping (coming soon), which is a great benefit for everyone who is not so familiar with SQL. (But don’t forget, regarding performance, SQL-optimization is a very important thing).
So what we need for the Query Object in first place is a object, I’ll use the Cookie out of the specification pattern post again. Then we need criteria-objects which hold the information for a single criteria, (determined by “database-field”, “operator” and “value”) and we also need the Query Objects itself to wrap up the SQL-querying and the object creation somehow.
A very simple example could look like this:
interface Critera {
public function getWhereClause();
}
class CookieCriteria implements Critera {
private $operator,$field,$value;
protected function __construct($operator,$field,$value) {
$this->operator=$operator;
$this->field=$field;
$this->value=$value;
}
public function getWhereClause() {
return implode(” “,array($this->field,$this->operator,$this->value));
}
public static function matches($field,$value) {
return new CookieCriteria(“LIKE”,$field,‘”‘.$value.‘”‘);
}
public static function greaterThan($field,$value) {
return new CookieCriteria(“>”,$field,$value);
}
}
class CookieFinder {
protected $criterias;
public function addCriteria(Critera $criteria) {
$this->criterias[] = $criteria;
}
public function generateSQL() {
$sql = “SELECT * FROM cookies”;
if(sizeof($this->criterias)) {
$where=array();
reset($this->criterias);
while(list(,$criteria)=each($this->criterias)) {
$where[] = $criteria->getWhereClause();
}
$sql.= sizeof($where)?” WHERE “.implode(” AND “,$where):“”;
}
return $sql;
}
public function find() {
$collection = array();
if(!$result = mysql_query($this->generateSQL())) {
throw new Exception(mysql_errno());
}
while($row = mysql_fetch_assoc($result)) {
$collection[] = new Cookie($row['name'],$row['flavor'],$name['size']);
}
return $collection;
}
}
Possible client code could look like this:
$finder = new CookieFinder();
$finder->addCriteria(CookieCriteria::matches(“name”,“Granny%”));
$finder->addCriteria(CookieCriteria::greaterThan(“size”,100));
$cookies = $finder->find();
We just pick up the Query Object, add one or more criteria and ask it to create the objects which fit them.
So this example is not as powerful as the one I used for the Specification pattern, but it should be a easy task to create some kind of “nested criteria objects”.
Query objects normally make use of data-mapping so that you can handle various classes, stored in different tables/databases, with a single and generic Query Object. This also enables to avoid SQL-Injection, since you’re able to validate the fields and values before you sent them to your database, also some kind of database abstraction would be possible.
With the “Query Object by example“, which requires to build up a single object which is used as blueprint for the required objects, exists another flavor of this pattern which is very handy to use and more descriptive.
But no matter which flavor you prefer, Query Objects bring some real benefits when you’ve to handle complex datasets – for smaller projects the effort might be to much so be careful where you use it.
Maybe you remember the Specification Pattern I explained some weeks ago. It enabled a easy and intuitive searching within large object-collections. A drawback of my example was that I stored the objects in the memory. This can be really ineffective if you want a single object out of hundreds, because you have to ...
Posted: May 15th, 2008 | Author: tolleiv | Tags: pattern, service, specification | 1 Comment »
Maybe sometimes the cookie tin is filled with all sorts of cookies and only some of them are what you’d like in this special moment. Often it’s very easy to specify which cookie you like, but sometimes your wishes are very complex, for example if you look for grannies special cookie with chocolate, coconut and vanilla crumbles. This could lead into a real crumby problem if you try to sort all the cookies and then select eat the right one.
In the world of OOP it’s often much harder to collect a few objects out of a large number of different objects, also combining different requirements isn’t easy and that’s where the specification pattern can help you out. It implements some basic operations to combine requirements (AND, OR, NOT) and the only thing a concrete specification for a concrete class has to do is to implement a method (isSatisfied()) which is able to determine whether a object meats a requirement or not.
I splitted the generic part of the script and the cookie-example. The first part just implements the methods which are needed for the combination and provides a abstract class which is extended by the specifications in the second part. As you see the specification is combined which normally encapsulates the retrieval of the objects ,for example from a database. ….just have a look it’s really easy to select the right cookie … yummy

interface Specification {
public function isSatisfiedBy($obj);
public function _and(Specification $spec);
public function _or(Specification $spec);
public function _not();
}
abstract class AbstractSpecification implements Specification {
public function isSatisfiedBy($obj) { }
public function _and(Specification $spec) {
return new AndSpecification($this, $spec);
}
public function _or(Specification $spec) {
return new OrSpecification($this, $spec);
}
public function _not() {
return new NotSpecification($this);
}
}
class AndSpecification extends AbstractSpecification {
private $spec1, $spec2;
public function __construct(Specification $spec1, Specification $spec2) {
$this->spec1 = $spec1;
$this->spec2 = $spec2;
}
public function isSatisfiedBy($obj) {
return $this->spec1->isSatisfiedBy($obj) && $this->spec2->isSatisfiedBy($obj);
}
}
class OrSpecification extends AbstractSpecification {
private $spec1, $spec2;
public function __construct(Specification $spec1, Specification $spec2) {
$this->spec1 = $spec1;
$this->spec2 = $spec2;
}
public function isSatisfiedBy($obj) {
return $this->spec1->isSatisfiedBy($obj) || $this->spec2->isSatisfiedBy($obj);
}
}
class NotSpecification extends AbstractSpecification {
private $spec;
public function __construct(Specification $spec) {
$this->spec = $spec;
}
public function isSatisfiedBy($obj) {
return !$this->spec->isSatisfiedBy($obj);
}
}
/**
* Cookie object just a container for the relevant data.
*
*/
class Cookie {
protected $name,$flavor,$size;
public function __construct($name=”,$flavor=‘chocolate’,$size=100) {
$this->name=$name;
$this->flavor = $flavor;
$this->size = abs($size); // avoid negative size
}
public function getName() { return $this->name; }
public function getFlavor() { return $this->flavor; }
public function getSize() { return $this->size; }
}
/**
* Cookie service delivers cookies, offers some ways to select specific types of cookies
*
*/
class CookieService {
protected $cookies = array();
/**
* Add a cookie to the collection
* name is used as identifier, thats not the best choice
* but it’s ok for the example
*
* @param Cookie $cookie
*/
public function add(Cookie $cookie) {
$this->cookies[$cookie->getName()]=$cookie;
}
/**
* Generic method to check which objects fit the spec
*
* @param Specification $spec
*/
private function filter(Specification $spec) {
$result=array();
reset($this->cookies);
foreach($this->cookies as $name=>$cookie) {
if($spec->isSatisfiedBy($cookie)) {
$result[]=$cookie;
}
}
return $result;
}
public function getLargeCookies() {
$spec = new SmallCookieSpecification();
$spec = $spec->_not();
return $this->filter($spec);
}
public function getSmallChocolateCookies() {
$spec = new SmallCookieSpecification();
$spec = $spec->_and(new ChocolateCookieSpecification());
return $this->filter($spec);
}
public function loadDummyData() {
$this->add(new Cookie(‘Granny\’s classic’,‘chocolate’,60));
$this->add(new Cookie(‘Modern Jumbo’,‘moca,chocolate’,180));
$this->add(new Cookie(‘Kitchen Sink’,‘macadamia,cranberrie’,90));
$this->add(new Cookie(‘Vanilla Cloud’,‘vanilla’,120));
$this->add(new Cookie(‘Chocolate chip’,‘coconut,chocolate’,160));
}
}
class SmallCookieSpecification extends AbstractSpecification {
public function isSatisfiedBy($obj) {
return $obj->getSize() < 100;
}
}
class ChocolateCookieSpecification extends AbstractSpecification {
public function isSatisfiedBy($obj) {
return stristr(strtolower($obj->getFlavor()),‘chocolate’) !== FALSE;
}
}
/**
* Client code
*/
$service = new CookieService();
$service->loadDummyData();
echo ‘
’;
var_dump($service->getLargeCookies());
var_dump($service->getSmallChocolateCookies());
echo ‘
’;
more information by E.Evans and M.Fowler
Maybe sometimes the cookie tin is filled with all sorts of cookies and only some of them are what you'd like in this special moment. Often it's very easy to specify which cookie you like, but sometimes your wishes are very complex, for example if you look for grannies special cookie with chocolate, coconut ...