Principles

S.O.L.I.D.

Single Responsibility

A class should have one and only one reason to change, meaning that a class should have only one job.

class Order {
  public calculateTotalSum() {...}
  public getItems() {...}
  public printOrder() {...}
  public showOrder() {...}
  public save() {...}
  public update() {...}
  public delete() {...}
}

What is wrong?

Single Responsibility

class Order {
  public calculateTotalSum() {...}
  public getItems() {...}
}

Better

class OrderRepository {
  public save() {...}
  public update() {...}
  public delete() {...}
}
class OrderView {
  public printOrder() {...}
  public showOrder() {...}
}

Single Responsibility

Open/closed

Classes should be open for extension, but closed for modification

abstract class Screening {
  public getTitle(type: ScreeningType): string {
    switch(type) {
      case ScreeningType.TransUnion:
        return "TransUnion: Credit Scores, Credit Reports";
      case ScreeningType.RentPrep:
        return "RentPrep: Tenant Screening Services & Rental Background";
    }
  }
  
  public getDescription(): string {
    return "Get your Credit Score & Report. Do not worry. Checking your score won't lower it.";
  }
}

What is wrong?

Open/closed

abstract class Screening {
  abstract getTitle(): string;
  
  public getDescription(): string {
    return "Get your Credit Score & Report. Do not worry";
  }
}

Better

class TransUnion extends Screening {
  public getTitle(): string {
    return "TransUnion: Credit Scores, Credit Reports";
  }
}
class RentPrep extends Screening {
  public getTitle(): string {
    return "RentPrep: Tenant Screening Services & Rental Background";
  }
}

Open/closed

Liskov Substitution

Subclasses should be substitutable for their base classes.

class User {
  constructor() {
    // ...
  }

  getSessionID(): ID {
    return this.sessID;
  }

  hasAccess(action: Actions): boolean {
    // ...
    return access;
  }

  updateProfile(data: Profile): CommandStatus {
    // ...
    return status;
  }
}
class Guest extends User {
  constructor() {
    super();
  }

  hasAccess(action: Actions): boolean {
    return access;
  }

  updateProfile(data: Profile): CommandStatus {
    // штанга! міняєм поведімку
    throw new Error(`Guests don't have profiles`);
  }
}

New type of user

Liskov Substitution

interface User {
  getSessionID(): ID;
}

interface UserWithAccess {
  hasAccess(action: Actions): boolean;
}

interface UserWithProfile {
  updateProfile(data: Profile): CommandStatus;
}
class BaseUser implements User {
  constructor() {
    // ...
  }

  getSessionID(): ID {
    return this.sessID;
  }
}

Interfaces

Base class

Liskov Substitution

class RegularUser extends BaseUser implements UserWithAccess, UserWithProfile {
  constructor() {
    super();
  }

  hasAccess(action: Actions): boolean {
    // ...
    return access;
  }

  updateProfile(data: Profile): CommandStatus {
    // ...
    return status;
  }
}

class Guest extends BaseUser implements UserWithAccess {
  constructor() {
    super();
  }

  hasAccess(action: Actions): boolean {
    // ...
    return access;
  }
}

Liskov Substitution

Interface Segregation

A client should never be forced to implement an interface that it doesn’t use or clients shouldn’t be forced to depend on methods they do not use.

interface CloudProvider {
  storeFile(name: string);
  getFile(name: string);
  createServer(region: string);
  listServers(region: string);
  getCDNAddress();
}

What is wrong?

class Dropbox implements CloudProvider {
  public storeFile(name: string) {...};
  public getFile(name: string) {...};
  public createServer(region: string) {}; // No implementation
  public listServers(region: string) {};  // No implementation
  public getCDNAddress() {};              // No implementation
}
class Amazon implements CloudProvider {
  public storeFile(name: string) {...};
  public getFile(name: string) {...};
  public createServer(region: string) {...};
  public listServers(region: string) {...};
  public getCDNAddress() {...};
}

Interface Segregation

interface CloudHostingProvider {
  createServer(region: string);
  listServers(region: string);
}

Better

class Dropbox implements CloudStorageProvider {
  public storeFile(name: string) {...};
  public getFile(name: string) {...};
}
class Amazon implements CloudStorageProvider, CloudHostingProvider,
  CDNProvider {

  public storeFile(name: string) {...};
  public getFile(name: string) {...};
  public createServer(region: string) {...};
  public listServers(region: string) {...};
  public getCDNAddress() {...};
}
interface CloudCDNProvider {
  getCDNAddress();
}
interface CloudStorageProvider {
  storeFile(name: string);
  getFile(name: string);
}

Interface Segregation

Dependency Inversion

High level classes should not depend upon low level modules. Rather, both should depend upon abstractions.

class Report {
  private db: MySQL;

  constructor(private MySQL: MySQL) {
    this.db = MySQL;
  }

  public deleteItem(id: number) {
    this.db.delete(id);
  };
}
class MySQL {
  public update(id: number) {...};

  public delete(id: number) {...};
}

Low level class

High level class

Dependency Inversion

class Report {
  private db: MongoDB;                        // private db: MySQL;

  constructor(private MongoDB: MongoDB) {     // private MySQL: MySQL
    this.db = MongoDB;                        // this.db = MySQL;
  }

  public deleteItem(id: number) {
    this.db.remove(id);                       // this.db.delete(id);
  };
}
class MongoDB {
  public update(id: number) {...};

  public remove(id: number) {...};
}

Low level class

High level class

Dependency Inversion

class Report {
  private db: DataBaseInterface;

  constructor(private MongoDB: DataBaseInterface) {
    this.db = MongoDB;
  }

  public deleteItem(id: number) {
    this.db.delete(id);
  };
}
class MongoDB implements DataBaseInterface {
  public update(id: number) {...};

  public delete(id: number) {...};
}
class MySQL implements DataBaseInterface {
  public update(id: number) {...};

  public delete(id: number) {...};
}
interface DataBaseInterface {
  update(id: number): void;
  delete(id: number): void;
}

Low level class

High level class

Abstraction

Dependency Inversion

The end

S.O.L.I.D. principles

By TenantCloud

S.O.L.I.D. principles

Solid principles using TypeScript.

  • 195