wtd
|
Posted: Wed Jun 28, 2006 11:11 am Post subject: Command/Query Separation |
|
|
What the heck is Command/Query Separation?
I think that to answer that, we have to think about what it isn't. As databases are the centerpiece around which most programming takes place these days, let's consider a problem in that domain.
You want to write a method in your class that queries a database and then returns the results to you. Assuming we know how to query the database, this is a fairly straightforward task. In a vaguely Java-ish example:
code: | class MyClass {
public DBResults queryDatabase() {
DB myDatabase = new DB("foo");
myDatabase.select("bar");
DBHandle myHandle = new DBHandle(myDatabase);
myHandle.execute("select * from baz");
DBResults myResults = new DBResults();
for (Line l ; myHandle.getResultsInRows()) {
myResults.append(l);
}
return myResults;
}
} |
And that would seem all well and good, but let's think about it.
Our method is really doing two things. It's executing a command, by executing an SQL query on the database. This is a side-effect of the method. It's also returning some value.
Command/Query Separation says this is a bad thing. It makes it more difficult to comprehend the program. A simple symptom of this is naming. Do we give the method a verb name, because it causes a side-effect, or a noun name because it returns a value?
Rather, we would have something like:
code: | class MyClass {
private DBResults theResults;
public MyClass() {
theResults = new DBResults();
}
public DBResults getResults() {
return theResults;
}
public void queryDatabase() {
DB myDatabase = new DB("foo");
myDatabase.select("bar");
DBHandle myHandle = new DBHandle(myDatabase);
myHandle.execute("select * from baz");
theResults.clear();
for (Line l ; myHandle.getResultsInRows()) {
theResults.append(l);
}
}
} |
Of course, it looks a bit nicer when expressed in the language Bertrand Meyer created with this and other ideas in mind.
code: | class
MY_CLASS
creation
make
feature
the_results : DB_RESULTS
make is
do
create the_results.make
end
query_database is
local
my_database : DB
my_handle : DB_HANDLE
iter : DB_ITERATOR
do
create my_database.with_name("foo")
create my_handle.with_db(my_database)
my_handle.execute("select * from baz")
the_results.clear
iter := my_database.get_new_iterator
from
iter.start
until
iter.is_off
loop
the_results.append(iter.current_line)
iter.next
end
end
end |
Ignoring the difference in the length of the loop, Meyer's Eiffel is better at this. By default, features like the the_results variable are read-only from outside the object, meaning that distinct getters do not have to be written.
Additionally, Eiffel enforces CQS. If we had written query_database such that it returned a value, we would not have been able to call procedures which cause side-effects from within it.
A common real example of using CQS is reading input. A sample:
code: | class
FOO
creation
make
feature
make is
do
std_output.put_string("Your name is? ")
std_input.read_line
std_output.put_string("Hello, " + std_input.last_string + "%N")
end
end |
Command/Query Separation is a powerful concept, and one worth investigating if you're working with an object-oriented language. |
|
|