Built a Text Editor using Low-Level Design & Design Patterns in Python

Built a Text Editor using Low-Level Design & Design Patterns in Python

I worked on implementing a Text Editor (LLD) in Python—focusing not on the UI, but on how real editors efficiently store and manage text behind the scenes.

To make the editor scalable and memory-efficient, I implemented the Flyweight Design Pattern and structured the project into clean, modular classes. This exercise helped me understand how large-scale editors (like VS Code, Sublime, etc.) avoid storing duplicate styling data and handle operations like insert, delete, and read efficiently.


🧱 Design Patterns Used

🔹 Flyweight Pattern

I used the Flyweight pattern to avoid storing duplicate style objects. For example, if the user types many characters with the same font settings, the editor reuses the same style object instead of creating new ones.

Where it's used: CharFlyweight + CharFlyweightFactory

  • CharFlyweight → Holds a character + style
  • CharFlyweightFactory → Ensures reusable, memory-efficient style objects


🧩 Class Breakdown

1️⃣ CharFlyweight

Represents a character along with its styling (font, size, bold, italic). Instead of storing styling for every character separately, we share these objects.

@dataclass
class CharFlyweight:
    ch: str
    font_name: str
    font_size: int
    is_bold: bool
    is_italic: bool
        

2️⃣ CharFlyweightFactory

The heart of the Flyweight pattern. It stores previously created styles and reuses them when the same combination appears again.

This significantly reduces memory usage when thousands of characters share the same style.

3️⃣ TextRow

Represents a single row in the editor. Handles:

  • Adding a character at a specific column
  • Reading the full line
  • Deleting a character

This keeps row-level logic clean and modular.

4️⃣ Solution (Main Editor Controller)

This is the main class that:

  • Holds all rows
  • Connects user operations to row operations
  • Uses the factory to create flyweight objects

Supports features like:

  • insert character
  • delete character
  • read line
  • get style of a specific character


Full code:

from dataclasses import dataclass, field
from typing import List, Dict, Optional


class Solution:
    def __init__(self):
        self.factory = CharFlyweightFactory()
        self.rows: List[TextRow] = []

    def init(self, helper):
        self.helper = helper

    def add_character(self, row: int, column: int, ch: str, font_name: str,
                      font_size: int, is_bold: bool, is_italic: bool):
        """
        Add a character at a specific row and column.
        """
        while row >= len(self.rows):
            self.rows.append(TextRow())
        
        flyweight = self.factory.create_style(ch, font_name, font_size, is_bold, is_italic)
        self.rows[row].add_character(flyweight, column)

    def get_style(self, row: int, col: int) -> str:
        """
        Retrieve the style of a character at a specific row and column.
        """
        if row < 0 or row >= len(self.rows):
            return ""
        flyweight = self.rows[row].get_flyweight(col)
        return flyweight.get_char_and_style() if flyweight else ""

    def read_line(self, row: int) -> str:
        """
        Read a full line at a specific row.
        """
        if row < 0 or row >= len(self.rows):
            return ""
        flyweights = self.rows[row].read_line()
        return ''.join(flyweight.get_char() for flyweight in flyweights)

    def delete_character(self, row: int, col: int) -> bool:
        """
        Delete a character at a specific row and column.
        """
        if row < 0 or row >= len(self.rows):
            return False
        return self.rows[row].delete_character(col)


@dataclass
class CharFlyweight:
    """
    Represents a character with its font style attributes.
    """
    ch: str
    font_name: str
    font_size: int
    is_bold: bool
    is_italic: bool

    def get_char(self) -> str:
        """Returns the character."""
        return self.ch

    def get_char_and_style(self) -> str:
        """Returns a formatted string with character and its style."""
        style = f"{self.ch}-{self.font_name}-{self.font_size}"
        if self.is_bold:
            style += "-b"
        if self.is_italic:
            style += "-i"
        return style


class CharFlyweightFactory:
    """
    Factory for creating and reusing CharFlyweight instances.
    """
    def __init__(self):
        self.map: Dict[str, CharFlyweight] = {}

    def create_style(self, ch: str, font_name: str, font_size: int,
                     is_bold: bool, is_italic: bool) -> 'CharFlyweight':
        key = f"{ch}-{font_name}-{font_size}"
        if is_bold:
            key += "-b"
        if is_italic:
            key += "-i"
        
        if key not in self.map:
            self.map[key] = CharFlyweight(ch, font_name, font_size, is_bold, is_italic)
        
        return self.map[key]


class TextRow:
    """
    Represents a single row in the text editor.
    """
    def __init__(self):
        self.data: List[CharFlyweight] = []

    def add_character(self, ch: CharFlyweight, column: int):
        """
        Add a character at a specific column.
        """
        # keep behavior: if column > length, append; else insert at column
        if column >= len(self.data):
            self.data.append(ch)
        else:
            self.data.insert(column, ch)

    def get_flyweight(self, column: int) -> Optional['CharFlyweight']:
        """
        Retrieve a CharFlyweight at the given column.
        """
        if 0 <= column < len(self.data):
            return self.data[column]
        return None

    def read_line(self) -> List['CharFlyweight']:
        """
        Return the entire row as a list of CharFlyweights.
        """
        return self.data

    def delete_character(self, col: int) -> bool:
        """
        Delete a character at a specific column.
        """
        if 0 <= col < len(self.data):
            self.data.pop(col)
            return True
        return False
        
Article content
UML diagram

🏗️ UML Class Diagram Analysis

The diagram illustrates how the classes are structured to manage character data and styles efficiently using the Flyweight pattern.

1. CharFlyweight (Flyweight)

  1. Role: The Flyweight class. It stores the extrinsic (shared) state: the character itself (ch) and its full style attributes (font_name, font_size, is_bold, is_italic).

Relationships:

  • Dependency/Creation: Created by CharFlyweightFactory.
  • Composition: TextRow contains a list of CharFlyweight objects.

2. CharFlyweightFactory (Flyweight Factory)

  • Role: Manages and ensures the sharing of CharFlyweight objects.
  • Attributes: map (a dictionary) acts as a pool to store and look up existing flyweights based on their combined style key.
  • Method: create_style() is the core method for retrieving an existing flyweight or creating a new one if none exists.
  • Relationships:
  • Creates/Manages: Creates and stores instances of CharFlyweight.
  • Used by: Solution class.

3. TextRow

  • Role: Manages the sequence of characters (flyweights) within a single line of text.
  • Attributes: data (a list) stores the sequence of CharFlyweight objects for that row. Since the flyweights hold the extrinsic state, the row structure holds the intrinsic (unique/locational) state (the order and position of the characters).
  • Relationships:
  • Composition: Contains a list of CharFlyweight objects.
  • Used by: Solution class.

4. Solution (Client/Context)

  • Role: The main client or context that utilizes the Flyweight and other components to provide the text editor's high-level functionality.
  • Attributes:
  • Relationships:
  • Dependency/Association: Holds references to CharFlyweightFactory and TextRow objects. It is responsible for coordinating their use (e.g., calling factory.create_style() and then row.add_character()).


🧠 Key Learnings

✔ How real text editors avoid memory overhead

✔ Importance of separating logic into small, meaningful classes

✔ How the Flyweight pattern improves performance in high-data applications

✔ Clean LLD structuring to simulate real-world systems


#Python #DesignPatterns #SoftwareDesign #FlyWeightPattern #FactoryPattern #LLD #BackendDevelopment #CleanCode #OOP


To view or add a comment, sign in

Others also viewed

Explore content categories