[第一部分] SOLID Typescript 简介

SOLID 原则是 OOP 中的一组设计原则,有助于创建稳健、可维护且可扩展的代码。在 Typescript 中实现它们非常简单,因为它支持类、接口和强类型。

在这篇文章中,我简要概述了 SOLID,以帮助您更轻松地理解它,并且我将在有时间时深入研究每个部分 _ =)))。

下面通过 Typescript 示例详细解释 SOLID 原则:

S——单一责任原则(SRP)

**一个类应该只有一个改变的原因**

这意味着每个类应该只做一件事。 如果需要做多件事,它应该将一些职责委托给其他类。

例如:

❌糟糕

class UserService {
    createUser(user: string){
        console.log(`User ${user} created`);
    } // UserService

    sendWelcomeEmail(user: string){
        console.log(`Welcome email sent to ${user}`)
    } //EmailService
}

✅好

class UserService{
    createUser(user: string): string
    {
        console.log(`User ${user} created`);
        return user;
    }
}

class EmailService{
    sendMail(user: string){
        console.log(`Send mail to ${user}`);
    }
}

那么我应该如何在我的代码中正确有效地应用 SRP 呢?

=> **根据业务逻辑对职责进行分组。**

  • SendMail 涉及电子邮件和通信 -> 属于 EmailService。
  • CreateUser 涉及用户创建(或 CRUD 用户)-> 属于 UserServices。
  • O——开放/封闭原则(OCP)

    **软件实体应该对扩展开放,对修改关闭。**

    这意味着您无需更改现有代码即可添加新功能。

    ❌糟糕

    class DiscountService{
        calculateDiscount(typeOfDiscount:string, amount: number) : number {
            if (typeOfDiscount == 'standard') {
                return amount * 0.1;
            }
            else if (typeOfDiscount == 'premium') {
                return amount * 0.5;
            }
            return 0;
        }
    }
    let standard = new DiscountService();
    standard.calculateDiscount('standard', 10);

    为什么它不好?

    如果您收到添加 DiamondDiscount 的请求,要求其提供 100% 折扣以及“isMemberShip”等附加参数,则需要修改“calculateDiscount()”方法,如下所示:

    class DiscountService{
        calculateDiscount(typeOfDiscount:string, amount: number, isMemberShip: boolean) : number {
            if (typeOfDiscount == 'standard') {
                return amount * 0.1;
            }
            else if (typeOfDiscount == 'premium') {
                return amount * 0.5;
            }
            else if (typeOfDiscount == 'diamond') {
                if (isMemberShip){
                    return amount;
                }
            }
            return 0;
        }
    }
    
    let standard = new DiscountService();
    standard.calculateDiscount('standard', 10); // => You must change this one if update calculateDiscount

    这可能会影响已经在使用“calculateDiscount()”的其他方法(您需要搜索任何使用“calculateDiscount”的代码并将其更新为包含“isMemberShip”。

    ✅好

    interface Discount {
        calculateDiscount(amount: number) : number;
    }
    
    class StandardDiscount implements Discount {
        calculateDiscount(amount: number): number {
            return amount * 0.1;
        }
    }
    
    class DiamondDiscount implements Discount {
        private isMemberShip: boolean = false;
        constructor(isMemberShip: boolean) {
        }
        calculateDiscount(amount: number): number {
            if (this.isMemberShip){
                return amount;
            }
            return amount * 0.5;
        }
    }
    
    class DisCountService {
        constructor(private readonly discountStrategy: Discount) {
        }
        calculateDiscount(amount: number) {
            return this.discountStrategy.calculateDiscount(amount);
        }
    }
    
    const discount = new DisCountService( new StandardDiscount()); //Do not affect this code

    所有问题均已通过 OCP 解决。

    L——里氏替换原则(LSP)

    **派生类必须可以替代它们的基类。**

    这意味着你应该能够用子类替换父类而不会破坏功能

    ❌糟糕

    class Rectangle {
        constructor(protected width: number, protected height: number) {}
    
        setWidth(width: number) {
            this.width = width;
        }
    
        setHeight(height: number) {
            this.height = height;
        }
    
        getArea(): number {
            return this.width * this.height;
        }
    }
    
    class Square extends Rectangle {
        setWidth(width: number) {
            this.width = width;
            this.height = width; // A square's height is always equal to its width
        }
    
        setHeight(height: number) {
            this.height = height;
            this.width = height; // A square's width is always equal to its height
        }
    }
    
    // Usage
    const shape: Rectangle = new Square(5, 5);
    shape.setWidth(10);
    console.log(shape.getArea()); // Expected: 50, Actual: 100

    **为什么它不好?**

  • 正方形违反了长方形的预期行为,改变正方形的宽度或高度会改变两个维度,这就是长方形的工作方式。
  • 用正方形代替长方形会导致不正确的行为:
  • const shape: Rectangle = new Square(5, 5); //Substituting a `Square` for a `Rectangle` lead to incorrect behavior
    shape.setWidth(10);

    ✅好:

    abstract class Shape {
        abstract getArea(): number;
    }
    
    class Rectangle extends Shape {
        constructor(protected width: number, protected height: number) {
            super();
        }
    
        getArea(): number {
            return this.width * this.height;
        }
    }
    
    class Square extends Shape {
        constructor(protected side: number) {
            super();
        }
    
        getArea(): number {
            return this.side * this.side;
        }
    }
    
    // Usage
    const shape: Shape = new Rectangle(10, 5);
    console.log(shape.getArea());

    **为什么它更好?**

  • Shape定义了通用的getArea()方法,Rectangle和Square都提供了具体的实现。
  • Square 类不再从 Rectangle 继承。这确保 Square 不会继承对正方形无意义的 setWidth() 或 setHeight() 方法。
  • 那么我应该如何在我的代码中正确有效地应用 LSP 呢?

    => **当子类与父类的行为不完全一致时,避免强制继承。**

    [...继续下一课:)]