principlecppModerate
Design dilemma: extensibility vs simplicity
Viewed 0 times
simplicitydesigndilemmaextensibility
Problem
Here is my problem abstracted to
Classic:
"Better?
Bird classes. I know that number of Birds will increase on the future and new behaviors might be needed. With 10 Birds first design might not look so simple and lead to lots of duplicated code. On the other hand, second design can be perceived as "class explosion". Which of these two designs would be considered best-practice?Classic:
#include
class Bird
{
public:
virtual void Fly() const = 0;
virtual void Speak() const = 0;
};
class Eagle : public Bird
{
public:
virtual void Fly() const
{
std::cout Fly();
bird->Speak();
delete bird; bird = NULL;
bird = new Penguin();
bird->Fly();
bird->Speak();
delete bird; bird = NULL;
return 0;
}"Better?
#include
#include
class FlyStyle
{
public:
virtual void Fly() const = 0;
};
class FlyHigh : public FlyStyle
{
virtual void Fly() const
{
std::cout Fly();
}
virtual void Speak() const
{
speakstyle->Speak();
}
protected:
FlyStyle* flystyle;
SpeakStyle* speakstyle;
};
class SuperBirdFactory
{
public:
static SuperBird* createEagle()
{
return new SuperBird(new FlyHigh(), new SpeakLoud());
}
static SuperBird* createPenguin()
{
return new SuperBird(new NoFly(), new NoSpeak());
}
};
int main()
{
SuperBird* bird = NULL;
bird = SuperBirdFactory::createEagle();
bird->Fly();
bird->Speak();
delete bird; bird = NULL;
bird = SuperBirdFactory::createPenguin();
bird->Fly();
bird->Speak();
delete bird; bird = NULL;
return 0;
}Solution
In my opinion, a better approach (along similar lines) is to avoid abstract base classes and instead use generic "policy" classes. You informally define an interface for each behaviour, and mix them into the class as template parameters. This avoids the need for dynamic memory allocation, and removes the overhead of virtual function calls; everything is resolved at compile time. Your example could be something like this:
#include
// Flying styles must have interfaces compatible with this
// struct FlyStyle
// {
// void Fly() const;
// };
struct FlyHigh
{
void Fly() const
{
std::cout
class SuperBird
{
public:
void Fly() const
{
flystyle.Fly();
}
void Speak() const
{
speakstyle.Speak();
}
private:
FlyStyle flystyle;
SpeakStyle speakstyle;
};
typedef SuperBird Eagle;
typedef SuperBird Penguin;
int main()
{
Eagle eagle;
eagle.Fly();
eagle.Speak();
Penguin penguin;
penguin.Fly();
penguin.Speak();
}Code Snippets
#include <iostream>
// Flying styles must have interfaces compatible with this
// struct FlyStyle
// {
// void Fly() const;
// };
struct FlyHigh
{
void Fly() const
{
std::cout << "Fly high!" << std::endl;
}
};
struct NoFly
{
void Fly() const
{
std::cout << "No fly!" << std::endl;
}
};
// Speaking styles must have interfaces compatible with this
// struct SpeakStyle
// {
// void Speak() const;
// };
struct SpeakLoud
{
void Speak() const
{
std::cout << "Speak LAUD!!!!" << std::endl;
}
};
struct NoSpeak
{
void Speak() const
{
std::cout << "No speaking!" << std::endl;
}
};
template <class FlyStyle, class SpeakStyle>
class SuperBird
{
public:
void Fly() const
{
flystyle.Fly();
}
void Speak() const
{
speakstyle.Speak();
}
private:
FlyStyle flystyle;
SpeakStyle speakstyle;
};
typedef SuperBird<FlyHigh, SpeakLoud> Eagle;
typedef SuperBird<NoFly, NoSpeak> Penguin;
int main()
{
Eagle eagle;
eagle.Fly();
eagle.Speak();
Penguin penguin;
penguin.Fly();
penguin.Speak();
}Context
StackExchange Code Review Q#1022, answer score: 17
Revisions (0)
No revisions yet.