如何消除继承场景下的代码重复?以Character工厂方法为例
Great question! This is exactly the kind of scenario where the Factory Pattern (with a registry-based approach) shines—it eliminates repetitive if branches and makes your code way more maintainable, especially as you add dozens or hundreds of CharacterType variants.
The Problem with Your Current Code
Right now, your makeCharacter function relies on hardcoded conditional checks. Every time you add a new character type (like SNIPER or ENGINEER), you have to:
- Open up the
makeCharacterfunction - Add another
ifblock - Recompile and redeploy your code
This violates the Open/Closed Principle (a core SOLID tenet)—your code should be open for extension but closed for modification.
The Optimized Solution: Registry-Based Factory
Here's how to refactor your code to wipe out all those repetitive branches:
Step 1: Define a Creator Function Type
First, create a type alias for a function that handles character creation. This keeps your code clean and reusable:
#include <functional> #include <unordered_map> #include <memory> // Can be defined inside or outside your Game class, based on scope needs using CharacterCreator = std::function<std::shared_ptr<mtm::Character>( mtm::Team, mtm::units_t, mtm::units_t, mtm::units_t, mtm::units_t )>;
Step 2: Create a Static Registry Map
Next, set up a static map that links each CharacterType to its corresponding creation logic. Initialize this once (e.g., in a static helper or the Game class constructor):
class Game { private: // Static registry: maps CharacterType to its creator function static std::unordered_map<mtm::CharacterType, CharacterCreator> character_creators; // Helper to initialize the registry (runs once at program start) static void initCharacterRegistry() { character_creators[mtm::SOLDIER] = [](mtm::Team team, mtm::units_t health, mtm::units_t ammo, mtm::units_t range, mtm::units_t power) { return std::make_shared<mtm::Soldier>(team, health, ammo, range, power); }; character_creators[mtm::MEDIC] = [](mtm::Team team, mtm::units_t health, mtm::units_t ammo, mtm::units_t range, mtm::units_t power) { return std::make_shared<mtm::Medic>(team, health, ammo, range, power); }; // Add new character types here later—no need to touch makeCharacter! } public: // Ensure the registry is initialized before first use Game() { static bool registry_initialized = false; if (!registry_initialized) { initCharacterRegistry(); registry_initialized = true; } } std::shared_ptr<mtm::Character> makeCharacter(mtm::CharacterType type, mtm::Team team, mtm::units_t health, mtm::units_t ammo, mtm::units_t range, mtm::units_t power); }; // Initialize the static map outside the class std::unordered_map<mtm::CharacterType, CharacterCreator> Game::character_creators;
Step 3: Simplify makeCharacter
Now your makeCharacter function becomes lean and focused—no more endless if blocks:
std::shared_ptr<mtm::Character> Game::makeCharacter(mtm::CharacterType type, mtm::Team team, mtm::units_t health, mtm::units_t ammo, mtm::units_t range, mtm::units_t power) { if (health <= 0 || ammo < 0 || range < 0 || power < 0) { throw mtm::IllegalArgument(); } auto creator_it = character_creators.find(type); if (creator_it == character_creators.end()) { // Optional: Throw a custom exception like mtm::UnknownCharacterType instead throw mtm::IllegalArgument(); } // Delegate creation to the registered function return creator_it->second(team, health, ammo, range, power); }
Why This Works Better
- No More Repetition: Adding a new character type only requires adding one line to the
initCharacterRegistryfunction (or even dynamically, see below). - Complies with Open/Closed Principle: You never need to modify
makeCharacteragain—extend by adding new entries to the registry instead. - Cleaner, More Readable Code: All creation logic is centralized in the registry, making it easier to debug and maintain.
Bonus: Dynamic Registration (For Large Codebases)
If your character types are spread across different modules or plugins, add a public method to register new creators at runtime:
void Game::registerCharacterType(mtm::CharacterType type, CharacterCreator creator) { character_creators[type] = creator; }
This lets external code (like a plugin) register new character types without touching the core Game class at all.
Quick Note on std::make_shared
I swapped your std::shared_ptr<Character>(new mtm::Soldier(...)) with std::make_shared<mtm::Soldier>(...)—this is safer (avoids potential memory leaks if an exception is thrown between new and assigning to the shared_ptr) and more efficient (allocates the control block and object memory in a single chunk).
内容的提问来源于stack exchange,提问作者user13812739

