KEMBAR78
SOLID principles with Typescript examples | PDF
SOLID
PRINCIPLES
WITH
TYPESCRIPT
EXAMPLES
ANDREW NESTER
Software Engineer
WHAT IS SOLID?
SINGLE RESPONSIBILITY
OPEN - CLOSED
LISKOV SUBSTITUTION
INTERFACE SEGREGATION
DEPENDENCY INVERSION
BUT WHY?
MORE UNDERSTANDABLE CODE DESIGNS
EASIER TO MAINTAIN
EASIER TO EXTEND
SINGLE RESPONSIBILITY
DO ONE AND ONLY ONE THING
BUT DO IT WELL
WHAT IS WRONG?
class Order {
public calculateTotalSum() {/* ... */ }
public getItems() {/* ... */ }
public getItemCount() {/* ... */ }
public addItem(item: Item) {/* ... */ }
public removeItem(item: Item) {/* ... */ }
public printOrder() {/* ... */ }
public showOrder() {/* ... */ }
public load() {/* ... */ }
public save() {/* ... */ }
public update() {/* ... */ }
public delete() {/* ... */}
}
DIFFICULT TO MAINTAIN
LOTS OF REASONS TO CHANGE
CASCADE CHANGES
BETTER CODE
class Order
{
public calculateTotalSum() {/*...*/}
public getItems() {/*...*/}
public getItemCount() {/*...*/}
public addItem(item: Item) {/*...*/}
public deleteItem(item: Item) {/*...*/}
}
class OrderRepository
{
public load(){}
public save(){}
public update(){}
public delete(){}
}
class OrderViewer
{
public printOrder(){}
public showOrder(){}
}
OPEN - CLOSED
FEEL FREE TO EXTEND BUT
DO NOT MODIFY
WHAT IS WRONG?
class OrderCalculator
{
public calculate(orders): number {
let sum = 0;
for (let order of orders) {
if (order instanceof SingleOrder) {
sum += this.calculateSingle(order);
}
if (order instanceof MultiOrder) {
sum += this.calculateMulti(order);
}
}
return sum;
}
private calculateSingle(order: SingleOrder): number {}
private calculateMulti(order: MultiOrder): number {}
}
DIFFICULT TO EXTEND
DIFFICULT TO REUSE
NEED TO CHANGE
ORDERCALCULATOR
IF NEW TYPE OF ORDERS APPEAR
BETTER CODE
class OrderCalculator {
public calculate(orders: OrderInterface[]){
let sum = 0;
for (let order of orders) {
sum += order.calculate();
}
return sum;
}
}
interface OrderInterface {
calculate(): number;
}
class SingleOrder implements OrderInterface {
calculate(): number {}
}
class MultiOrder implements OrderInterface {
calculate(): number {}
}
LISKOV SUBSTITUTION
IF YOU USE BASE TYPE
YOU SHOULD BE ABLE TO USE SUBTYPES
AND DO NOT BREAK ANYTHING
WHAT IS WRONG?
class Order {
protected items: Item[] = [];
public addItem(item: Item) {
this.items.push(item);
}
public getItems() {
return this.items;
}
}
class OrderCollector {
public collect(order: Order, items: Item[]) {
for (let item of items) {
order.addItem(item);
}
}
}
ACTUALLY NOTHING IS WRONG WITH IT
WHAT IS WRONG?
class Order {
protected items: Item[] = [];
public addItem(item: Item) {
this.items.push(item);
}
public getItems() {
return this.items;
}
}
class FreeOrder extends Order {
public addItem(item: Item) {
if (item.price() !== 0) {
throw new Error();
}
this.items.push(item);
}
}
class OrderCollector {
public collect(order: Order, items: Item[]) {
for (let item of items) {
order.addItem(item);
}
}
}
FREEORDER BREAKS ORDERCOLLECTOR
FREEORDER IS NOT “REAL” SUBCLASS
INHERITANCE SHOULD BE DEFINED BASED
ON BEHAVIOUR
BETTER CODE
abstract class ItemList {
protected items: Item[] = [];
public getItems() {
return this.items;
}
}
class Order extends ItemList {
public addItem(item: Item) {
this.items.push(item);
}
}
class FreeOrder extends ItemList {
public addItem(item: FreeItem) {
this.items.push(item);
}
}
INTERFACE SEGREGATION
SEVERAL SPECIALISED
INTERFACES ARE BETTER THAN
1 ALL-PURPOSE ONE
WHAT IS WRONG?
interface ItemInterface
{
applyDiscount(discount: number);
applyPromocode(promocode: string);
setColor(color: string);
setSize(size: Size);
setCondition(condition: Condition);
setPrice(price: number);
}
DIFFICULT TO REUSE
INTERFACE IS TOO BIG TO IMPLEMENT
POTENTIAL VIOLATION OF
SINGLE RESPONSIBILITY
AND
LISKOV SUBSTITUTION
PRINCIPLES.
BETTER CODE
interface ItemInterface
{
setCondition(condition: Condition);
setPrice(price: number);
}
interface ClothesInterface
{
setColor(color: string);
setSize(size: Size);
setMaterial(material: string);
}
interface DiscountableInterface
{
applyDiscount(discount: number);
applyPromocode(promocode: string);
}
DEPENDENCY INVERSION
DEPEND ON ABSTRACTION NOT
IMPLEMENTATION
class Customer {
private currentOrder: Order = null;
public buyItems() {
if (!this.currentOrder) {
return false;
}
const processor = new OrderProcessor();
return processor.checkout(this.currentOrder);
}
}
class OrderProcessor {
public function checkout(order: Order){/*...*/}
}
WHAT IS WRONG?
LESS FLEXIBLE
DIFFICULT TO WRITE UNIT TESTS
YOUR TEAMMATES WILL NOT
APPROVE THIS CODE
BETTER CODE
class Customer {
private currentOrder: Order = null;
public buyItems(processor: OrderProcessorInterface) {
if(!this.currentOrder) {
return false;
}
return processor.checkout(this.currentOrder);
}
}
interface OrderProcessorInterface {
checkout(order: Order);
}
class OrderProcessor implements OrderProcessorInterface {
public checkout(order: Order){/*...*/}
}
AFTERWORD
SOLID
is not panacea
“All problems in computer science
can be solved by another level of
indirection except for the problem of
too many layers of indirection”
Focus on code complexity management,
do not overcomplicate your code
THANKS!

