面向对象四大特征
一、抽象
概念:
- 抽象主要是隐藏方法的实现,让调用者只关心有哪些功能而不是关心功能的实现。
- 抽象可以提高代码的可扩展性和维护性,修改实现不需要改变定义,可以减少代码的改动范围。
代码展示:
// 接口
interface IStorage {
save(key: string, value: any): void;
read(key: string): any;
}
// userinfo类
class UserInfo {
constructor(public name: string, public storage: IStorage) {}
save() {
this.storage.save('userinfo', JSON.stringify(this));
}
read() {
return this.storage.read('userinfo');
}
}
// LocalStorage类实现IStroage接口
class LocalStorage implements IStorage {
save(key: string, value: any): void {
localStorage.setItem(key, value);
}
read(key: string) {
return localStorage.getItem(key);
}
}
let local = new LocalStorage();
let userinfo = new UserInfo('zhufeng', local);
console.log(userinfo.name);
代码解释:
现在代码里实现的是把传到UserInfo
的信息存入到本地存储里。
- 在代码里,我们有一个
IStorage
接口,这个接口又两个方法,save() 存,read() 取。 - 然后我们定义了一个
LocalStorage
类,让它去实现了IStroage
接口。 - 我们调用的时候只需要
new 一个 LocalStorage 的实例
,然后连带着信息传给Userinfo即可。
重点来了:
看了我上面写的概念、代码、解释,你可能还是晕乎乎的。你的问题可能还是:什么是抽象。
别急,我们对着概念一步一步来看:
-
抽象主要是隐藏方法的实现,让调用者只关心有哪些功能而不是关心功能的实现。
- 在上面的代码里,很明显,调用者是
UserInfo
类,UserInfo类 调用 storage 存储功能时,它需要知道 LocalStorage 怎么实现的吗?它不需要知道,他只需要知道通过 storage打点可以拿到save或read进行存储或读取 就行了。
- 在上面的代码里,很明显,调用者是
-
抽象可以提高代码的可扩展性和维护性,修改实现不需要改变定义,可以减少代码的改动范围。
- 这里面的重点句子是:扩展或修改的时候我们只需要改动实现,不需要改变定义。
- 上面代码中的实现很明显是
LocalStorage
如果接下来我们有一个需求,我们进行一个MySQL的存储【模拟】,那我们怎么扩展我们的代码呢?
// MySqlStorage类实现IStroage接口 class MySqlStorage implements IStorage { save(key: string, value: any): void { // mysql.setItem(key,value); } read(key: string) { // return mysql.getItem(key); } } let mysqlStorage = new MySqlStorage(); let mysql = new UserInfo('mysql',mysqlStorage);
我们只需要定义一个
MySqlStorage
类实现即可,不用去改动UserInfo
类的定义。
调用者和功能实现之间耦合度降低,调用者不需要知道怎么功能实现,改了功能也不用去通知调用者或者修改调用者,这就是抽象。
二、封装
概念:
- 把数据封装起来 , 减少耦合,不该外部访问的代码不要让外部访问。
- 封装利于数据的接口权限管理 。
- 封装仅暴露有限的必要接口,可以提高类的易用性。
实现:
- public:共有修饰符,可以在类内或者类外使用public修饰的属性或者行为,默认修饰符。
- protected:受保护的修饰符,可以在本类和子类中使用protected修饰的属性和行为。
- private:私有修饰符,只可以在类内部使用private修饰的属性和行为。
代码展示:
class Animal {
// 公有:此属性可以在本类,子类和其他类中访问
public name: string;
// 受保护的:此属性可以在本类和子类中访问,其他类不行
protected age: number;
// 私有:此属性只能在本类中访问,子类和其他类都不行。
private weight: number;
constructor(name: string, age: number, weight: number) {
this.name = name;
this.age = age;
this.weight = weight;
}
}
class Person extends Animal {
// 账户余额 私有的,只有自己可以访问
private balance: number;
constructor(name: string, age: number, weight: number, balance: number) {
super(name, age, weight);
this.balance = balance;
}
getName() {
return this.name;
}
getAge() {
return this.age;
}
getWeight() {
return this.weight; // weight私有,无法访问
}
}
let p1 = new Person('zhufeng', 10, 100, 1000);
p1.name;
p1.age; // age受保护,不可访问
p1.weight; // weight私有,不可访问
代码解释:
上面封装的都是以 属性 来举例,对于类内部的方法来说同样可以用修饰符修饰。
- Person类继承了Animal类,但是Person无法访问Animal类中的 weight 属性,因为 weight 属性 private 修饰。
- 我们new 了一个Person的实例,然后去访问 age,我们发现 age 不可访问,因为 protected修饰,protected修饰的属性或方法只能在本类和子类中访问。
封装的概念较为简单,有代码做理解,这里就不过多赘述了。
三、继承
概念:
-
继承主要的用处是实现代码复用。
-
继承可以把父类和子类的公共方法抽象出来,提高复用,减少冗余。
缺点:
- 过度使用继承或者说继承层次过深会导致代码可读性变差,可维护性变差,子类和父类高度耦合,修改父类的代码会直接影响到子类。
代码展示:
class Animal {
public name: string;
constructor(){
this.name = name;
}
eat(){
console.log('吃东西');
}
}
let animal = new Animal();
animal.eat();
class Dog extends Animal {
}
let dog = new Dog();
dog.eat();
代码解释:
- Dog类继承自Animal类,Dog也就拥有了Animal类所有的方法和属性。
- 说到这里,我们可能就要联系一下另外一个特征:面向对象——封装 了,这个可以自己体会一下。
**题外话:**现在我们一般用的框架,例如:Angular,组件之间也是可以继承的,感兴趣的同学可以尝试尝试组件之间的继承。
四、多态
概念:
- 多态是指,子类可以替换父类。
- 保持子类的开放性和灵活性,可以重写父类的方法。
- 实现面向接口编程。
代码展示:
class Animal {
speak() {
throw new Error('此方法必须由子类实现');
}
}
// 子类必须实现speak方法,不实现就报错
class Dog extends Animal {
speak() {
console.log('汪汪汪');
}
}
class Cat extends Animal {
speak() {
console.log('喵喵喵');
}
}
function talk(animal: Animal) {
animal.speak();
}
talk(new Dog()); //汪汪汪
talk(new Cat()); //喵喵喵
代码解释:
-
Cat类和Dog类都集成了Animal类,同时也都重写了Animal类的speak方法。
子类对父类的方法重写是必须的,不重写就会出错。
-
这种情况下,其实Animal类还有另一种写法,使用
abstract
关键字【抽象类】:abstract class Animal { abstract speak(): void; }
对于父类的方法,子类可以有各种各样的实现,这就是多态。