I am a bit worried about the mutability of your command parameters. Is it really necessary to create a command with constantly changing parameters?
Problems with your approach:
Do you want other threads/commands to change your parameters while perform
is going on?
Do you want visitBefore
and visitAfter
of the same Command
object to be called with different DIParameter
objects?
Do you want someone to feed parameters to your commands, that the commands have no idea about?
None of this is prohibited by your current design. While a generic key-value parameter concept has its merits at times, I do not like it with respect to a generic command class.
Example of consequences:
Consider a concrete realization of your Command
class - something like CreateUserCommand
. Now obviously, when you request a new user to be created, the command will need a name for that user. Given that I know the CreateUserCommand
and the DIParameters
classes, which parameter should I set?
I could set the userName
parameter, or username
.. do you treat parameters case insensitively? I wouldn't know really.. oh wait.. maybe it's just name
?
As you can see the freedom you gain from a generic key-value mapping implies that using your classes as someone who did not implement them is unwarrantedly difficult. You would at least need to provide some constants for your commands to let others know which keys are supported by that command.
Possible different designs approaches:
- Immutable parameters: By turning your
Parameter
instances immutable you can freely reuse them among different commands.
- Specific parameter classes: Given a
UserParameter
class that contains exactly the parameters I would need for commands that involve a user, it would be much simpler to work with this API. You could still have inheritance on the parameters, but it would not make sense any longer for command classes to take arbitrary parameters - at the pro side of course this means that API users know which parameters are required exactly.
- One command instance per context: If you need your commands to have things like
visitBefore
and visitAfter
, whilst also reusing them with different parameters, you will be open to the problem of getting called with differing parameters. If the parameters should be the same on multiple method calls, you need to encapsulate them into the command such that they cannot be switched out for other parameters in-between the calls.
Abstracting your class into an interface is something you should consider if and only if you intend on writing other implementations of said interface or the strong possibility of doing so in the future exists.
So perhaps SomeClass
and ISomeClass
is a bad example, because it would be like having a OracleObjectSerializer
class and a IOracleObjectSerializer
interface.
A more accurate example would be something like OracleObjectSerializer
and a IObjectSerializer
. The only place in your program where you care what implementation to use is when the instance is created. Sometimes this is further decoupled by using a factory pattern.
Everywhere else in your program should use IObjectSerializer
not caring how it works. Lets suppose for a second now that you also have a SQLServerObjectSerializer
implementation in addition to OracleObjectSerializer
. Now suppose you need to set some special property to set and that method is only present in OracleObjectSerializer and not SQLServerObjectSerializer.
There are two ways to go about it: the incorrect way and the Liskov substitution principle approach.
The incorrect way
The incorrect way, and the very instance referred to in your book, would be to take an instance of IObjectSerializer
and cast it to OracleObjectSerializer
and then call the method setProperty
available only on OracleObjectSerializer
. This is bad because even though you may know an instance to be an OracleObjectSerializer
, you're introducing yet another point in your program where you care to know what implementation it is. When that implementation changes, and presumably it will sooner or later if you have multiple implementations, best case scenario, you will need to find all these places and make the correct adjustments. Worst case scenario, you cast a IObjectSerializer
instance to a OracleObjectSerializer
and you receive a runtime failure in production.
Liskov Substitution Principle approach
Liskov said that you should never need methods like setProperty
in the implementation class as in the case of my OracleObjectSerializer
if done properly. If you abstract a class OracleObjectSerializer
to IObjectSerializer
, you should encompass all the methods necessary to use that class, and if you can't, then something is wrong with your abstraction (trying to make a Dog
class work as a IPerson
implementation for instance).
The correct approach would be to provide a setProperty
method to IObjectSerializer
. Similar methods in SQLServerObjectSerializer
would ideally work through this setProperty
method. Better still, you standardize property names through an Enum
where each implementation translates that enum to the equivalent for its own database terminology.
Put simply, using an ISomeClass
is only half of it. You should never need to cast it outside the method that is responsible for its creation. To do so is almost certainly a serious design mistake.
Best Answer
I wouldn't get too hung up on the terminology. Google has some good definitions:
Design: (search for 'definition of design')
noun:
verb:
Actual drawings sometimes exist (think flowcharts), but most designs I'm familiar with are typically written descriptions.
Implementation:
As for your specific questions: