ThomasReggi
ThomasReggi

Reputation: 59475

Trouble creating parent class

I am struggling creating a parent class for this "normalizer" type. I have a couple of classes that all share this same class structure. I'd love for a way to have the EmailNormalizer just contain some of the logic inside the constructor, and have all the common functionality tucked away in the parent class.

I have this email normalizer class:

export class EmailNormalizer {
  readonly TYPE: string | EmailNormalizer;
  normalized: string;
  constructor(raw: EmailNormalizer['TYPE']) {
    if (!raw) throw new Error('Invalid Email');
    if (raw instanceof EmailNormalizer) {
      this.normalized = raw.normalized;
    } else {
      const results = email.analyze(raw);
      if (results) throw new Error(`Email ${results.error}`);
      this.normalized = _normalizeEmail(raw);
    }
  }
  static normalize(raw: EmailNormalizer['TYPE']) {
    return new EmailNormalizer(raw).normalized;
  }
}

You can use it two ways

I am trying to create a shared "normalizer" parent class so I don't forget any of the pieces.

This was my first attempt. I am running into issues with the static method, and then I ran into issues when I attempted to make a more generic normalizer method.

class Normalizer<In = unknown, Out = unknown> {
  readonly TYPE: In;
  normalized: Out;
  normalize: (raw: In) => Out;
  constructor(raw: In) {
    this.normalized = this.normalize(raw);
  }
  static normalize<In, T extends typeof Normalizer>(this: T, raw: In) {
    return new this(raw).normalized;
  }
}

export class EmailNormalizer extends Normalizer<EmailNormalizer | string, string> {
  normalize = (raw: Email['TYPE']) => {
    if (raw instanceof Email) return raw.normalized;
    if (!raw) throw new Error('Invalid Email');
    const results = email.analyze(raw);
    if (results) throw new Error(`Email ${results.error}`);
    return _normalizeEmail(raw);
  };
}

function normalize<I extends any, O extends any, T extends Normalizer<I, O>>(i: T, value) {
  return new i(value).normalized;
}

// ideally
EmailNormalizer.normalize('[email protected]');
// or
normalize(EmailNormalizer, '[email protected]')

Can anyone help design a normalize parent class?

Does this parent class need to be abstract?

Upvotes: 0

Views: 42

Answers (1)

falinsky
falinsky

Reputation: 7428

I could suggest considering using composition over inheritance. For example, some strategy pattern would be helpful here:

interface NormalizerStrategy<In, Out> {
  normalize: (raw: In) => Out;
}

class EmailNormalizerStrategy implements NormalizerStrategy<string, string> {
  normalize(raw: string): string {
    // pretend like there is some processing here
    const result = raw;

    return result;
  }
}

class AgeNormalizerStrategy implements NormalizerStrategy<string, number> {
  normalize(raw: string): number {
    // pretend like there is some processing here
    const result = Number(raw);

    return result;
  }
}

class Normalizer<In, Out> {
  constructor(private readonly strategy: NormalizerStrategy<In, Out>) {}

  normalize(input: In): Out {
    this.someProcessingCommonToAllTheStrategies(input);
    
    return this.strategy.normalize(input);
  }

  private someProcessingCommonToAllTheStrategies(data: In): void {
    // pretend like there is some processing here
  }
}

const emailNormalizer = new Normalizer(new EmailNormalizerStrategy);
const normalizedEmail: string = emailNormalizer.normalize('some email');

const ageNormalizer = new Normalizer(new AgeNormalizerStrategy);
const normalizedAge: number = ageNormalizer.normalize('some age');

Upvotes: 2

Related Questions