Skip to content

Refactoring: Split BasicBody.ts into focused modulesย #10

@FinClipper

Description

@FinClipper

BasicBody.ts at 1,774 lines is the second largest file in the codebase. It handles multiple responsibilities that should be separated:

  1. Tree node structure and traversal
  2. Value computation and caching
  3. Formula text generation
  4. Serialization/deserialization
  5. Event handling for changes

This violates the Single Responsibility Principle and makes the code difficult to maintain.

Current Class Structure

// BasicBody.ts - 1,774 lines
class BasicBody {
  // Tree structure (~400 lines)
  parent: BasicBody | null
  children: BasicBody[]
  addChild(), removeChild(), getRoot()...

  // Value computation (~500 lines)
  getValue(), computeValue(), evaluateExpression()...

  // Text generation (~300 lines)
  getTextValue(), getCharacterValue(), formatFormula()...

  // Serialization (~250 lines)
  toJSON(), fromJSON(), clone()...

  // Event handling (~150 lines)
  onChange(), notifyParent()...

  // Utility methods (~174 lines)
  // ... various helpers
}

Proposed Architecture

Option A: Composition Pattern (Recommended)

src/data/models/
โ”œโ”€โ”€ basic-body/
โ”‚   โ”œโ”€โ”€ index.ts              - Re-exports for backward compatibility
โ”‚   โ”œโ”€โ”€ BasicBody.ts          (~300 lines) - Core class with composition
โ”‚   โ”œโ”€โ”€ TreeOperations.ts     (~200 lines) - Tree traversal mixin
โ”‚   โ”œโ”€โ”€ ValueComputation.ts   (~250 lines) - Calculation logic
โ”‚   โ”œโ”€โ”€ TextGenerator.ts      (~200 lines) - Formula text formatting
โ”‚   โ”œโ”€โ”€ Serializer.ts         (~150 lines) - JSON serialization
โ”‚   โ””โ”€โ”€ types.ts              - Shared interfaces

Option B: Inheritance Hierarchy

src/data/models/
โ”œโ”€โ”€ AbstractNode.ts           - Base tree node
โ”œโ”€โ”€ ComputedNode.ts           - Adds computation (extends AbstractNode)
โ”œโ”€โ”€ BasicBody.ts              - Full implementation (extends ComputedNode)
โ””โ”€โ”€ types.ts

Implementation: Option A Details

TreeOperations.ts

export class TreeOperations<T extends TreeNode> {
  getRoot(node: T): T
  getDepth(node: T): number
  traverse(node: T, callback: (n: T) => void): void
  findByPredicate(node: T, predicate: (n: T) => boolean): T | null
  getPath(node: T): T[]
  isAncestorOf(node: T, potential: T): boolean
}

ValueComputation.ts

export class ValueComputation {
  private cache: Map<string, CachedValue>

  compute(node: BasicBody, context: EvaluationContext): number
  invalidateCache(nodeId: string): void
  getCacheStats(): CacheStatistics
}

TextGenerator.ts

export class TextGenerator {
  generateText(node: BasicBody, format: TextFormat): string
  generateCharacterRepresentation(node: BasicBody): string
  formatWithPrecedence(node: BasicBody): string
}

export enum TextFormat {
  PLAIN = 'plain',
  LATEX = 'latex',
  ASCII = 'ascii'
}

Serializer.ts

export class BasicBodySerializer {
  serialize(node: BasicBody): SerializedBasicBody
  deserialize(data: SerializedBasicBody): BasicBody
  clone(node: BasicBody, deep?: boolean): BasicBody
}

Updated BasicBody.ts

import { TreeOperations } from './TreeOperations'
import { ValueComputation } from './ValueComputation'
import { TextGenerator } from './TextGenerator'
import { BasicBodySerializer } from './Serializer'

export class BasicBody {
  private static treeOps = new TreeOperations()
  private static computation = new ValueComputation()
  private static textGen = new TextGenerator()
  private static serializer = new BasicBodySerializer()

  // Delegate to specialized classes
  getRoot() { return BasicBody.treeOps.getRoot(this) }
  getValue() { return BasicBody.computation.compute(this, this.context) }
  getTextValue() { return BasicBody.textGen.generateText(this, TextFormat.PLAIN) }
  toJSON() { return BasicBody.serializer.serialize(this) }
}

Migration Strategy

  1. Create new module files without changing BasicBody
  2. Extract logic method by method with tests
  3. Update BasicBody to delegate to new classes
  4. Ensure 100% backward compatibility
  5. Deprecate direct method usage (optional)
  6. Update documentation

Backward Compatibility

The public API of BasicBody remains unchanged:

const node = new BasicBody()
node.addChild(childNode)      // Still works
node.getValue()               // Still works
node.getTextValue()           // Still works

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions