Generics Class

RECAP

 

WHAT IS GENERICS?

WHY GENERICS?

What is Generics?

  • a kind of tool
  • enable programmers to create reusable method and class that can handle any type of data
  • able to retrieve complete type information

Why Generics?

  • reduce / eliminate the need for casting
  • change runtime error into compile time error

Generics Example

function create<T>(data: T): T {
    return data;
};
interface KeyValue<K,V> {
    key: K;
    value: V;
}

Single Type:

Multiple Type:

Use Case:

Algorithms and Data Structure

class Array<T> {
    private _list: T[] = [];

    public add(value: T){
        this._list.push(value);
    }

    public get(index: number): T {
        return this._list[index];
    }

}

Use Case:

Key Value Typed Boxes

interface KeyValue<K,V> {
    key: K;
    value: V;
}

class Map<K,V> {
    
    private _obj: KeyValue<K,V>[] = [];

    public set(key: K, value: V){
        const obj = this._obj.find(e => e.key === key)
        if(obj){
            obj.value = value;
            return;
        }
        this._obj.push({key, value})
    }

    public get(key: K): V | undefined {
        const obj = this._obj.find(e => e.key === key)
        if(obj){
            return obj.value;
        }
        return undefined;
    }

    public obj(): KeyValue<K,V>[]{
        return this._obj;
    }
}

Use Case:

Create Object by Generic Type

public class Factory
{
    public T Create<T>()
        where T: new()
    {
        return new T();
    }
}

var factory = new Factory();
var person = factory.Create<Person>();

In C# we can define a generic type argument and instantiate this type at runtime

Use Case:

Create Object by Generic Type

class Person {
    firstName = 'John';
    lastName = 'Doe';

    get(): string {
        return `${this.firstName} ${this.lastName}`
    }
}
let person = Creator.create(Person);
console.log(person.get()); // John Doe
class Creator {
    static create<T>(type: (new () => T)): T {
        return new type();
    }
}

generic type argument T is not available at runtime because TypeScript generics are only used at compile time for type checking and removed in the transpiled JavaScript code

Strategy Pattern with Generics

Statement: Calculating tax for different Categories of TaxPayer

export interface TaxStrategy {
  extortCash(p: TaxPayer): number;
}

export class EmployeeTaxStrategy implements TaxStrategy {
  private static NORMAL_RATE: number = 0.40;
  private static MARRIED_FEMALE_RATE: number = 0.48;

  public extortCash(p: TaxPayer): number {
    let e: Employee = <Employee>p; // here we need to downcast!!!
    if (e.isMarried() &&
        e.getGender() == Gender.FEMALE) {
      return e.getIncome() * EmployeeTaxStrategy.MARRIED_FEMALE_RATE;
    }
    return e.getIncome() * EmployeeTaxStrategy.NORMAL_RATE;
  }
}

export class TaxPayer {
  public static EMPLOYEE: TaxStrategy = new EmployeeTaxStrategy();
  public static COMPANY: TaxStrategy = new CompanyTaxStrategy();
  
  private strategy: TaxStrategy;
  private income: number;

  public constructor(strategy: TaxStrategy, income: number) {
    this.strategy = strategy;
    this.income = income;
  }
  
  public getIncome(): number {
    return this.income;
  }

  public extortCash(): number {
    return this.strategy.extortCash(this);
  }
}

export class Employee extends TaxPayer {
  private married: boolean;
  private gender: Gender;

  public constructor(strategy: TaxStrategy, income: number, married: boolean, gender: Gender) {
    super(strategy, income);
    this.married = married;
    this.gender = gender;
  }

  public isMarried(): boolean {
    return this.married;
  }

  public getGender(): Gender {
    return this.gender;
  }
}

export enum Gender {
  MALE,
  FEMALE
}

export class ReceiverOfRevenue {
  public static main() {
    let heinz: Employee = new Employee(TaxPayer.EMPLOYEE, 50000, true, Gender.FEMALE);
    let maxsol: Company = new Company(TaxPayer.COMPANY, 100000, 50);
    console.log(heinz.extortCash());
    console.log(maxsol.extortCash());
  }
}

By using Generic Type

export interface TaxStrategy<P extends TaxPayer<P>> {
  extortCash(p: P): number;
}
export interface TaxStrategy {
  extortCash(p: TaxPayer): number;
}

before using Generics:

after using Generics:

export class EmployeeTaxStrategy implements TaxStrategy<Employee> {
  private static NORMAL_RATE: number = 0.40;
  private static MARRIED_FEMALE_RATE: number = 0.48;

  public extortCash(e: Employee): number {
    if (e.isMarried() &&
        e.getGender() == Gender.FEMALE) {
      return e.getIncome() * EmployeeTaxStrategy.MARRIED_FEMALE_RATE;
    }
    return e.getIncome() * EmployeeTaxStrategy.NORMAL_RATE;
  }
}
export class EmployeeTaxStrategy implements TaxStrategy {
  private static NORMAL_RATE: number = 0.40;
  private static MARRIED_FEMALE_RATE: number = 0.48;

