After looking at your designs, both your first and third iterations appear to be more elegant designs. However, you mention that you're a student and your professor gave you some feedback. Without knowing exactly what your assignment or the purpose of the class is or more information about what your professor suggested, I would take anything I say below with a grain of salt.
In your first design, you declare your RuleInterface
to be an interface that defines how to handle each player's turn, how to determine if the game is over, and how to determine a winner after the game ends. It seems like that's a valid interface to a family of games that experiences variation. However, depending on the games, you might have duplicated code. I'd agree that the flexibility to change the rules of one game is a good thing, but I'd also argue that code duplication is terrible for defects. If you copy/paste defective code between implementations and one has a bug in it, you now have multiple bugs that need to be fixed in different locations. If you rewrite the implementations at different times, you could introduce defects in different locations. Neither of those is desirable.
Your second design seems rather complex, with a deep inheritance tree. At least, it's deeper than I would expect for solving this type of problem. You're also starting to break up implementation details into other classes. Ultimately, you are modeling and implementing a game. This might be an interesting approach if you were required mix-and-match your rules for determining the results of a move, the end of the game, and a winner, that doesn't seem to be in the requirements that you've mentioned. Your games are well defined sets of rules, and I'd try to encapsulate the games as much as I can into separate entities.
Your third design is one that I like the best. My only concern is that it's not at the right level of abstraction. Right now, you appear to be modeling a turn. I would recommend considering designing the game. Consider that you have players who are making moves on some kind of board, using stones. Your game requires these actors to be present. From there, your algorithm is not doTurn()
but playGame()
, which goes from the initial move to the final move, after which it terminates. After every player's move, it adjusts the state of the game, determines if the game is in a final state, and if it is, determines the winner.
I would recommend taking closer looks at your first and third designs and working with them. It might also help to think in terms of prototypes. What would the clients that use these interfaces look like? Does one design approach make more sense for implementing a client that's actually going to instantiate a game and play the game? You need to realize what it's interacting with. In your particular case, it's the Game
class, and any other associated elements - you can't design in isolation.
Since you mention you're a student, I'd like to share a few things from a time when I was the TA for a software design course:
- Patterns are simply a way of capturing things that have worked in the past, but abstracting them to a point where they can be used in other designs. Each catalog of design patterns gives a name to a pattern, explains its intentions and where it can be used, and situations where it would ultimately constrain your design.
- Design comes with experience. The best way to get good at design isn't to simply focus on the modeling aspects, but realize what goes into the implementation of that model. The most elegant design is useful if it can't easily be implemented or it doesn't fit into the larger design of the system or with other systems.
- Very few designs are "right" or "wrong". As long as the design fulfills the requirements of the system, it can't be wrong. Once there's a mapping from each requirement into some representation of how the system is going to meet that requirement, the design can't be wrong. It's only a qualitative at this point about concepts such as flexibility or reusability or testability or maintainability.
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.
Best Answer
No, your database/config files should not mention class names.
Instead, you should include the channel in your model, as it is a relevant part of your domain. This doesn't have to be fancy - a simply enum may be sufficient. If your strategy depends on many parameters, you can group them into a
SalesStrategyConfiguration
.The actual strategy is then instantiated by a factory, which evaluates the provided parameters and instantiates the correct strategy. This decision is based purely on domain-relevant concepts, so there is no need for any specific framework.
Where a framework may well come in handy is the translation of the config data to the model (i.e. creating the
SalesStrategyConfiguration
). But this is a very general task, in no way related to strategy instantiation.