Taking the simple example of a user, in my experience, I have found that there is always a slightly different piece of checking/logic to perform depending on whether you are creating or updating a user (this problem extends to updating other domain entities too). The code quickly gets spaghetti-fied when you add liberal sprinkilings with rationale such as "Just do this check for the user when updating them" and "ooh just do this check, logic, insert when creating the user"开发者_如何学JAVA
The sum of all this is a monolithic behemoth of code which is a bloody eyesore to look at. I suppose its just common-sense to refactor/separate out this logic completely. Or am I mad.
Thanks,
Well, there basically is two patterns to choose from:
- Make completely separate cases for adding and updating.
- Make a single case that handles both adding and updating.
The advantage of having separate cases is that the code gets cleaner, the drawback is that you are repeating most of the code. Using a single case to avoid repeating the code also means that the code gets more complex.
Trying to create something that is in-between will most likely end up with the drawbacks of both patterns.
I would use the Template Pattern for this situation. Basically, you can put all the duplicate code in the base class and have the inherited classes implement the specifics. A simple psuedo code example would look like this:
IRep
class IRep
{
void Add(object a);
void Remove(object a);
}
BaseRep
abstract class BaseRep : IRep
{
void Add(object a)
{
if(OkToAdd(a))
{
// Common Rep code here
}
}
void Remove(object a)
{
if(OkToRemove(b))
{
// Common Rep code here
}
}
abstract bool OkToAdd(object a);
abstract bool OkToRemove(object a);
}
MyRep1
class MyRep1 : BaseRep
{
bool OkToAdd(object a)
{
// Add specific checks here for MyRep1
}
bool OkToRemove(object a)
{
// Add specific checks here for MyRep1
}
}
MyRep2
class MyRep2 : BaseRep
{
bool OkToAdd(object a)
{
// Add specific checks here for MyRep2
}
bool OkToRemove(object a)
{
// Add specific checks here for MyRep2
}
}
I like to have them completely separated. Even though this may look as code duplication, it allows to evolve these actions independently in the future. And they can get quite different from each other: when creating, there may be some fields not required, or even not allowed to be set, and there may be lots of different rules to be checked in each case. If you completely separate these actions, then you have complete freedom how to evolve them.
I have found that there is always a slightly different piece of checking/logic to perform depending on whether you are creating or updating a user
Of course there is different logic involved.
Creating an entity and making a change to an entity are two distinctly different operations. Don't try to make them the same operation.
Ok, so I'm a few years further on in my career and I now have an awareness of a number of approaches when designing/understanding software.
It seems what I was describing seems symptomatic of an anaemic domain model and transaction script. Where the business rules buried in the method/operation used for persistance quickly becomes tangled if shoe-horned into a create/upsert/update operation on a repository or against a persistence store.
Intent and business rules are quickly lost in this kind of approach.
A way to alleviate this is to lean on a DDD approach and carefully model behaviour so that only relevant state is changed and then persisted (most likely on an aggregate or entity/value object in that aggregate). This helps ensure that there is an auditable story by virtue of an operation explicitly indicating what state is being changed. For example:
Customer.AddUsername(string username)
Where this method would update the username, with scope to have business rules in play within the operation.
精彩评论