SOLID principles with Typescript examples

  • 1.
  • 2.
  • 3.
    WHAT IS SOLID? SINGLERESPONSIBILITY OPEN - CLOSED LISKOV SUBSTITUTION INTERFACE SEGREGATION DEPENDENCY INVERSION
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
    SINGLE RESPONSIBILITY DO ONEAND ONLY ONE THING BUT DO IT WELL
  • 9.
    WHAT IS WRONG? classOrder { public calculateTotalSum() {/* ... */ } public getItems() {/* ... */ } public getItemCount() {/* ... */ } public addItem(item: Item) {/* ... */ } public removeItem(item: Item) {/* ... */ } public printOrder() {/* ... */ } public showOrder() {/* ... */ } public load() {/* ... */ } public save() {/* ... */ } public update() {/* ... */ } public delete() {/* ... */} }
  • 10.
  • 11.
    LOTS OF REASONSTO CHANGE
  • 12.
  • 13.
    BETTER CODE class Order { publiccalculateTotalSum() {/*...*/} public getItems() {/*...*/} public getItemCount() {/*...*/} public addItem(item: Item) {/*...*/} public deleteItem(item: Item) {/*...*/} } class OrderRepository { public load(){} public save(){} public update(){} public delete(){} } class OrderViewer { public printOrder(){} public showOrder(){} }
  • 14.
    OPEN - CLOSED FEELFREE TO EXTEND BUT DO NOT MODIFY
  • 15.
    WHAT IS WRONG? classOrderCalculator { public calculate(orders): number { let sum = 0; for (let order of orders) { if (order instanceof SingleOrder) { sum += this.calculateSingle(order); } if (order instanceof MultiOrder) { sum += this.calculateMulti(order); } } return sum; } private calculateSingle(order: SingleOrder): number {} private calculateMulti(order: MultiOrder): number {} }
  • 16.
  • 17.
  • 18.
    NEED TO CHANGE ORDERCALCULATOR IFNEW TYPE OF ORDERS APPEAR
  • 19.
    BETTER CODE class OrderCalculator{ public calculate(orders: OrderInterface[]){ let sum = 0; for (let order of orders) { sum += order.calculate(); } return sum; } } interface OrderInterface { calculate(): number; } class SingleOrder implements OrderInterface { calculate(): number {} } class MultiOrder implements OrderInterface { calculate(): number {} }
  • 20.
    LISKOV SUBSTITUTION IF YOUUSE BASE TYPE YOU SHOULD BE ABLE TO USE SUBTYPES AND DO NOT BREAK ANYTHING
  • 21.
    WHAT IS WRONG? classOrder { protected items: Item[] = []; public addItem(item: Item) { this.items.push(item); } public getItems() { return this.items; } } class OrderCollector { public collect(order: Order, items: Item[]) { for (let item of items) { order.addItem(item); } } }
  • 22.
    ACTUALLY NOTHING ISWRONG WITH IT
  • 23.
    WHAT IS WRONG? classOrder { protected items: Item[] = []; public addItem(item: Item) { this.items.push(item); } public getItems() { return this.items; } } class FreeOrder extends Order { public addItem(item: Item) { if (item.price() !== 0) { throw new Error(); } this.items.push(item); } } class OrderCollector { public collect(order: Order, items: Item[]) { for (let item of items) { order.addItem(item); } } }
  • 24.
  • 25.
    FREEORDER IS NOT“REAL” SUBCLASS
  • 26.
    INHERITANCE SHOULD BEDEFINED BASED ON BEHAVIOUR
  • 27.
    BETTER CODE abstract classItemList { protected items: Item[] = []; public getItems() { return this.items; } } class Order extends ItemList { public addItem(item: Item) { this.items.push(item); } } class FreeOrder extends ItemList { public addItem(item: FreeItem) { this.items.push(item); } }
  • 28.
  • 29.
    WHAT IS WRONG? interfaceItemInterface { applyDiscount(discount: number); applyPromocode(promocode: string); setColor(color: string); setSize(size: Size); setCondition(condition: Condition); setPrice(price: number); }
  • 30.
  • 31.
    INTERFACE IS TOOBIG TO IMPLEMENT
  • 32.
    POTENTIAL VIOLATION OF SINGLERESPONSIBILITY AND LISKOV SUBSTITUTION PRINCIPLES.
  • 33.
    BETTER CODE interface ItemInterface { setCondition(condition:Condition); setPrice(price: number); } interface ClothesInterface { setColor(color: string); setSize(size: Size); setMaterial(material: string); } interface DiscountableInterface { applyDiscount(discount: number); applyPromocode(promocode: string); }
  • 34.
    DEPENDENCY INVERSION DEPEND ONABSTRACTION NOT IMPLEMENTATION
  • 35.
    class Customer { privatecurrentOrder: Order = null; public buyItems() { if (!this.currentOrder) { return false; } const processor = new OrderProcessor(); return processor.checkout(this.currentOrder); } } class OrderProcessor { public function checkout(order: Order){/*...*/} } WHAT IS WRONG?
  • 36.
  • 37.
  • 38.
    YOUR TEAMMATES WILLNOT APPROVE THIS CODE
  • 39.
    BETTER CODE class Customer{ private currentOrder: Order = null; public buyItems(processor: OrderProcessorInterface) { if(!this.currentOrder) { return false; } return processor.checkout(this.currentOrder); } } interface OrderProcessorInterface { checkout(order: Order); } class OrderProcessor implements OrderProcessorInterface { public checkout(order: Order){/*...*/} }
  • 40.
  • 41.
  • 42.
    “All problems incomputer science can be solved by another level of indirection except for the problem of too many layers of indirection”
  • 43.
    Focus on codecomplexity management, do not overcomplicate your code
  • 44.