Skip to content

Instantly share code, notes, and snippets.

@jrosell
Last active May 2, 2024 15:47
Show Gist options
  • Save jrosell/f2e71b5850845b31952c11b7183eaea0 to your computer and use it in GitHub Desktop.
Save jrosell/f2e71b5850845b31952c11b7183eaea0 to your computer and use it in GitHub Desktop.
#!/usr/bin/env Rscript
cat("==========================================================================\n")
cat("Example of strategy patern in R: Multiple ways to show routes. \n")
# Problem:
# You build a navigation app for casual travelers and help users quickly orient themselves in any city.
#
# - The first only build the routes over roads.
# - Next update, you added an option to build walking routes.
# - Right after that, you added another option to let people use public transport in their routes.
# - Later you planned to add route building for cyclists.
#
# Each time you added a new routing algorithm, the navigator function increased the size.
# Any change to one of the algorithms, affected the whole function, increasing the chance of creating an error in already-working code.
# Implementing a new feature requires you to change the same huge function, conflicting with the code produced by other people.
#
# Solution in R:
# Take a function with a lot of conditionals or branchs and move them into separate implementations.
# We can use generic functions to write strategy implementations in each concrete method of each strategy.
#
# The main function would be isn’t responsible for selecting an appropriate algorithm for the job.
# In fact, the main function doesn’t know much about strategies.
# Is the client who passes the object as argument with the desired strategy for the generic function.
#
# In our navigation app each routing algorithm can be extracted to its buildRoute implementation.
# Even though given the same arguments, each routing class might build a different route, the main navigator function.
# doesn’t really care which algorithm is selected since its primary job is to get a set of checkpoints on the map.
# The class has a property for switching the active routing strategy, so its clients in the
# user interface, can replace the currently selected routing behavior with another one.
select_route_strategy <- \() {
strategy_class <- ""
while(strategy_class == "") {
cat("\nWhat option do you want?\nHere are the options: 1) roads 2) walk.\n");
strategy_selection <- readLines("stdin", n = 1);
strategy_class <- dplyr::case_match(
strategy_selection,
"1" ~ "routes_roads",
"2" ~ "routes_walking",
.default = ""
)
}
return(structure(list(), class = strategy_class))
}
new_context <- \(data, routes_strategy = NULL) {
structure(list(data = data, routes_strategy = routes_strategy), class = "context")
}
buildRoute <- \(x, context) {
UseMethod("buildRoute")
}
buildRoute.default <- \(x, context) {
stop("You must select a valid route_strategy")
}
buildRoute.routes_roads <- \(x, context) {
subset(context$data, y == 1)
}
buildRoute.routes_walking <- \(x, context) {
subset(context$data, y == 2)
}
navigator <- \(context){
buildRoute(context$routes_strategy, context)
}
cat("Give me 4 numbers:\n");
x <- readLines("stdin", n = 4);
df <- data.frame(x = x, y = c(1, 1, 2, 2))
repeat {
route_strategy <- select_route_strategy()
context <- new_context(
data = df,
routes_strategy = route_strategy
)
print(navigator(context))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment