面向对象思想

发展过程

【目的】使软件的维护和重用变得更容易。

【编程核心原则】封装(encapsulation)、继承、多态、抽象。

【基本思想】重点关注各个构件,提高构件的独立性,将构件组合起来,实现系统整体的功能。通过提高构件的独立性,当发生修改时,能够使影响范围最小,在其他系统中也可以重用。

OOP 使得大规模软件的可重用构件群的创建成为可能,这些被称为类库或者框架 。另外,创建可重用构件群时使用的固定的设计思想被提炼为设计模式

使用图形来表示利用OOP 结构创建的软件结构的方法称为统一建模语言(Unified Modeling Language)。在此基础上,还出现了将OOP 思想应用于上流工程的建模 ,以及用于顺利推进系统开发的开发流程

面向对象 VS 面向过程

  • 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步步实现,使用的使用一个个依次调用就可以;比如,写了3个对输入框中输入的数据校验功能方法,用了3个函数,这是一种面向过程的实现方式。
  • 面向对象是把构成问题事物分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描述某个事物在整个解决问题的步骤中的行为;面向对象编程就是将需求抽象成一个对象,然后针对这个对象分析其特征(属性)与动作(方法)。这个对象称之为类。
  • 面向对象是以功能来划分问题,而不是步骤
js 复制代码
 // 邮箱格式校验
 function validateEmail(email) {
   const emailPattern = /^[^\s@]+@[^\s@]+.[^\s@]+$/;
   return emailPattern.test(email);
 }
 ​
 // 密码强度校验
 function validatePasswordStrength(password) {
   const minLength = 8;
   return password.length >= minLength;
 }
 ​
 // 手机号码格式校验
 function validatePhoneNumber(phoneNumber) {
   const phonePattern = /^\d{10}$/; // 假设手机号为10位数字
   return phonePattern.test(phoneNumber);
 }
 ​
 const emailInput = "test@example.com";
 const passwordInput = "strongPassword123";
 const phoneNumberInput = "1234567890";
 ​
 console.log(validateEmail(emailInput)); // 输出: true
 console.log(validatePasswordStrength(passwordInput)); // 输出: true
 console.log(validatePhoneNumber(phoneNumberInput)); // 输出: true
javascript 复制代码
 class Validator {
   static validateEmail(email) {
     const emailPattern = /^[^\s@]+@[^\s@]+.[^\s@]+$/;
     return emailPattern.test(email);
   }
 ​
   static validatePasswordStrength(password) {
     const minLength = 8;
     return password.length >= minLength;
   }
 ​
   static validatePhoneNumber(phoneNumber) {
     const phonePattern = /^\d{10}$/;
     return phonePattern.test(phoneNumber);
   }
 }

三大要素

结构化语言(C/Pascal 等)无法解决的两个问题:

  • 全局变量问题
  • 可重用性差问题

OOP 的类、多态和继承三种结构正好可以解决这两个问题。

Java、Ruby、C#、Visual Basic.NET、Objective-C、C++ 和Smalltalk 等语言都属于OOP。

类用于创建独立性高的结构;多态和继承用于消除重复代码,创建通用性强的构件。

在Javascript 中一般将这个代表类的变量名首字母大写。然后在这个函数(类)的内部通过this(函数内部自带的一个变量,用于指向当前这个对象)变量添加属性或者方法来实现对类添加属性或者方法。

也可以通过在类的原型(类也是一个对象,所以也有原型prototype)上添加属性和方法,有两种方式,一种是一一为原型对象属性赋值,另一种则是将一个对象赋值给类的原型对象。但这两种不要混用。

注意在使用第二种方式时,不要完全替换原型对象,以免丢失其他原型上的属性和方法。因为赋值操作会替换原型对象,如果在替换之前原型上已经存在其他属性或方法,那些属性和方法都会被覆盖,从而丢失。因此,在使用这种方式时,要确保将新的属性和方法添加到原有的原型对象上,而不是完全替换它。

js 复制代码
 for (let methodName in animalMethodsToAdd) {
   Animal.prototype[methodName] = animalMethodsToAdd[methodName];
 }

【this】

