TypeScript 面向对象编程 – 封装

大家好! 这里我们仍然在谈论面向对象编程,今天我想介绍一下封装,通过这个 OOP 关键概念,我们将在构建类时掌握一些关键概念。


什么是封装

封装是将数据绑定为类属性,将方法绑定为行为函数的能力,换句话说,封装是通过方法将数据构建和操作到一个单元中的行为。

那好吧!我们又来了!这似乎有点难以理解,但它比你想象的要容易。

这是来自 W3Schools 的一个很好的定义:

Encapsulation 的含义是确保对用户隐藏“敏感”数据。为此,你必须将类变量/属性声明为私有(不能从类外部访问)。如果你希望其他人读取或修改私有成员的值,则可以提供公共的 get 和 set 方法。

所以考虑到这个观点,我们可以找出一些代码并举例说明,我认为我们仍然可以使用我们上次介绍的苏打水机,使用 TypeScript 进行面向对象编程 – 抽象。

我们将发布一项新功能,使我们的机器能够销售啤酒,询问客户姓名和年龄,如果客户未满 18 岁并试图购买啤酒,则必须拒绝交易,否则应继续购买。

考虑到所呈现的特性,让我们开始添加新的属性,也就是类属性,在这种情况下,clientName 为字符串,clientAge 为数字,isAlcoholic 为布尔值。`

/**
 * Friendly enum for Soda Flavours
 */
enum ESodaFlavours {
    COCA,
    LEMON,
    ORANGE
}

/**
 * The soda fountain machine class
 */
class SodaMachine {

    public clientName: string
    private clientAge: number
    private isAlcoholic: boolean

    /**
     * 
     * @param {string} clientName - The Client name
     * @param {number} clientAge - The Client age
     * @param {boolean} isAlcoholic - 检查要求的饮料是否含酒精
     */
    constructor(clientName: string, clientAge: number, isAlcoholic?: boolean) {
        this.clientName = clientName
        this.clientAge = clientAge
        this.isAlcoholic = isAlcoholic || false
    }

    /**
     * Returns the soda when pushed
     */
    dropSoda(flavour: ESodaFlavours) {
        console.log(`get your soda flavour ${ESodaFlavours[flavour]}`)
        console.log(this.isAlcoholic)
    }

    /**
     * Drops ice cubes when pushed
     */
    dropIce() {
        console.log(`get your ice cubes`)
    }

}

const sodaGabriel = new SodaMachine('Gabriel', 50, true)
const sodaJohn = new SodaMachine('John Doe', 16)


sodaGabriel.dropSoda(0)
sodaGabriel.dropIce()
console.log(sodaGabriel.clientName)

sodaJohn.dropSoda(1)

L#15、L#16 和 L#17 — 声明类属性 L#25 – 设置类构造函数,当你实例化你的类时,它作为一个总是执行的方法 L#48, L#49 — 实例化 sodaMachine 类,这一次初始属性为 clientName clientAge 和 isAlcoholic L#54 — 打印客户端名称

将 clientName 值保持为公开允许访问此敏感数据,并且鉴于“确保敏感数据对用户隐藏”,保持公开不是一个好主意,所以让我们将 L#15 从 public clientName: string 更改为 private clientName: string

Getter 和 Setter

这在封装类时非常重要,通过它们你可以为你的类属性写入和读取值,所以 getter 是只读的,setter 是只写的,两者都是提供访问的公共方法 到你的类属性,所以让我们实现它们:

/**
 * Friendly enum for Soda Flavours
 */
enum ESodaFlavours {
    COCA,
    LEMON,
    ORANGE
}

/**
 * The soda fountain machine class
 */
class SodaMachine {

    private clientName: string
    private clientAge: number
    private isAlcoholic: boolean

    /**
     * 
     * @param {string} clientName - The Client name
     * @param {number} clientAge - The Client age
     * @param {boolean} isAlcoholic - Check if the requested drink is alcoholic
     */
    constructor(clientName: string, clientAge: number, isAlcoholic?: boolean) {
        this.clientName = clientName
        this.clientAge = clientAge
        this.isAlcoholic = isAlcoholic || false
    }

    /**
     * Sets the client name
     * @param {string} clientName - The Client name
     */
    public setClientName(clientName: string) {
        this.clientName = clientName;
    }

    /**
    * Sets the client age
    * @param {string} clientAge - The Client name
    */
    public setClientAge(clientAge: number) {
        this.clientAge = clientAge;
    }

    /**
     * Gets the client name
     * @returns the current clientName
     */
    public getClientName(): string {
        return this.clientName
    }

    /**
     * Gets the client name
     * @returns the current clientName
     */
    public getClientAge(): number {
        return this.clientAge
    }

    /**
     * Returns the soda when pushed
     * @param flavour - The soda flavour as {@link ESodaFlavours} enum item
     */
    dropSoda(flavour: ESodaFlavours) {
        console.log(`get your soda flavour ${ESodaFlavours[flavour]}`)
        console.log(this.isAlcoholic)
    }

    /**
     * Drops ice cubes when pushed
     */
    dropIce() {
        console.log(`get your ice cubes`)
    }

}

const sodaGabriel = new SodaMachine('Gabriel', 50, true)
const sodaJohn = new SodaMachine('John Doe', 16)


sodaGabriel.dropSoda(0)
sodaGabriel.dropIce()
console.log(sodaGabriel.getClientName())

sodaJohn.dropSoda(1)
console.log(sodaJohn.getClientName())

L#15 — 将类属性 clientName 从 public 移动到 private 限制外部访问 L#35 和 L#43 — clientName 和 clientAge 的设置器 L#51 和 L#59 — clientName 和 clientAge 的 Getter L#87 和 L#90 — 使用 get 方法打印客户端名称

请记住:通常 getter 方法以“get”开头,setter 以 set 开头,后跟变量名,在这两种方法中,变量名称的第一个字母应大写。

使用 setter,我们可以轻松地更改对象属性值,在这种情况下,我想更改客户端名称,我从 John 开始,但应该是 Mark,那么我应该怎么做才能修复它? 也许创建一个新的purchase,如:

/** Marks's purchase*/
const sodaMark = new SodaMachine('sodaMark', 26)

sodaMark.dropSoda(0)
sodaMark.dropIce()
console.log(sodaMark.getClientName())

或者更好的是,我们可以使用我们的 setter 方法,例如:

const sodaGabriel = new SodaMachine('Gabriel', 50, true)
const sodaJohn = new SodaMachine('John Doe', 16)

/** Gabriel's purchase*/
sodaGabriel.dropSoda(0)
sodaGabriel.dropIce()
console.log(sodaGabriel.getClientName())


/** John's purchase*/
sodaJohn.dropSoda(1)
console.log(sodaJohn.getClientName())

sodaJohn.setClientName("Mark")
console.log(sodaJohn.getClientName())

所以现在我们确切地知道如何使用 getter 和 setter,我们终于可以实现我们的新功能了:

/**
 * Friendly enum for Soda Flavours
 */
enum ESodaFlavours {
    COCA,
    LEMON,
    ORANGE
}

/**
 * The soda fountain machine class
 */
class SodaMachine {

    private clientName: string
    private clientAge: number
    private isAlcoholic: boolean

    /**
     * 
     * @param {string} clientName - The Client name
     * @param {number} clientAge - The Client age
     * @param {boolean} isAlcoholic - Check if the requested drink is alcoholic
     */
    constructor(clientName: string, clientAge: number, isAlcoholic?: boolean) {
        this.clientName = clientName
        this.clientAge = clientAge
        this.isAlcoholic = isAlcoholic || false
    }

    /**
     * Sets the client name
     * @param {string} clientName - The Client name
     */
    public setClientName(clientName: string) {
        this.clientName = clientName;
    }

    /**
    * Sets the client age
    * @param {string} clientAge - The Client name
    */
    public setClientAge(clientAge: number) {
        this.clientAge = clientAge;
    }

    /**
     * Gets the client name
     * @returns the current clientName
     */
    public getClientName(): string {
        return this.clientName
    }

    /**
     * Gets the client name
     * @returns the current clientName
     */
    public getClientAge(): number {
        return this.clientAge
    }

    /**
     * Returns the soda when pushed
     * @param flavour - The soda flavour as {@link ESodaFlavours} enum item
     */
    dropSoda(flavour: ESodaFlavours): void {
        if (this.clientAge < 18 && this.isAlcoholic) {
            console.log(`Sorry ${this.clientName}, you're ${this.clientAge}, so you're not able to get this drink`)
        } else if (this.clientAge >= 18 && this.isAlcoholic) {
            console.log(`Hey ${this.clientName}, here's your beer`)
        } else {
            console.log(`Hey ${this.clientName}, get your soda flavour ${ESodaFlavours[flavour]}`)
        }
    }

    /**
     * Drops ice cubes when pushed
     */
    dropIce(): void {
        console.log(`get your ice cubes`)
    }

}