  public extortCash(p: TaxPayer): number {
    let e: Employee = <Employee>p; // here we need to downcast!!!
    if (e.isMarried() &&
        e.getGender() == Gender.FEMALE) {
      return e.getIncome() * EmployeeTaxStrategy.MARRIED_FEMALE_RATE;
    }
    return e.getIncome() * EmployeeTaxStrategy.NORMAL_RATE;
  }
}

before using Generics:

after using Generics:

export abstract class TaxPayer<P extends TaxPayer<P>> {
  public static EMPLOYEE: TaxStrategy<Employee> = new EmployeeTaxStrategy();
  public static COMPANY: TaxStrategy<Company> = new CompanyTaxStrategy();

  private strategy: TaxStrategy<P>;
  private income: number;
  
  public constructor(strategy: TaxStrategy<P>, income: number) {
    this.strategy = strategy;
    this.income = income;
  }

  protected abstract getDetailedType(): P;

  public getIncome(): number {
    return this.income;
  }

  public extortCash(): number {
    return this.strategy.extortCash(this.getDetailedType());
  }
}
export class TaxPayer {
  public static EMPLOYEE: TaxStrategy = new EmployeeTaxStrategy();
  public static COMPANY: TaxStrategy = new CompanyTaxStrategy();
  
  private strategy: TaxStrategy;
  private income: number;

  public constructor(strategy: TaxStrategy, income: number) {
    this.strategy = strategy;
    this.income = income;
  }
  
  public getIncome(): number {
    return this.income;
  }

  public extortCash(): number {
    return this.strategy.extortCash(this);
  }
}

after using Generics:

before using Generics:

export class Employee extends TaxPayer<Employee> {
  private married: boolean;
  private gender: Gender;

  public constructor(strategy: TaxStrategy<Employee>, income: number, married: boolean, gender: Gender) {
    super(strategy, income);
    this.married = married;
    this.gender = gender;
  }

  protected getDetailedType(): Employee {
    return this;
  }

  public isMarried(): boolean {
    return this.married;
  }

  public getGender(): Gender {
    return this.gender;
  }
}

export enum Gender {
  MALE,
  FEMALE
}
export class Employee extends TaxPayer {
  private married: boolean;
  private gender: Gender;

  public constructor(strategy: TaxStrategy, income: number, married: boolean, gender: Gender) {
    super(strategy, income);
    this.married = married;
    this.gender = gender;
  }

  public isMarried(): boolean {
    return this.married;
  }

  public getGender(): Gender {
    return this.gender;
  }
}

export enum Gender {
  MALE,
  FEMALE
}

after using Generics:

before using Generics:

export interface TaxStrategy {
  extortCash(p: TaxPayer): number;
}

export class EmployeeTaxStrategy implements TaxStrategy {
  private static NORMAL_RATE: number = 0.40;
  private static MARRIED_FEMALE_RATE: number = 0.48;

  public extortCash(p: TaxPayer): number {
    let e: Employee = <Employee>p; // here we need to downcast!!!
    if (e.isMarried() &&
        e.getGender() == Gender.FEMALE) {
      return e.getIncome() * EmployeeTaxStrategy.MARRIED_FEMALE_RATE;
    }
    return e.getIncome() * EmployeeTaxStrategy.NORMAL_RATE;
  }
}

export class CompanyTaxStrategy implements TaxStrategy {
  private static BIG_COMPANY_RATE: number = 0.30;
  private static SMALL_COMPANY_RATE: number = 0.15;

  public extortCash(p: TaxPayer): number {
    let company: Company = <Company>p; // here we need to downcast!!!
    if (company.getNumberOfEmployees() > 5
        && company.getIncome() < 1000000) {
      return company.getIncome() * CompanyTaxStrategy.SMALL_COMPANY_RATE;
    }
    return company.getIncome() * CompanyTaxStrategy.BIG_COMPANY_RATE;
  }
}

export class TaxPayer {
  public static EMPLOYEE: TaxStrategy = new EmployeeTaxStrategy();
  public static COMPANY: TaxStrategy = new CompanyTaxStrategy();
  // public static TRUST: TaxStrategy = new TrustTaxStrategy();
  
  private strategy: TaxStrategy;
  private income: number;

  public constructor(strategy: TaxStrategy, income: number) {
    this.strategy = strategy;
    this.income = income;
  }
  
  public getIncome(): number {
    return this.income;
  }

  public extortCash(): number {
    return this.strategy.extortCash(this);
  }
}

export enum Gender {
  MALE,
  FEMALE
}

export class Employee extends TaxPayer {
  private married: boolean;
  private gender: Gender;

  public constructor(strategy: TaxStrategy, income: number, married: boolean, gender: Gender) {
    super(strategy, income);
    this.married = married;
    this.gender = gender;
  }

  public isMarried(): boolean {
    return this.married;
  }

  public getGender(): Gender {
    return this.gender;
  }
}

export class Company extends TaxPayer {
  private numberOfEmployees: number;

  public constructor(strategy: TaxStrategy, income: number, numberOfEmployees: number) {
    super(strategy, income);
    this.numberOfEmployees = numberOfEmployees;
  }

  public getNumberOfEmployees(): number {
    return this.numberOfEmployees;
  }
}

export class ReceiverOfRevenue {
  public static main() {
    let heinz: Employee = new Employee(TaxPayer.EMPLOYEE, 50000, true, Gender.FEMALE);
    let maxsol: Company = new Company(TaxPayer.COMPANY, 100000, 50);
    // let family: Trust = new Trust(TaxPayer.TRUST, 30000);
    console.log(heinz.extortCash());
    console.log(maxsol.extortCash());
    // console.log(family.extortCash());
  }
}

ReceiverOfRevenue.main();
//24000
//15000

Coding Before using Generics

export interface TaxStrategy<P extends TaxPayer<P>> {
  extortCash(p: P): number;
}

export class EmployeeTaxStrategy implements TaxStrategy<Employee> {
  private static NORMAL_RATE: number = 0.40;
  private static MARRIED_FEMALE_RATE: number = 0.48;

  public extortCash(e: Employee): number {
    if (e.isMarried() &&
        e.getGender() == Gender.FEMALE) {
      return e.getIncome() * EmployeeTaxStrategy.MARRIED_FEMALE_RATE;
    }
    return e.getIncome() * EmployeeTaxStrategy.NORMAL_RATE;
  }
}

export class CompanyTaxStrategy implements TaxStrategy<Company> {
  private static BIG_COMPANY_RATE: number = 0.30;
  private static SMALL_COMPANY_RATE: number = 0.15;

  public extortCash(company: Company): number {
    if (company.getNumberOfEmployees() > 5
        && company.getIncome() < 1000000) {
      return company.getIncome() * CompanyTaxStrategy.SMALL_COMPANY_RATE;
    }
    return company.getIncome() * CompanyTaxStrategy.BIG_COMPANY_RATE;
  }
}

export abstract class TaxPayer<P extends TaxPayer<P>> {
  public static EMPLOYEE: TaxStrategy<Employee>  = new EmployeeTaxStrategy();
  public static COMPANY: TaxStrategy<Company> = new CompanyTaxStrategy();
  // public static TRUST: TaxStrategy<Trust> = new TrustTaxStrategy();
  private strategy: TaxStrategy<P>;
  private income: number;
  
  public constructor(strategy: TaxStrategy<P>, income: number) {
    this.strategy = strategy;
    this.income = income;
  }

  protected abstract getDetailedType(): P;

  public getIncome(): number {
    return this.income;
  }

  public extortCash(): number {
    return this.strategy.extortCash(this.getDetailedType());
  }
}

export enum Gender {
  MALE,
  FEMALE
}

export class Employee extends TaxPayer<Employee> {
  private married: boolean;
  private gender: Gender;

  public constructor(strategy: TaxStrategy<Employee>, income: number, married: boolean, gender: Gender) {
    super(strategy, income);
    this.married = married;
    this.gender = gender;
  }

  protected getDetailedType(): Employee {
    return this;
  }

  public isMarried(): boolean {
    return this.married;
  }

  public getGender(): Gender {
    return this.gender;
  }
}

export class Company extends TaxPayer<Company> {
  private numberOfEmployees: number;

  public constructor(strategy: TaxStrategy<Company>, income: number, numberOfEmployees: number) {
    super(strategy, income);
    this.numberOfEmployees = numberOfEmployees;
  }

  protected getDetailedType(): Company {
    return this;
  }

  public getNumberOfEmployees(): number {
    return this.numberOfEmployees;
  }
}

export class ReceiverOfRevenue {
  public static main() {
    let heinz: Employee = new Employee(TaxPayer.EMPLOYEE, 50000, true, Gender.FEMALE);
    let maxsol: Company = new Company(TaxPayer.COMPANY, 100000, 50);
    // let family: Trust = new Trust(TaxPayer.TRUST, 30000);
    console.log(heinz.extortCash());
    console.log(maxsol.extortCash());
    // console.log(family.extortCash());
  }
}

ReceiverOfRevenue.main();
//24000
//15000

Coding After using Generics

Reference

  • https://www.youtube.com/watch?v=5kVcJxPt2uI
  • https://www.youtube.com/watch?v=_ZnNZWlyw7M
  • https://dzone.com/articles/hack-1-understanding-the-use-cases-of-generics
  • https://blog.rsuter.com/how-to-instantiate-a-generic-type-in-typescript/
  • https://www.javaspecialists.eu/archive/Issue123-Strategy-Pattern-with-Generics.html

What are Generics Classes?

  • same as a non-generic class
  • dont have to specify a type, meaning that a Generic class can make generic objects that accepts multiple types, and a type can be specified on the object, instead of the class.

Generic Class

By shirlin1028

Generic Class

  • 114