如何避免类中的代码重复?基于抽象类继承场景
Great question! When you’ve got multiple subclasses sharing similar implementation logic for methods like _save(), there are a few clean approaches to eliminate repetition while keeping your code maintainable. Let’s walk through the most practical options for your scenario:
Option 1: Extract Common Logic to the Abstract Parent Class
The simplest approach is to move shared _save() functionality into your SubstitutionAbstract base class, then let subclasses override or extend it as needed. Here’s how you could refactor:
abstract class SubstitutionAbstract { // Extract shared data construction logic into a protected helper protected buildBaseSubstitutionData(): Partial<ISubstitutionModelTeacher> { return { lesson: this.substitution.lessonNumber, confirmed: true, subsDate: this.substitution.date, // Add all other common fields here }; } // Define the abstract method to enforce implementation in subclasses public abstract _save(): void; } class SubstitutionFree extends SubstitutionAbstract { public _save(): void { // Start with the base data from the parent const substitutionAdd = this.buildBaseSubstitutionData(); // Add subclass-specific fields if needed substitutionAdd.type = "free"; // Execute the save operation (e.g., API call) api.save(substitutionAdd); } } class SubstitutionSubject extends SubstitutionAbstract { public _save(): void { const substitutionAdd = this.buildBaseSubstitutionData(); // Add Subject-specific fields substitutionAdd.subject = this.substitution.newSubject; api.save(substitutionAdd); } } // For SubstitutionTeacher, implement a completely custom save logic class SubstitutionTeacher extends SubstitutionAbstract { public _save(): void { const teacherSubData = { lesson: this.substitution.lessonNumber, teacherId: this.substitution.newTeacherId, // Custom fields for teacher substitutions }; api.saveTeacherSubstitution(teacherSubData); } }
This keeps shared code centralized, making it easy to update later without touching every subclass.
Option 2: Use Mixins for Reusable Functionality
If you want to keep the parent class focused and avoid cluttering it with save logic, mixins let you inject reusable _save() behavior into only the subclasses that need it:
// Define a constructor type for mixins type Constructor<T = {}> = new (...args: any[]) => T; // Create a mixin with the base save logic function SubstitutionSaveMixin<TBase extends Constructor<SubstitutionAbstract>>(Base: TBase) { return class extends Base { public _save(): void { const substitutionAdd = { lesson: this.substitution.lessonNumber, confirmed: true, subsDate: this.substitution.date, }; // Hook for subclasses to modify data before save this.beforeSave(substitutionAdd); api.save(substitutionAdd); } // Default empty hook (subclasses can override this) protected beforeSave(data: Partial<ISubstitutionModelTeacher>): void {} }; } // Apply the mixin to SubstitutionFree class SubstitutionFree extends SubstitutionSaveMixin(SubstitutionAbstract) { protected beforeSave(data: Partial<ISubstitutionModelTeacher>): void { data.type = "free"; } } // Apply the mixin to SubstitutionSubject class SubstitutionSubject extends SubstitutionSaveMixin(SubstitutionAbstract) { protected beforeSave(data: Partial<ISubstitutionModelTeacher>): void { data.subject = this.substitution.newSubject; } } // SubstitutionTeacher still implements its own save logic class SubstitutionTeacher extends SubstitutionAbstract { public _save(): void { // Custom teacher-specific save logic here } }
Mixins are great when you want to reuse logic across unrelated classes later on.
Option 3: Strategy Pattern for Flexible Data Construction
If the main difference between subclasses is how they construct the substitution data (rather than the save process itself), the strategy pattern lets you decouple data building from the save method:
// Define a strategy interface for data construction interface SubstitutionDataStrategy { buildData(substitution: any): Partial<ISubstitutionModelTeacher>; } // Base strategy with shared logic class BaseSubstitutionStrategy implements SubstitutionDataStrategy { buildData(substitution: any): Partial<ISubstitutionModelTeacher> { return { lesson: substitution.lessonNumber, confirmed: true, subsDate: substitution.date, }; } } // Free-specific strategy class FreeSubstitutionStrategy extends BaseSubstitutionStrategy { buildData(substitution: any): Partial<ISubstitutionModelTeacher> { const baseData = super.buildData(substitution); return { ...baseData, type: "free" }; } } // Subject-specific strategy class SubjectSubstitutionStrategy extends BaseSubstitutionStrategy { buildData(substitution: any): Partial<ISubstitutionModelTeacher> { const baseData = super.buildData(substitution); return { ...baseData, subject: substitution.newSubject }; } } // Update the abstract parent to use a strategy abstract class SubstitutionAbstract { protected dataStrategy: SubstitutionDataStrategy; constructor(strategy: SubstitutionDataStrategy) { this.dataStrategy = strategy; } public _save(): void { const substitutionAdd = this.dataStrategy.buildData(this.substitution); api.save(substitutionAdd); } } // Initialize each subclass with its strategy class SubstitutionFree extends SubstitutionAbstract { constructor() { super(new FreeSubstitutionStrategy()); } } class SubstitutionSubject extends SubstitutionAbstract { constructor() { super(new SubjectSubstitutionStrategy()); } } class SubstitutionTeacher extends SubstitutionAbstract { constructor() { super({ buildData: (substitution) => ({ lesson: substitution.lessonNumber, teacherId: substitution.newTeacherId, // Custom teacher data fields }) }); } }
This pattern makes it easy to add new substitution types later without modifying existing code—just create a new strategy class.
Which Option to Choose?
- Option 1 is best for simple cases where most subclasses share core save logic.
- Option 2 works well if you want to keep the parent class lean and reuse the save logic elsewhere.
- Option 3 is ideal if your substitution types vary mostly in how they construct data, and you want maximum flexibility.
内容的提问来源于stack exchange,提问作者dooglu