const beerGabriel = new SodaMachine('Gabriel', 50, true)
const sodaJohn = new SodaMachine('John Doe', 16)
const beerTony = new SodaMachine('Tony', 14, true)

/** Gabriel's purchase*/
beerGabriel.dropSoda(0)
beerGabriel.dropIce()
console.log(beerGabriel.getClientName())


/** John's - Mark purchase*/
sodaJohn.dropSoda(1)
console.log(sodaJohn.getClientName())

sodaJohn.setClientName("Mark")
console.log(sodaJohn.getClientName())

/** Tony's purchase */
beerTony.dropSoda(0)

太好了! 我们做到了! 考虑到“敏感数据对用户是隐藏的”,我们为 SodaMachine 增加了新的功能,很好地利用了封装。

免责声明:
1.本站所有内容由本站原创、网络转载、消息撰写、网友投稿等几部分组成。
2.本站原创文字内容若未经特别声明,则遵循协议CC3.0共享协议,转载请务必注明原文链接。
3.本站部分来源于网络转载的文章信息是出于传递更多信息之目的,不意味着赞同其观点。
4.本站所有源码与软件均为原作者提供,仅供学习和研究使用。
5.如您对本网站的相关版权有任何异议,或者认为侵犯了您的合法权益,请及时通知我们处理。
火焰兔 » TypeScript 面向对象编程 – 封装