通过this 添加的属性、方法是在当前对象上添加的,然而JS 是一种基于原型prototype 的语言,所以每创建一个对象时,它都有一个原型prototype 用于指向其继承的属性、方法。这样通过prototype 继承的方法并不是对象自身的,所以在使用这些方法时,需要通过prototype 一级一级查找来得到。

所以每次通过类创建一个新对象时,this 指向的属性和方法都会得到相应的创建,而通过prototype 继承的属性或者方法是每个对象通过prototype 访问到,所以每次通过类创建一个新对象时这些属性和方法不会再次创建。

【constructor】

constructor 是一个属性,当创建一个函数或者对象时都会为其创建一个原型对象prototype,在prototype 对象中又会创建一个constructor 属性,那么constructor 属性指向的就是拥有整个原型对象的函数或对象。

类的功能

  • 汇总方法和变量
  • 隐藏只在类内部使用的变量和方法
    • 可以限定只有类内部的方法才能访问某个变量
  • 从一个类创建很多个实例
    • 实例是类定义的实例变量所持有的内存区域
    • 定义了类就可以在运行时创建多个实例,即能够确保多个内存区域

实例变量是存在期间长的局部变量或者限定访问范围的全局变量。

超类,也称为父类(基类),包含了继承自它的所有类的公共属性和行为。

子类,也称为孩子类(衍生类),是超类的扩展。

多态/多相(Polymorphism)

多态是指相同的操作作用于不同的对象上,可以产生不同的结果。使用多态可以使得代码更加灵活和可扩展。

多态最根本的作用:通过把过程化的条件分支语句转化为对象的多态性,从而消除这些条件分支语句。

在 JavaScript 中,由于语言的动态特性和"鸭子类型"的支持,多态的实现更加自然。一个对象的方法可以被任何对象调用,只要对象具有相同的方法名和参数即可,这就实现了多态性。

js 复制代码
 function speak(animal) {
   if (animal && typeof animal.speak === 'function') {
     animal.speak();
   }
 }
 ​
 let cat = {
   speak: function() {
     console.log('Meow!');
   }
 };
 ​
 let dog = {
   speak: function() {
     console.log('Woof!');
   }
 }
 ​
 let cow = {};
 ​
 speak(cat); // 输出"Meow!"
 speak(dog); // 输出"Woof!"
 speak(cow); // 什么也不输出
 ​
 // speak 函数可以接受任何对象作为参数,只要这个对象具有speak 方法,就可以调用该方法,实现了多态性。

在面向对象编程中,多态使用的最重要的便是重写(方法的名字相同,但实现不同)以及重载(方法的名字相同,但是参数不同)。

重写(overriding) 指的是在子类中实现一个与父类方法签名相同的方法,从而为子类提供特定的实现。在这种情况下,方法的参数列表和返回值类型也应该与父类中的方法保持一致。

java 复制代码
 class Animal {
   void makeSound() {
     System.out.println("Animal makes a sound");
   }
 }
 ​
 class Dog extends Animal {
   @Override
   void makeSound() {
     System.out.println("Dog barks");
   }
 }

JavaScript 没有显式的重写关键字,但通过方法的重新定义,可以达到类似于方法重写的效果。

TypeScript 中的方法重写与其他面向对象语言的实现方式类似,通过在子类中重新定义方法来实现方法重写,保持方法签名和返回值类型的一致性。

ts 复制代码
 class Animal {
   makeSound(): string {
     return 'Animal makes a sound';
   }
 }
 ​
 class Dog extends Animal {
   makeSound(): string {
     return 'Dog barks'; // 重写父类方法,保持方法签名和返回值类型一致
   }
 }
 ​
 const dog = new Dog();
 console.log(dog.makeSound());

重载(Overload) 指在同一个类中,可以定义多个具有相同名称但参数列表不同的方法。这样通过传递不同的参数,可以实现不同的功能。方法重载也是多态性的体现,因为它允许在相同的方法名下,根据不同的参数选择正确的实现。

ts 复制代码
 function greet(name: string): string;
 function greet(age: number): string;
 function greet(value: string | number): string { 
   if (typeof value === 'string') {
     return `Hello, ${value}!`;
   } else if (typeof value === 'number') {
     return `You are ${value} years old.`;
   } else { 
     return 'Hello!';
   }
 }
 console.log(greet('Alice'));
 console.log(greet(25));
 console.log(greet(true));
 class Calculator {
   int add(int a, int b) {
     return a + b;
   }
 ​
   double add(double a, double b) {
     return a + b;
   }
 }

