In simple terms, a Specification is a small encapsulated unit of logic that gives an answer to a simple straight forward question: “does it match?”
Using a Specification we will split the logic of how we make a selection, away from objects we are selecting.
The Specification Pattern was originally proposed by Martin Fowler and Eric Evans.
Here is Dojo implementation of the CompositeSpecification.
/*
* The Specification Pattern JavaScript Implementation
*
* A specification is a powerful design pattern able to tell if a candidate object
* matches some criteria. The specification has a method isSatisfiedBy(entity)
* that returns true if all criteria are met by entity
*
* @example http://www.martinfowler.com/apsupp/spec.pdf
* @example http://en.wikipedia.org/wiki/Specification_pattern
* @example http://devlicio.us/blogs/jeff_perrin/archive/2006/12/13/the-specification-pattern.aspx
* @example http://devlicio.us/blogs/casey/archive/2009/03/02/ddd-the-specification-pattern.aspx
*
*
* LICENSE: Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to permit
* persons to whom the Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author Goran Miskovic, schkovich[at]gmail[dot]com
* @copyright 2010 Goran Miskovic ©
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
* @version SVN: $Id: CompositeSpecification.js 85 2011-03-02 22:26:46Z schkovich $
*/
dojo.provide("schkovich.CompositeSpecification");
dojo.declare("schkovich.CompositeSpecification", null, {
constructor: function() {
var AndSpecification = dojo.declare(schkovich.CompositeSpecification, {
constructor: function(self, other) {
var One = self;
var Other = other;
this.IsSatisfiedBy = function(entity) {
return One.IsSatisfiedBy(entity) && Other.IsSatisfiedBy(entity);
}
}
});
var OrSpecification = dojo.declare(schkovich.CompositeSpecification, {
constructor: function(self, other) {
var One = self;
var Other = other;
this.IsSatisfiedBy = function(entity) {
return One.IsSatisfiedBy(entity) || Other.IsSatisfiedBy(entity);
}
}
});
var NotSpecification = dojo.declare(schkovich.CompositeSpecification, {
constructor: function(self) {
var Wrapped = self;
this.IsSatisfiedBy = function(entity) {
return !Wrapped.IsSatisfiedBy(entity);
}
}
});
this.And = function(other) {
return new AndSpecification(this, other);
}
this.Or = function(other) {
return new OrSpecification(this, other);
}
this.Not = function() {
return new NotSpecification(this);
}
}
})
We will setup very simple example. Imagine you are librarian and the students asks for a list of composers that where productive during Romantic or Post-Romantic epoch and have compositions in C-minor or G-major.
In order to simplify the example we will assume that those whose first name starts with letter C did compose something in C-minor and that those whose first name starts on G composed something in G-major. :)
Data are structured in this way:
{"id":"1","firstName":"Johann Sebastian","lastName":"Bach","category":"Baroque"}
First we will need to create epoch specification:var EpochSpecification = dojo.declare(
schkovich.CompositeSpecification, {
constructor: function(epoch) {
this.IsSatisfiedBy = function(entity) {
return entity.category
&& (entity.category === epoch);
}
}
});
Then we need logic to select ones having compositions in C-minor or G-major or as we assumed ones whose first name starts with letter C or G:
var FirstLetterSpecification = dojo.declare(
schkovich.CompositeSpecification, {
constructor: function(firstLetter) {
this.IsSatisfiedBy = function(entity) {
return entity.firstName
&& (entity.firstName.indexOf(firstLetter) === 0);
}
}
});
Once we defined conditions we will put them in a single specification:
var spec = new EpochSpecification("Romantic").
Or(new EpochSpecification("Post-Romantic")).
And(
new FirstLetterSpecification("G")
.Or(new FirstLetterSpecification("C"))
);
Finally we need a function that will filter data:
var filterComposers = function(specification) {
var filtered = dojo.filter(composersData.items, function(entity){
return specification.IsSatisfiedBy(entity);
});
composersData.items = filtered;
return composersData;
}
Here is the HTML page that puts all of the above together:
<!DOCTYPE html>
<html>
<head>
<title>The Dojo Toolkit:: The Specification Pattern</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet"
type="text/css"
href="http://ajax.googleapis.com/ajax/libs/dojo/1.5/dijit/themes/claro/claro.css"
/>
<style type="text/css">
@import "http://ajax.googleapis.com/ajax/libs/dojo/1.5/dojox/grid/resources/claroGrid.css";
html, body {
width: 100%;
margin: 0;
font-family:helvetica,arial,sans-serif; font-size:90%;
}
.dojoxGrid table {margin: 0;}
#gridPlaceholder {height:314px; width: 50%}
</style>
</head>
<body class="claro">
<div id="gridPlaceholder"></div>
<script type="text/javascript">
djConfig={
parseOnLoad: false,
isDebug: false,
baseUrl: "./",
modulePaths: {schkovich: "./resources/schkovich" }
};
</script>
<script src="http://ajax.googleapis.com/ajax/libs/dojo/1.5/dojo/dojo.xd.js">
</script>
<script>
dojo.require("dojo.data.ItemFileReadStore");
dojo.require("dojox.grid.DataGrid");
dojo.require("dijit.form.Button");
dojo.require("schkovich.CompositeSpecification")
dojo.addOnLoad(function() {
dojo.parser.parse();
var composersData = {
identifier: "id",
label: 'firstName',
items: [
{"id":"1","firstName":"Johann Sebastian","lastName":"Bach","category":"Baroque"},
{"id":"2","firstName":"Arcangelo","lastName":"Corelli","category":"Baroque"},
{"id":"3","firstName":"George Frideric","lastName":"Handel","category":"Baroque"},
{"id":"4","firstName":"Henry","lastName":"Purcell","category":"Baroque"},
{"id":"5","firstName":"Jean-Philippe","lastName":"Rameau","category":"Baroque"},
{"id":"6","firstName":"Domenico","lastName":"Scarlatti","category":"Baroque"},
{"id":"7","firstName":"Antonio","lastName":"Vivaldi","category":"Baroque"},
{"id":"8","firstName":"Ludwig van","lastName":"Beethoven","category":"Classical"},
{"id":"9","firstName":"Johannes","lastName":"Brahms","category":"Classical"},
{"id":"10","firstName":"Francesco","lastName":"Cavalli","category":"Classical"},
{"id":"11","firstName":"Fryderyk Franciszek","lastName":"Chopin","category":"Classical"},
{"id":"12","firstName":"Antonin","lastName":"Dvorak","category":"Classical"},
{"id":"13","firstName":"Franz Joseph","lastName":"Haydn","category":"Classical"},
{"id":"14","firstName":"Gustav","lastName":"Mahler","category":"Classical"},
{"id":"15","firstName":"Wolfgang Amadeus","lastName":"Mozart","category":"Classical"},
{"id":"16","firstName":"Johann","lastName":"Pachelbel","category":"Classical"},
{"id":"17","firstName":"Gioachino","lastName":"Rossini","category":"Classical"},
{"id":"18","firstName":"Dmitry","lastName":"Shostakovich","category":"Classical"},
{"id":"19","firstName":"Richard","lastName":"Wagner","category":"Classical"},
{"id":"20","firstName":"Louis-Hector","lastName":"Berlioz","category":"Romantic"},
{"id":"21","firstName":"Georges","lastName":"Bizet","category":"Romantic"},
{"id":"22","firstName":"Cesar","lastName":"Cui","category":"Romantic"},
{"id":"23","firstName":"Claude","lastName":"Debussy","category":"Romantic"},
{"id":"24","firstName":"Edward","lastName":"Elgar","category":"Romantic"},
{"id":"25","firstName":"Gabriel","lastName":"Faure","category":"Romantic"},
{"id":"26","firstName":"Cesar","lastName":"Franck","category":"Romantic"},
{"id":"27","firstName":"Edvard","lastName":"Grieg","category":"Romantic"},
{"id":"28","firstName":"Nikolay","lastName":"Rimsky-Korsakov","category":"Romantic"},
{"id":"29","firstName":"Franz Joseph","lastName":"Liszt","category":"Romantic"},
{"id":"30","firstName":"Felix","lastName":"Mendelssohn","category":"Romantic"},
{"id":"31","firstName":"Giacomo","lastName":"Puccini","category":"Romantic"},
{"id":"32","firstName":"Sergei","lastName":"Rachmaninoff","category":"Romantic"},
{"id":"33","firstName":"Camille","lastName":"Saint-Saens","category":"Romantic"},
{"id":"34","firstName":"Franz","lastName":"Schubert","category":"Romantic"},
{"id":"35","firstName":"Robert","lastName":"Schumann","category":"Romantic"},
{"id":"36","firstName":"Jean","lastName":"Sibelius","category":"Romantic"},
{"id":"37","firstName":"Bedrich","lastName":"Smetana","category":"Romantic"},
{"id":"38","firstName":"Richard","lastName":"Strauss","category":"Romantic"},
{"id":"39","firstName":"Pyotr Il'yich","lastName":"Tchaikovsky","category":"Romantic"},
{"id":"40","firstName":"Guiseppe","lastName":"Verdi","category":"Romantic"},
{"id":"41","firstName":"Bela","lastName":"Bartok","category":"Post-Romantic"},
{"id":"42","firstName":"Leonard","lastName":"Bernstein","category":"Post-Romantic"},
{"id":"43","firstName":"Benjamin","lastName":"Britten","category":"Post-Romantic"},
{"id":"44","firstName":"John","lastName":"Cage","category":"Post-Romantic"},
{"id":"45","firstName":"Aaron","lastName":"Copland","category":"Post-Romantic"},
{"id":"46","firstName":"George","lastName":"Gershwin","category":"Post-Romantic"},
{"id":"47","firstName":"Sergey","lastName":"Prokofiev","category":"Post-Romantic"},
{"id":"48","firstName":"Maurice","lastName":"Ravel","category":"Post-Romantic"},
{"id":"49","firstName":"Igor","lastName":"Stravinsky","category":"Post-Romantic"},
{"id":"50","firstName":"Carl","lastName":"Orff","category":"Post-Romantic"}
]
};
var EpochSpecification = dojo.declare(
schkovich.CompositeSpecification, {
constructor: function(epoch) {
this.IsSatisfiedBy = function(entity) {
return entity.category
&& (entity.category === epoch);
}
}
});
var FirstLetterSpecification = dojo.declare(
schkovich.CompositeSpecification, {
constructor: function(firstLetter) {
this.IsSatisfiedBy = function(entity) {
return entity.firstName
&& (entity.firstName.indexOf(firstLetter) === 0);
}
}
});
var filterComposers = function(specification) {
var filtered = dojo.filter(composersData.items, function(entity){
return specification.IsSatisfiedBy(entity);
});
composersData.items = filtered;
return composersData;
}
var layout = [
{
name: "First Name",
field: "firstName",
width: "40%"
},
{
name: "Last Name",
field: "lastName",
width: "40%"
},
{
name: "Epoque",
field: "category",
width: "20%"
}
];
var spec = new EpochSpecification("Romantic").
Or(new EpochSpecification("Post-Romantic")).
And(
new FirstLetterSpecification("G")
.Or(new FirstLetterSpecification("C"))
);
var store = new dojo.data.ItemFileReadStore({
data: filterComposers(spec)
});
var grid = new dojox.grid.DataGrid({
store: store,
structure: layout,
clientSort: true,
rowSelector: '20px',
query: {firstName: "*"}
}, dojo.doc.createElement("div"));
dojo.place(grid.domNode, dojo.byId("gridPlaceholder"), "first");
grid.startup();
});
</script>
</body>
</html>
In reality I been using the Specification Pattern to handle highly complex forms having predefined sequence or entering data and numerous validation conditions. In short, each change of input data will trigger a loop over the form elements making sure that each element meets specification.
I am also considering creating data store based on the Specification Pattern: Instead passing complex queries a Specification will be passed.
No comments:
Post a Comment