For example, I have something need to do depend on user input:
test.cpp
#include <iostream>
#include <fstream>
int main(){
std::string input;
std::cin >> std::noskipws >> input;
if(input=="A"){
std::cout << "A selected" << std::endl;
}else if(input=="B"){
std::ofstream myfile;
myfile.open ("test_log.txt");
myfile << time(NULL);
myfile.close();
std::cout << "time saved" << std::endl;
}else{
std::cout << "error" << std::endl;
}
return 0;
}
now has option A and B,and it is violating open closed principle because when adding new condition,e.g.: option “C”,needs to modify the if else statement in test.cpp. Is it possible to modify the code so that adding option “C” just adding a new file (class) and does not require to modify test.cpp?
I tried wrap the operation into a template class:
char a[]="A";
template<>
struct st<a>{
st(){
std::cout << "A selected" << std::endl;
}
};
and try to call the template in main:
#include "A.h"
#include "B.h"
int main(){
std::string input;
std::cin >> std::noskipws >> input;
st<input.c_str()>();
return 0;
}
so that adding a new option only needs to add a new template, but it failed to compile because the template can accept constant string only, and even it works, add new option still require to modify test.cpp to add new #include statement. Is it possible to add new option by defining a new class/new file without editing existing source code?
4
Yes, it is possible to have a command-handling mechanism that doesn’t require (extensive) editing to add a new command. It does require some additional infrastructure though.
The basis is that
-
you have a common interface for your commands through which you can ask which string triggers the command and through which you can tell the command to do its work:
class ICommand { public: std::string getTrigger() const = 0; void execute() = 0; };
-
at the point where you read the user input, you have a collection of
ICommand
implementations available that you can loop over and there you would callexecute
on those commands whose trigger matches the current input.
Adding a new command would involve creating a new class that implements the ICommand
interface and adding (an instance of) that new class to the list of known commands.
1
Bart’s gives the common OO approach, C++ (and many other languages) will also admit a more functional approach:
first lets change the list of commands to some sort of associative container, std::map or std::multimap, this will allow look up of commands to be better than O(n) and means the trigger is not needed as part of the command interface
second, we remove the trigger from the command interface and now are left with an interface that is equivalent to std::function so lets use that instead
we can now add commands to our container by giving the trigger as the key and a lambda as the action, no need for any new classes/interfaces