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