继承 (inheritance)

继承允许子类获取父类的特性,以便复用代码和构建类层次结构。

【实现的继承 - Implementation Inheritance】

一个类从另一个类继承属性和方法的过程。在实现继承中,一个类(子类或派生类)继承了另一个类(父类或基类)的特性,包括它的属性和方法。子类可以重写父类的方法或添加新的方法,从而自定义自己的行为。

ts 复制代码
 class ParentClass {
   constructor(public name: string) {}
   sayHello() {
     console.log(`Hello, I'm ${this.name}.`);
   }
 }
 ​
 class ChildClass extends ParentClass {
   constructor(name: string, public age: number) {
     super(name)
   }
   sayAge() {
     console.log(`I'm ${this.age} years old.`)
   }
 }
 ​
 const child = new ChildClass('lucius', 18);
 child.sayHello();
 child.sayAge();

【接口的继承 - Interface Inheritance】

指的是一个类可以实现一个或多个接口,从而获得这些接口定义的方法签名。接口是一种规范,定义了一组方法,但不提供方法的实际实现。通过实现接口,一个类承诺实现接口中定义的所有方法。多个类可以实现同一个接口,从而在代码中达到一致性。

ts 复制代码
 interface Flyable {
   fly(): viod;
 }
 interface Swimable {
   swim(): void;
 }
 ​
 class Bird implements Flyable, Swimable {
   fly() {
     console.log("Flying...")
   }
   swim() {
     console.log("Swimming...")
   }
 }
 const bird = new Bird();
 bird.fly();
 bird.swim();

【多重继承】

C++支持多重继承,即一个类可以从多个基类(父类)中继承属性和方法。

多重继承可能引发的问题:命名冲突和Diamond 继承问题。

c++ 复制代码
 class Derived : public Base1, public Base2 {
   // Derived class definition
 }

Java、Object-C 和.NET 不支持多重继承。尽管Java、Object-C 和.NET 类只能继承自一个父类,但它们可以实现多个接口。通过实现多个接口,一个类可以获得多个不同的方法签名,从而达到类似多重继承的效果。

这里的接口(可看作是一种特殊类型的抽象类)不同于抽象类,它们只定义了方法签名,而不提供实现。实现接口的类必须提供方法的具体实现。

java 复制代码
 interface Flyable {
   void fly();
 }
 interface Swimmable {
   void swim();
 }
 ​
 class Animal implements Flyable, Swimmable {
   @Override
   public void fly() {
     System.out.println('This animal can fly.');
   }
 ​
   @Override
   public void swim() {
     System.out.println('This animal can swim.');
   }
 }
 ​
 // Animal 类实现了 Flyable 和 Swimmable 接口,因此它可以调用 fly() 和 swim() 方法。
 // 这种方式实际上模拟了多重继承,让一个类从多个"功能模块"中继承特性。

【单一继承】

JavaScript 支持单一继承,但可使用原型链和混入来实现类似多重继承的效果。

js 复制代码
 var flyMixin = {
   fly: function() {
     console.log('Flying...');
   }
 }
 ​
 var swimMixin = {
   swim: function() {
     console.log('Swimming...');
   }
 }
 ​
 function Animal() {};
 Object.assign(Animal.prototype, flyMixin, swimMixin);
 var animal = new Animal();
 animal.fly();
 animal.swim();

继承是指子类继承父类的属性和方法,并可以在此基础上添加新的属性和方法。继承使得代码的复用和扩展变得更加容易。

js 复制代码
 function Person(name) {
   this.name = name;
 }
 ​
 Person.prototype.sayHello = function() {
   console.log('Hello, my name is' + this.name);
 };
 ​
 function Student(name, grade) {
   Person.call(this, name);
   this.grade = grade;
 }
 Student.prototype = Object.create(Person.prototype);
 Student.prototype.constructor = Student;
 ​
 Student.prototype.sayGrade = function() {
   console.log('My grade is' + this.grade);
 };
 ​
 // Student 构造函数调用了 Person 构造函数,并使用 Object.create() 方法将 Person.prototype 设置为自己的原型,实现对 Person 属性和方法的继承。
 // 然后,Student.prototype 添加了自己的新属性和方法 sayGrade。

