问题
Assume class Animal which does Eat, Sleep and Make Noise.
Assume class Mammal : public Animal which also does MakesBaby
Assume that Mammal also does Eat, Sleep and Make Noise which it inherits from Animal.
This code is extended from code presented in this question which was answered yesterday by Dan Masek.
The problems with this code are:
- Mammal can't inherit from Animal (compile error)
- Mammal cannot Eat, Sleep or Make Noise
- Code suffers from "Needless Repetition", thus meeting with the scorn and derision of Uncle Bob Martin.
I want this code to be as excellent as possible, but I am stuck.
I think that the similarities between the two classes center around the typedef that defines a function which is a pointer to a member in the class. I tried templating this, but ran into numerous compile errors, thus demonstrating my utter failure to comprehend how to solve this problem. Hence this post.
For those who saw my previous post, I have added an #ifdef to disable printing (This is Arduino code, so STD is not available), and I added asserts wherever I could. I also added the Mammal class.
// Set to false to suppress Serial.print output
#define TRACING true
class Animal
{
public:
enum {
CMD_EAT = 1
, CMD_SLEEP
, CMD_MAKENOISE
, COMMAND_COUNT
};
// Define a pointer to a function within this class that takes
// an INT as its argument
typedef void(Animal::*animalFunc)(int); // U THING THIS IS THE PROBLEM - CAN IT BE TEMPLATED? IF SO, I COULD NOT FIGURE OUT THE SYNTAX TO MAKE IT WORK
struct commandInfo
{
unsigned int code;
animalFunc handler;
char const* name;
};
Animal(char const* name) : _name(name) {
registerCommands();
}
// Register all commands supported by this class
virtual void registerCommands() {
#if TRACING
Serial.print(F("Registering "));
Serial.print(getName());
Serial.println(F(" Actions:"));
#endif
unsigned int index = 0;
registerCommand(index++, CMD_EAT, "EAT", &Animal::eat);
registerCommand(index++, CMD_SLEEP, "SLEEP", &Animal::sleep);
registerCommand(index++, CMD_MAKENOISE, "MAKE NOISE", &Animal::makeNoise);
assert(index == 3);
}
// register using an index rather than code so that we don't overload the meaning of code.
void registerCommand(unsigned int index, unsigned int code, char const* action, animalFunc fn) {
assert(code < COMMAND_COUNT);
_commands[index].code = code;
_commands[index].name = action;
_commands[index].handler = fn;
#if TRACING
Serial.print( "Registering: Index=");
Serial.print(index);
Serial.print(F(", Code="));
Serial.print(_commands[index].code);
Serial.print(F(", Action="));
Serial.println(_commands[index].name);
#endif
}
void report(unsigned int code, char const* msg) {
commandInfo info = getCommandInfo(code);
#if TRACING
Serial.print(msg);
Serial.print(info.code);
Serial.print(" [");
Serial.print(info.name);
Serial.println("]");
#endif
}
void exec(int code, int value) {
commandInfo info = getCommandInfo(code);
#if TRACING
report(code, "Executing: ");
#endif
(this->*info.handler)(value);
}
char const* getName() {
return _name;
}
// base class methods
virtual void eat(int times) {
while (times-- > 0) {
reportDoFunc(CMD_EAT);
}
}
virtual void sleep(int times) {
while (times-- > 0) {
reportDoFunc(CMD_SLEEP);
}
}
virtual void makeNoise(int times) {
while (times-- > 0) {
reportDoFunc(CMD_MAKENOISE);
}
}
virtual void showActions() {
#if TRACING
Serial.print(getName());
Serial.print(F(" Implements "));
Serial.print(COMMAND_COUNT);
Serial.println(F(" actions:"));
for (int i = 0; i < COMMAND_COUNT; i++) {
showAction(i);
}
#endif
}
void showAction(unsigned int index) {
#if TRACING
Serial.print(F("Index: ["));
Serial.print(index);
Serial.print(F("] code: ["));
Serial.print(_commands[index].code);
Serial.print(F("] "));
Serial.println(_commands[index].name);
#endif
}
private:
char const* _name;
// Define an array of pointers to action functions
commandInfo _commands[COMMAND_COUNT];
// return the command associated with code
commandInfo getCommandInfo(unsigned int code) {
#if TRACING
Serial.print(F("IN getCommandInfo: for code="));
Serial.print(code);
Serial.println(F(":"));
#endif
for (int i = 0; i < COMMAND_COUNT; i++) {
#if TRACING
Serial.print(F("Checking Index: ["));
Serial.print(i);
Serial.print(F("], Code: ["));
Serial.print(_commands[i].code);
#endif
if (code == _commands[i].code) {
#if TRACING
Serial.print(F("] FOUND "));
Serial.println(_commands[i].name);
#endif
return _commands[i];
}
#if TRACING
Serial.println("]");
#endif
}
// Invalid command code for this object
assert(false);
}
void reportDoFunc(unsigned int code) {
commandInfo info = getCommandInfo(code);
#if TRACING
Serial.print(code);
Serial.print(F(": "));
Serial.println(info.name);
#endif
}
};
//class Mammal : public Animal { <-- THIS FORM FAILS: "No matching function call to Animal::Animal()
class Mammal {
public:
enum {
CMD_BIRTHS_BABY = 1,
COMMAND_COUNT
};
typedef void(Mammal::*mammalFunc)(int); // <-- Only difference from animalFunc is class name
struct commandInfo {
unsigned int code;
char const* name;
mammalFunc handler; // <-- Only difference from parent class is mammalFunc
};
// Constructor
Mammal(char const* name) : _name(name) { // <-- Identical to parent (Animal) class
registerCommands();
}
// Register all commands supported by this class
virtual void registerCommands() {
#if TRACING
Serial.print(F("Registering "));
Serial.print(getName());
Serial.println(F(" Actions:"));
#endif
// CAN THIS LIST BE ABSTRACTED INTO A TABLE SO REGISTER COMMAND CAN INHERIT?
unsigned int index = 0;
registerCommand(index++, CMD_BIRTHS_BABY, "BIRTHS BABY", &Mammal::birthsBaby);
}
// Only difference from Animal::registerCommand is last argument (mannalFunc instead of animalFunc)
void registerCommand(unsigned int index, unsigned int code, char const* action, mammalFunc fn) {
// Assure that we don't have more commands than we thought...
assert(index < COMMAND_COUNT);
// Update values of current placeholder struct's values
_commands[index].code = code;
_commands[index].name = action;
_commands[index].handler = fn;
#if TRACING
report(code, "Registering: ");
#endif
}
// This is identical to Animal::exec
void exec(int code, int value) {
commandInfo info = getCommandInfo(code);
#if TRACING
report(code, "Executing: ");
#endif
(this->*info.handler)(value);
}
// This is identical to Animal::report
void report(unsigned int code, char const* msg) {
commandInfo info = getCommandInfo(code);
#if TRACING
Serial.print(msg);
Serial.print(info.code);
Serial.print(" [");
Serial.print(info.name);
Serial.println("]");
#endif
}
// This is identical to Animal::getName
char const* getName() {
return _name;
}
// This is identical to Animal::showActions
virtual void showActions() {
#if TRACING
Serial.print(getName());
Serial.print(F(" Implements "));
Serial.print(COMMAND_COUNT);
Serial.println(F(" actions:"));
for (int i = 0; i < COMMAND_COUNT; i++) {
showAction(i);
}
#endif
}
// This is identical to Animal::showAction
void showAction(unsigned int index) {
assert(index < COMMAND_COUNT);
#if TRACING
Serial.print(F("["));
Serial.print(_commands[index].code);
Serial.print(F("] "));
Serial.println(_commands[index].name);
#endif
}
// Concrete Action
virtual void birthsBaby(int times) {
while (times-- > 0) {
reportDoFunc(CMD_BIRTHS_BABY);
}
}
private:
// Inherit?
char const* _name;
// commandInfo is Mammal-specific. Can it be generic?
// Define an array of pointers to action functions
commandInfo _commands[COMMAND_COUNT];
commandInfo getCommandInfo(unsigned int code) {
#if TRACING
Serial.print(F("IN getCommandInfo: for code="));
Serial.print(code);
Serial.println(F(":"));
#endif
for (int i = 0; i < COMMAND_COUNT; i++) {
#if TRACING
Serial.print(F("Checking Index: ["));
Serial.print(i);
Serial.print(F("], Code: ["));
Serial.print(_commands[i].code);
#endif
if (code == _commands[i].code) {
#if TRACING
Serial.print(F("] FOUND "));
Serial.println(_commands[i].name);
#endif
return _commands[i];
}
#if TRACING
Serial.println(F("]"));
#endif
}
// Invalid command code for this object
assert(false);
}
// This is identical to Animal::reportDoFunc
void reportDoFunc(unsigned int code) {
commandInfo info = getCommandInfo(code);
#if TRACING
Serial.print(code);
Serial.print(F(": "));
Serial.println(info.name);
#endif
}
};
int main() {
Animal *pAnimal = new Animal("ANIMAL");
pAnimal->exec(Animal::CMD_EAT, 1);
pAnimal->exec(Animal::CMD_SLEEP, 1);
pAnimal->exec(Animal::CMD_MAKENOISE, 2);
pAnimal->showActions();
Mammal* pMammal = new Mammal("MAMMAL");
pMammal->exec(Mammal::CMD_BIRTHS_BABY, 2);
pMammal->showActions();
pMammal->exec(Animal::CMD_EAT, 2);
delete pMammal;
delete pAnimal;
}
来源:https://stackoverflow.com/questions/37500149/refactor-code-that-violates-needless-repition-principle