基于条件选择继承类构造器的通用实现方案咨询
Great question! Dealing with expanding switch/case blocks when instantiating subclasses is a classic maintainability problem. Here are two solid patterns to solve this, keeping your code clean even as you add more weapon types:
1. Dictionary-Based Factory (Recommended for Most Cases)
This approach replaces the switch block with a dictionary that maps WeaponType values to factory functions. It’s fast, explicit, and easy to maintain.
Implementation Steps:
- Create a static dictionary that pairs each
WeaponTypewith a function that creates the corresponding weapon instance. - Initialize this dictionary once (e.g., in a static constructor or app startup).
- When you need a weapon, just look up the factory function in the dictionary and execute it.
Example Code:
public static class WeaponFactory { private static readonly Dictionary<WeaponType, Func<WeaponData, BasicWeapon>> _weaponCreators; static WeaponFactory() { _weaponCreators = new Dictionary<WeaponType, Func<WeaponData, BasicWeapon>> { { WeaponType.ProjectileWeapon, data => new ProjectileWeapon(data) }, { WeaponType.Laser, data => new LaserWeapon(data) }, { WeaponType.snowBall, data => new SnowballWeapon(data) } }; } public static BasicWeapon CreateWeapon(WeaponData weaponData) { if (_weaponCreators.TryGetValue(weaponData.WeaponType, out var creator)) { return creator(weaponData); } // Handle unknown weapon type (throw exception, return default, etc.) throw new ArgumentOutOfRangeException(nameof(weaponData.WeaponType), $"Unknown weapon type: {weaponData.WeaponType}"); } }
Usage:
BasicWeapon weapon = WeaponFactory.CreateWeapon(_weaponsData);
Pros:
- Fast: No runtime reflection, just a dictionary lookup.
- Explicit: You can see exactly which weapon types are supported at a glance.
- Easy to debug: No hidden logic.
Cons:
- You need to manually add new entries to the dictionary when you create a new weapon subclass.
2. Attribute + Reflection (For Auto-Registration)
If you want to avoid manually updating the factory every time you add a new weapon, you can use custom attributes and reflection to auto-discover and register weapon types at startup.
Step 1: Create a Custom Attribute
[AttributeUsage(AttributeTargets.Class, Inherited = false)] public class WeaponTypeAttribute : Attribute { public WeaponType WeaponType { get; } public WeaponTypeAttribute(WeaponType weaponType) { WeaponType = weaponType; } }
Step 2: Mark Your Weapon Subclasses
[WeaponType(WeaponType.ProjectileWeapon)] public class ProjectileWeapon : BasicWeapon { public ProjectileWeapon(WeaponData weaponData) : base(weaponData) { } } [WeaponType(WeaponType.Laser)] public class LaserWeapon : BasicWeapon { public LaserWeapon(WeaponData weaponData) : base(weaponData) { } } [WeaponType(WeaponType.snowBall)] public class SnowballWeapon : BasicWeapon { public SnowballWeapon(WeaponData weaponData) : base(weaponData) { } }
Step 3: Build the Factory with Reflection
public static class WeaponFactory { private static readonly Dictionary<WeaponType, Func<WeaponData, BasicWeapon>> _weaponCreators; static WeaponFactory() { _weaponCreators = new Dictionary<WeaponType, Func<WeaponData, BasicWeapon>>(); // Discover all types that inherit from BasicWeapon and have the WeaponTypeAttribute var weaponTypes = typeof(BasicWeapon).Assembly.GetTypes() .Where(t => t.IsSubclassOf(typeof(BasicWeapon)) && !t.IsAbstract) .Select(t => new { Type = t, Attribute = t.GetCustomAttribute<WeaponTypeAttribute>() }) .Where(x => x.Attribute != null); foreach (var weapon in weaponTypes) { // Get the constructor that takes WeaponData var constructor = weapon.Type.GetConstructor(new[] { typeof(WeaponData) }); if (constructor == null) { throw new InvalidOperationException($"Weapon class {weapon.Type.Name} lacks a constructor taking WeaponData"); } // Create a factory function for this weapon type Func<WeaponData, BasicWeapon> creator = data => (BasicWeapon)constructor.Invoke(new object[] { data }); _weaponCreators.Add(weapon.Attribute.WeaponType, creator); } } public static BasicWeapon CreateWeapon(WeaponData weaponData) { if (_weaponCreators.TryGetValue(weaponData.WeaponType, out var creator)) { return creator(weaponData); } throw new ArgumentOutOfRangeException(nameof(weaponData.WeaponType), $"Unknown weapon type: {weaponData.WeaponType}"); } }
Pros:
- Auto-registration: No need to update the factory when adding new weapons—just mark the class with the attribute.
- Scalable: Perfect if you expect to add many weapon types over time.
Cons:
- Startup overhead: Reflection takes a small amount of time during initialization (usually negligible for most games/apps).
- Less explicit: You have to check the attributes on each class to see which type maps to which weapon.
Which One Should You Choose?
- Start with the Dictionary-Based Factory if you don’t have too many weapon types, or if you prefer explicit control over registration.
- Use Attribute + Reflection if you’re building a system where new weapon types will be added frequently, or if you want to decouple the factory from the weapon implementations.
Either way, you’ll eliminate the messy switch block and make your code much easier to maintain as your weapon library grows!
内容的提问来源于stack exchange,提问作者Madcode