可以使用 Object.create(null) 方法来创建一个没有原型的对象。

Object.create(null) 和 {} 创建空对象的区别

  • 使用create 创建的对象,没有任何属性,显示No properties,可以把它当作一个非常纯净的map 来使用,可以自己定义hasOwnProperty、toString 方法。
  • 在使用for...in 循环的时候会遍历对象原型链上的属性,使用create(null) 就不必再对属性进行检查

重用

软件的重用

在使用OOP 开发应用程序的情况下,并不是每次都从零做起,通常都是使用已经存在的可重用构件群,如源代码或运行形式的模块。这些可重用构件群称为类库、框架或组件等。

思想或技术窍门的重用

对各种技术窍门和手法进行命名,实现模式化。其中最广为人知的就是设计模式。

从历史上来说,首先利用OOP 创建可重用构件群,然后,提取可重用构件群中共同的设计思想,形成设计模式,最后,为了创建可重用构件群,会利用设计模式。

重用类

重用类只有两种方式,继承和组合。

组合(Composition) 是指通过将不同的类或对象组合在一起来创建新的类。这种方式强调了对象之间的合作关系,一个类将其他类的对象作为其属性,并通过这些对象来实现其功能。

组合使得一个类能够重用其他类的功能,同时不必继承它们的行为。这种方式更加灵活,因为它不要求在一个单一的类层次结构中实现所有的功能。

java 复制代码
 // Library 类通过组合方式包含了多个 Book 对象,而不是通过继承
 class Book {
   private String title;
 ​
   public Book(String title) {
     this.title = title;
   }
 ​
   public void read() {
     System.out.println("Reading " + title);
   }
 }
 ​
 class Library {
   private List<Book> books;
 ​
   public Library() {
     books = new ArrayList<>();
   }
 ​
   public void addBook(Book book) {
     books.add(book);
   }
 ​
   public void listBooks() {
     System.out.println("Books in the library:");
     for (Book book : books) {
       System.out.println(book.getTitle());
     }
   }
 }
ts 复制代码
 // TypeScript 没有提供特定的关键字或语法来表示组合
 // 可以通过将一个类的实例作为属性添加到另一个类中来实现组合
 class Book {
   private title: string;
 ​
   constructor(title: string) {
     this.title = title;
   }
 ​
   read() {
     console.log(`Reading ${this.title}`);
   }
 }
 ​
 class Library {
   private books: Book[];
 ​
   constructor() {
     this.books = [];
   }
 ​
   addBook(book: Book) {
     this.books.push(book);
   }
 ​
   listBooks() {
     console.log("Books in the library:");
     for (const book of this.books) {
       console.log(book.getTitle());
     }
   }
 }
 ​
 const book1 = new Book("Book 1");
 const book2 = new Book("Book 2");
 ​
 const library = new Library();
 library.addBook(book1);
 library.addBook(book2);
 ​
 library.listBooks();

组件

组件的一般定义如下:

  • 粒度比OOP 的类大
  • 提供的形式是二进制形式,而不是源代码形式
  • 提供时包含组件的定义信息
  • 功能的独立性高,即使不了解内部的详细内容,也可以使用

【参考资料】

《面向对象是怎样工作的》

《面向对象的思考过程》

相关推荐
鑫~阳36 分钟前
html + css 淘宝网实战
前端·css·html
Catherinemin41 分钟前
CSS|14 z-index
前端·css
2401_882727572 小时前
低代码配置式组态软件-BY组态
前端·后端·物联网·低代码·前端框架
NoneCoder2 小时前
CSS系列(36)-- Containment详解
前端·css
anyup_前端梦工厂3 小时前
初始 ShellJS:一个 Node.js 命令行工具集合
前端·javascript·node.js
5hand3 小时前
Element-ui的使用教程 基于HBuilder X
前端·javascript·vue.js·elementui
GDAL3 小时前
vue3入门教程:ref能否完全替代reactive?
前端·javascript·vue.js
六卿3 小时前
react防止页面崩溃
前端·react.js·前端框架
z千鑫3 小时前
【前端】详解前端三大主流框架:React、Vue与Angular的比较与选择
前端·vue.js·react.js
m0_748256144 小时前
前端 MYTED单篇TED词汇学习功能优化
前端·学习