BOM系统中用接口布尔属性替代类型检查的最佳实践咨询
Great question—this is a super common tradeoff between readability, coupling, and maintainability when building BOM systems, especially with the constraints of CAD tools. Let’s walk through your approach, its hidden risks, and practical alternatives that play nice with your single-command limitation.
Your Current Approach: Pros & Cons
First, let’s acknowledge what works:
- Your boolean flags make type checks like
Any(item => item.IsCommodity)very readable at a glance. - You’ve reduced direct type dependencies (no
is ClassifiedItemchecks), which feels like a step toward loose coupling.
But as you suspect, this approach starts to creak when you scale.
Potential Risks to Watch For
- Boolean Property Explosion: Every new material type (e.g., raw materials, subassemblies) will force you to add a new boolean to
IMaterialand update all existing implementations. This violates the Open/Closed Principle (open for extension, closed for modification) and becomes a maintenance nightmare fast. - Inconsistent State Bugs: It’s easy for a developer to accidentally set
IsAssembly = truein aCommodityclass. These bugs are hard to catch because they don’t throw errors—they just make your BOM logic behave incorrectly. - Logic Still Coupled to Types: Your
Executemethod still has hardcoded checks forIsAssemblyto create symbols. When you add a new material type that needs a different symbol, you’ll have to modify this method anyway. The boolean flags don’t solve the core coupling problem here.
Better Alternatives for Your CAD Constraint
Since you can’t split commands, you need a way to keep Execute clean while making the system extensible. Here are two patterns that fit perfectly:
1. Polymorphism (Move Behavior to Implementations)
Instead of using flags to decide what to do, let each material type handle its own behavior. Update your IMaterial interface to include methods for type-specific actions:
public interface IMaterial { // Remove the boolean flags unless you truly need them for filtering IBlockSymbol CreateBlockSymbol(); // Add other type-specific methods here as needed } // Assembly implementation public class AssemblyMaterial : IMaterial { public IBlockSymbol CreateBlockSymbol() => new BomAssemblySymbol(); // Your other properties/methods } // Commodity implementation public class CommodityMaterial : IMaterial { public IBlockSymbol CreateBlockSymbol() => new BomItemSymbol(); // Your other properties/methods }
Now your Execute method becomes much cleaner—no more if-else checks:
public override void Execute() { IMaterial bomMaterial = null; bool multipleByReference = false; Editor ed = Application.DocumentManager.MdiActiveDocument.Editor; if (!TryGetMaterialInformation(out bomMaterial, out multipleByReference)) { ed.WriteMessage("\nExiting command.\n"); return; } // Let the material handle symbol creation IBlockSymbol symbol = bomMaterial.CreateBlockSymbol(); if (multipleByReference) { SymbolUtility.InsertMultipleByReferenceSymbol(symbol, bomMaterial); } else { SymbolUtility.InsertSymbol(symbol, bomMaterial); } }
When you add a new material type, you just create a new IMaterial implementation with its own CreateBlockSymbol method—no changes to existing code. Perfect for scalability.
2. Enumeration for Type Filtering (If You Need It)
If you still need to filter collections by material type (like your hasCommodities check), replace the boolean flags with a MaterialType enum instead. This avoids flag bloat and keeps type checks explicit:
public enum MaterialType { Commodity, Assembly, Unclassified, // Add new types here later } public interface IMaterial { MaterialType Type { get; } IBlockSymbol CreateBlockSymbol(); } // In your Commodity class: public MaterialType Type => MaterialType.Commodity;
Then your filtering becomes:
bool hasCommodities = materialCollection.Any(item => item.Type == MaterialType.Commodity);
This is far more maintainable than adding a new boolean every time you expand the system.
Bonus: Factory Pattern (For Complex Creation Logic)
If creating symbols gets more complicated (e.g., dependencies, configuration), you can extract that logic into a factory class that takes an IMaterial and returns the right symbol. But for most cases, polymorphism will be simpler and more direct.
Final Takeaway
Your initial approach solves a small part of the coupling problem, but it doesn’t scale well. By shifting to polymorphism (and an enum if needed), you’ll keep your codebase maintainable, avoid bugs from inconsistent flags, and adhere to best practices like the Open/Closed Principle. The best part? It fits perfectly with your CAD tool’s single-command constraint—all logic stays encapsulated in the material types, so your Execute method stays clean and doesn’t need changes when you add new materials.
内容的提问来源于stack exchange,提问作者bjhuffine

