If you're not already using an ORM, you're probably doing it wrong, because you're not taking advantage of the leverage that an ORM can provide to the practice of writing CRUD methods.
However, there's a bit of history to all this, so I'm now going to tell you a little bedtime story.
In the beginning, there were SQL databases, and life was good. Web sites were pretty sparse little affairs, and everyone got along by writing data access code by hand.
Then the wolly mammoths came.
No, not that wooly mammoth.
This woolly mammoth. Databases that encompass an entire business domain:
Suddenly, data access code written by hand didn't cut it anymore. So we invented ORM's, code-generation programs that produced one class for each table, complete with CRUD methods. It's a lot of code, but you didn't have to write it, did you?
Great story, huh bro? Well, not exactly.
You see, someone came along and said "Why do I need to know database theory, when I could just write my classes and then have the ORM generate my tables for me?"
OK, but now you're gonna have to write all of those classes by hand, aren't you?
Which brings us to the question you asked, which essentially is this: why can't I just write a function that accepts a type T, and returns an object of type T, like this?
public T Read(int id); // C#, for those of you so challenged.
Sigh. Well, you can, actually. It's called a Generic Repository. If you want to find out how to build one, you can look here. It starts with something like this:
public function select($table, $where = '', $fields = '*', $order = '', $limit = null, $offset = null)
{
$query = 'SELECT ' . $fields . ' FROM ' . $table
. (($where) ? ' WHERE ' . $where : '')
. (($limit) ? ' LIMIT ' . $limit : '')
. (($offset && $limit) ? ' OFFSET ' . $offset : '')
. (($order) ? ' ORDER BY ' . $order : '');
$this->query($query);
return $this->countRows();
}
...and builds on that humble beginning (and other functions to round out the CRUD) to create a complete generic repository. Add some IoC to it, and you'll have something that any Architecture Astronaut would consider worthy enough to take to interstellar space.
Here are a couple suggestions to help you think in new directions:
First, it seems like you're trying to tie your calculation functionality specifically to your Employee class. That seems unnecessarily specific. Why should your calculator class care whether the object it's working on represents an employee or something else? This seems like a natural place to use an interface to define the functionality that your calculator needs to apply a formula, without caring what the thing that implements the interface actually represents. And the functionality that your calculator needs is probably pretty simple: it just needs to be able to get named values and possibly also set named values. If you have a formula like:
annualVacationDays = 10 + yearsOfService + bonusDays
then it seems like you might need a Calculable
interface that has a function like valueForkey(key)
where key
is a string, so that the calculator can fetch values for yearsOfService
and bonusDays
in order to do its work. And the interface should also have a setValueForKey(value, key)
method so that it can store the result of the formula for the annualVacationDays
key. But the only things the calculator needs to do its work are those methods -- it shouldn't care what kind of object it is.
Second, given that the point of OOP is that you can combine data and the operations on that data, separating the formulas from the class that applies them doesn't make a lot of sense to me. Let each formula be able to apply itself to an object that implements the Calculable
interface.
Best Answer
CQS is a guideline rather than an absolute rule. See the wiki article for examples of activities that are impossible under strict CQS.
However, in this case, if you did want to maintain CQS, you could either create the ID on the client side (such as a GUID), or the client could request an ID from the system before creating any objects, which feels cleaner to me than creating the object then querying for it (but is harder than just using an identity column).
Personally I'd just return the ID and call it one of those situations where CQS isn't a good fit.
Another good article with examples: Martin Fowler