【Web前端】OOP编程范式

时间:2024-11-07 12:08:50

面向对象编程(Object-Oriented Programming,简称 OOP)是一种程序设计思想,它通过将程序视为一组相互作用的对象来设计程序。OOP 提出了一些重要的基本概念,包括类与实例、继承和封装。面向对象编程将系统视为由多个对象组成的集合,每个对象代表系统的某个特定方面。对象包含方法和数据。一个对象可以向代码的其他部分提供公共接口,使得其他部分能够通过这个接口执行对象的特定操作,而不需要关心对象内部是如何实现这些操作的,从而维护了对象内部状态的私密性。

【Web前端】OOP编程范式_封装


一、类与实例

1.1 类(Class)

类是一个蓝图或模板,用于创建对象(实例)。它定义了对象的属性和方法,但并不是对象本身。类可以看作是对某一类事物的抽象描述。例如,可以创建一个表示“汽车”的类,这个类可以包含品牌、颜色、和速度等属性,以及加速和刹车等方法。

伪代码例子

class Car {
    // 属性
    string brand;
    string color;
    int speed;

    // 构造函数
    Car(string b, string c) {
        brand = b;
        color = c;
        speed = 0;
    }

    // 方法
    void accelerate(int increase) {
        speed += increase;
        print("Current speed: " + speed);
    }

    void brake(int decrease) {
        speed -= decrease;
        if (speed < 0) {
            speed = 0;
        }
        print("Current speed: " + speed);
    }
}

我们定义了一个名为 Car 的类,包含了三个属性 brandcolor 和 speed,以及两个方法 accelerate 和 brake。构造函数用于初始化对象的属性。


1.2 实例(Instance)

实例是根据类创建的具体对象。每个实例都有自己的属性值,尽管它们都是根据同一个类创建的。继续之前的例子,可以使用 Car 类创建多个汽车对象。

伪代码例子

// 创建实例
Car myCar = new Car("Toyota", "Red");
Car anotherCar = new Car("Honda", "Blue");

// 调用方法
myCar.accelerate(50);
myCar.brake(20);
anotherCar.accelerate(30);

这里创建了两个实例 myCar 和 anotherCar,并分别调用它们的方法。每个实例都有自己的 speed 属性,彼此之间不会相互影响。


二、继承

2.1 继承(Inheritance)

继承是面向对象编程的一个重要特性,它允许一个类(子类)从另一个类(父类)继承属性和方法。通过继承,我们可以创建一个更具体的类,而不需要重新定义所有的特性,从而实现代码的重用。

在继承中,子类可以拥有父类的所有属性和方法,同时也可以添加自己的特性。

伪代码例子

class Vehicle {
    string brand;
    string color;

    Vehicle(string b, string c) {
        brand = b;
        color = c;
    }

    void displayInfo() {
        print("Brand: " + brand);
        print("Color: " + color);
    }
}

class Car extends Vehicle {
    int speed;

    Car(string b, string c) : Vehicle(b, c) {
        speed = 0;
    }

    void accelerate(int increase) {
        speed += increase;
        print("Current speed: " + speed);
    }
}

定义了一个父类 Vehicle,它有 brand 和 color 两个属性以及一个方法 displayInfo。接着,我们定义了一个子类 Car,它继承了 Vehicle 类,同时添加了一个新的属性 speed 和一个方法 accelerate


2.2 使用继承

通过继承,我们可以轻松地创建一个新的对象,而不必重复编写代码。

伪代码例子

// 创建 Car 实例
Car myCar = new Car("Toyota", "Red");

// 调用父类和子类的方法
myCar.displayInfo();  // 调用父类方法
myCar.accelerate(50); // 调用子类方法

这段代码我们创建了一个 Car 的实例 myCar,并利用从 Vehicle 类继承过来的方法 displayInfo 来显示信息。


三、封装

3.1 封装(Encapsulation)

当其他代码部分需要对对象执行某些操作时,可以通过对象提供的接口来完成这些操作。这种方式确保了对象的内部状态不被外部代码随意更改。换句话说,对象的内部状态是私有的,外部代码只能通过对象提供的接口来访问和修改这些内部状态,而不能直接对其进行访问或更改。维护对象内部状态的私密性,并明确区分对象的公共接口与内部状态,这种特性被称为封装(encapsulation)。封装的目的是保护对象的内部状态,防止外部不当操作导致的错误。通常使用访问修饰符(如 public、private、protected)来控制对类成员的访问。

封装的优势在于,当程序员需要对某个对象的特定操作进行修改时,只需更改该对象相关方法的内部实现,而无需在整个代码中寻找并逐一更新该方法的所有实现。从某种角度来看,封装在对象内部和外部形成了一种独特的“防护屏障”。

伪代码例子

class BankAccount {
    private double balance; // 私有属性

    BankAccount(double initialBalance) {
        balance = initialBalance;
    }

    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
            print("Deposited: " + amount);
        }
    }

    public void withdraw(double amount) {
        if (amount > 0 && amount <= balance) {
            balance -= amount;
            print("Withdrawn: " + amount);
        } else {
            print("Insufficient funds");
        }
    }

    public double getBalance() {
        return balance; // 提供外部访问余额的方法
    }
}

