Skip to content

Strategy Pattern, a good way to extend my legacy code #101

@reboottime

Description

@reboottime

Background

  1. I inherited a legacy codebase from an offshore outsourced team and currently have limited access to them. Unfortunately, the project workflow needs urgent attention, but the giant frontend codebase was built around an unfinished backend API service. In other words, the frontend code data model is incompatible with the old backend API services.

  2. To address this issue, there is a workaround solution that gives users the option to choose which backend API services they want to use. This allows us to provide flexibility to users while we resolve compatibility issues.

  3. As a result of this solution, I had to modify the behavior of my utility functions to dynamically adapt to the user's choice of backend API endpoint. This ensures that the codebase remains modular and extensible, such that I do not need to change my frontend code dramatically urgently.


Solution

Proposal

To minimize changes to UI related frontend code, my utils code decides what logic to use at the time the user switches backend API services. This turns out to be the Strategy Design Pattern, which is the perfect solution I will count on.

Pseudo Code

  • Design the util contract such that the methods provided by two different utils share the same signature
interface IPlatformUtils {

  // Define some methods that IPlatformClient

  // Example

  finalizeResult: () => Promise<boolean>;

}
  • Legacy utils
class LegacyUtils implements IPlatformUtils {

  // Implements methods defined on IPlatformClient

}
  • Define the wrapper utils (context)
class Utils {

  private currentUtil: IPlatformUtils;

  private readonly allVersionOfUtils = {

    legacy: new LegacyUtils(),

    current: new CurrentUtils(), // the current utils codebase

  };

  constructor(utilName: 'legacy' | 'current') {

    this.currentUtil = this.allVersionOfUtils[utilName];

  }

  switchToPlatform(utilName: 'legacy' | 'current') {

    this.currentUtil = this.allVersionOfUtils[utilName];

  }

  // Define all the IPlatformUtils methods that returns the selected platform method

  finalizeResult (...args) {

    return this.currentUtil.finalizeResult(args);

  }

}

export default new MyUtils('legacyUtils');
  • How we will use the utils in frontend UI code
import { useState } from 'react';

import utils from 'utils';

const platformOptions = ['web', 'ios', 'android'];

const UiSolution = () => {

  const [platform, setPlatform] = useState('web');

  const handlePlatformChange = (event) => {

    setPlatform(event.target.value);

  };

  const hanldeFinalizeButtonClick = () => {

    utils.switchToPlatform(platform);

    utils.finalizeResult();

  };

  return (

    <>

      <select name="platform"

        onChange={handlePlatformChange}

        value={platform}

      >

        {platformOptions.map((platform) => (

          <option key={platform}

            value={platform}>{platform}</option>

        ))}

      </select>

      <button onClick={handleFinalizeButtonClick}>Finalize Result</button>

    </>

  );

};

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions