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
- 146