BankAccount 类有一个私有属性 balance,通过 public 方法 depositwithdraw 和 getBalance 来操作和访问 balance。这种方式确保了类的内部状态不会被随意修改,有利于保持数据的一致性和安全性。


3.2 使用封装

通过封装,我们可以确保对象的状态始终有效。

伪代码例子

// 创建 BankAccount 实例
BankAccount myAccount = new BankAccount(1000);

// 操作账户
myAccount.deposit(500);
myAccount.withdraw(200);
print("Current balance: " + myAccount.getBalance());

创建了一个 BankAccount 的实例,并通过 depositwithdraw 和 getBalance 方法安全地操作账户余额。


四、面向对象编程与 JavaScript

4.1 JavaScript 中的面向对象编程

虽然 JavaScript 是一门基于原型的语言,但它同样支持面向对象编程的概念。从 ECMAScript 6 开始,JavaScript 引入了 class 关键字,允许开发者以更接近传统面向对象的方式来定义和使用类。

伪代码例子(JavaScript)

class Car {
    constructor(brand, color) {
        this.brand = brand;
        this.color = color;
        this.speed = 0;
    }

    accelerate(increase) {
        this.speed += increase;
        console.log(`Current speed: ${this.speed}`);
    }

    brake(decrease) {
        this.speed -= decrease;
        if (this.speed < 0) {
            this.speed = 0;
        }
        console.log(`Current speed: ${this.speed}`);
    }
}

// 创建实例
const myCar = new Car("Toyota", "Red");
myCar.accelerate(50);
myCar.brake(20);

4.2 继承和封装在 JavaScript 中

JavaScript 同样支持继承和封装。你可以使用 extends 关键字创建子类,并使用方法来封装属性的访问。

伪代码例子(JavaScript)

class Vehicle {
    constructor(brand, color) {
        this.brand = brand;
        this.color = color;
    }

    displayInfo() {
        console.log(`Brand: ${this.brand}`);
        console.log(`Color: ${this.color}`);
    }
}

class Car extends Vehicle {
    constructor(brand, color) {
        super(brand, color);
        this.speed = 0;
    }

    accelerate(increase) {
        this.speed += increase;
        console.log(`Current speed: ${this.speed}`);
    }
}

// 使用
const myCar = new Car("Honda", "Blue");
myCar.displayInfo();
myCar.accelerate(30);

4.3 封装示例

通过 JavaScript 的闭包特性,我们可以在构造函数中创建私有属性,确保这些属性不被外部直接访问。

伪代码例子(JavaScript)

function BankAccount(initialBalance) {
    let balance = initialBalance; // 私有属性

    this.deposit = function(amount) {
        if (amount > 0) {
            balance += amount;
            console.log(`Deposited: ${amount}`);
        }
    };

    this.withdraw = function(amount) {
        if (amount > 0 && amount <= balance) {
            balance -= amount;
            console.log(`Withdrawn: ${amount}`);
        } else {
            console.log("Insufficient funds");
        }
    };

    this.getBalance = function() {
        return balance; // 提供外部访问余额的方法
    };
}

// 使用
const myAccount = new BankAccount(1000);
myAccount.deposit(500);
myAccount.withdraw(200);
console.log(`Current balance: ${myAccount.getBalance()}`);

balance 是一个私有变量,仅在 BankAccount 的方法中可访问,确保了封装性。


基于类的面向对象编程中,类和对象是两个独立的概念。一个对象通常是一个类的实例,由该类创建。定义类的语法与实例化对象的方式(如构造函数)是不同的。而在 JavaScript 中,我们可以通过函数或对象字面量来创建对象,这意味着在 JavaScript 中不需要特定的类定义即可生成对象。这种灵活的方式相较于基于类的面向对象编程更为轻便,能够让我们更加便捷地使用对象。

虽然原型链在某种程度上看起来与继承的层级结构相似,并且在某些方面表现出类似的行为,但它们之间依然存在着显著的区别。在继承的模式中,当一个子类继承自一个父类时,子类创建的对象将同时拥有子类定义的属性及其父类(及父类的父类,依此类推)定义的属性。而在原型链中,每个层级都代表一个不同的对象,这些对象是通过 proto 属性相互链接的。可以说,原型链的行为更类似于委派(delegation)而非直接继承。委派是一种对象间的编程模式,当我们希望对象执行某个任务时,委派模式允许对象要么自己执行该任务,要么请求另一个对象(被委派的对象)以其方式来完成这个任务。这种模式相较于继承,能够在多个对象之间建立更灵活的联系,并且委派关系可以在程序运行时进行更改或完全替换。

仍然可以通过构造函数和原型实现基于类的面向对象编程特性。直接使用构造函数和原型来实现这些特性(例如继承)可能比较复杂。JavaScript 提供了一些额外的功能,这些功能在原型模型之上再抽象出一层,将基于类的面向对象编程的概念映射到原型中,从而能够更加直接地使用这些基于类的编程概念。