Enabling dependencies or resolving dependencies is a way of deciding on an implementation as an argument to a function. I insert dependencies on most days of the week on my job in C #.
Named implementations
A strategy template can be implemented using named implementations as follows.
List the names of the implementations:
data Language = French | Icelandic deriving (Read)
define a function with different implementations for different names:
newtype Greeting = Greeting String translateGreetingTo French = Greeting "Bonjour, Monde!" translateGreetingTo Icelandic = Greeting "Halló heimur!"
if you have a user
type User = User { _language :: Language }
you can greet her using
greet (User (Language language)) = let (Greeting greeting) = (translateGreetingTo language) in (printStrLn greeting)
Thus, greet will automatically support the new Language when implementing translateGreetingTo. *
Default implementation
When programming dependencies are usually allowed to accept standard implementations. During testing, most of the dependencies should be replaced with simple stub implementations.
To achieve simple default settings for programming, while maintaining a flexible choice for testing, define a default implementation by passing reasonable default dependencies:
defaultGreeting = translateGreetingTo Icelandic
Repeat for each function with dependencies (just like in StructureMap, register each interface that you define with the implementation):
utter :: Greeting -> IO () utter (Greeting greeting) = printStrLn greeting defaultUtter = utter defaultGreeting
If now we create a more flexible version of the greeting depending on utter (instead of hardcoding printStrLn):
flexibleGreet :: (Greeting -> IO ()) -> User -> IO () flexibleGreet utterer (User (Language language)) = utterer (translateGreetingToLanguage language)
Then again we can refator greet like:
greet = flexibleGreet utter
If you write more tests than program code, you can save the short name of the flexible (dependent) functions. Conversely, if you write more program codes than tests, you might want to keep the default version names short. Be sure to use systematic names where possible.
* In C #, a strategy template can be similarly implemented using a named implementation:
interface IGreeting { public string Text; } [... dependency registration and definition of User ...] public class A { public void Greet(User user) { var greeting = serviceLocator.GetNamedInstance<IGreeting>(user.Language); WriteLine(greeting.Text); }}