设计模式和类
类的构成
类可以包含构造函数方法,实例方法,获取函数,设置函数和静态类方法,但这些都不是必须的。空的类定义照样有效。默认情况下,类定义中的代码都在严格模式下执行。
类表达式的名称是可选的。在把类表达式复制给变量后,可以通过name属性取得类表达式的名称字符串。但不能在类表达式作用域外部访问这个标识符。
类构造函数
constructor关键字用于在类定义块内部创建类的构造函数。方法名constructor会告诉解释器在使用new操作符创建类的新实例时,应该调用这个函数。 构造函数的定义不是必须的,不定义构造函数相当于构造函数定义为空函数。
1. 实例化
使用new操作符实例化person的操作等于使用new调用其构造函数。唯一可感知的不同之处就是,js解释器直到使用new和类意味着应该使用constructor函数进行实例化。
使用new调用类的构造函数会执行如下操作。
- 在内存中创建一个新对象。
- 在这个新对象内部的[[Prototype]]指针被赋值为构造函数的prototype属性。
- 构造函数内部的this被赋值为这个新的对象。
- 执行构造函数内部的代码(给新对象添加属性)
- 如果构造函数返回非空对象,则返回该对象,否则,返回刚创建的新对象。
默认情况下,类构造函数会在执行之后返回this对象。构造函数返回的对象会被作用实例化的对象,如果没有什么引用新创建的this对象,那么这个对象会被销毁。不过,如果返回的不是this对象。而是其他的对象,那么这个对象不会通过instanceof操作符检测出跟类有关联,因为这个对象的原型指针并没有被修改。
类构造函数与构造函数的主要却别是,调用类构造函数必须使用new操作符。而普通构造函数如果不实用new操作符,那么就会以全局的this作为内部对象。调用类构造函数时如果忘记使用new会报错。
类构造函数并没有什么特殊,实例化之后,它会成为普通的实例方法(但作为类构造函数,仍然需要使用new调用)因此,实例化之后可以在实例上引用它。
2.把类当成特殊函数
没有类这个类型,从各方面来看类就是一种特殊的函数。声明一个类之后,通过typeof查看,表明就是一个函数。
类标识符又prototype属性,而这个原型也有一个constructor属性指向类自身。
如前所述,类本身具有与普通构造函数一样的行为。在类的上下文中,类本身在使用new调用时会被当作构造函数。重点在于,类中定义的constructor放啊不会被当作构造函数,在对它使用instanceof操作符时会返回false。但是,如果在创建实例时直接将类构造函数当作普通构造函数来使用,那么结果会反转。
3.实例,原型和类成员
类的语法可以非常方便地定义应该存在于实例上的成员,应该存在于原型上的成员,以及应该存在于类本身的成员。
- 实例成员 每次通过new调用类标识符时,都会执行类构造函数也就是constructor方法。在这个函数内部,可以为新创建的实例(this)添加自有属性。至于添加什么样的属性,则没有限制。另外,在构造函数执行完毕之后,荏苒可以给实例添加新的成员。
- 原型方法于与访问器 为了在实例间共享方法,类定义语法把在类块中定义的方法作为原型方法 类定义也支持获取和设置访问器。语法行为和普通函数一样。
3.静态类方法
可以在类上定义静态方法。这些方法通常于执行不特定于实例的操作,也不要求存在类的实例。与原型成员类似,静态成员每个类上只能有一个。
静态类成员在类定义中使用static关键字作为前缀。在静态成员中,this引用类自身。其他约定跟原型成员一样。
4.非函数原型和类成员
虽然类定义并不显性支持在原型或者类上添加成员数据,但在类定义的外部,可以手动添加。
设计模式
目的是为了提高代码的可复用性,可读性,可维护性。
设计模式的本质是面向对象设计原则的基本运用,是对类的封装性,继承性和多态性以及类的关联关系和组合关系的充分理解。
面向对象编程
- 面向对象编程是一种编程范式或编程风格,它以类或对象作为组织代码的基本单位,并将封装,抽象,继承,多态四大特性,作为代码设计和实现的基石。
- 面向对象编程语言是支持类或对象的语法机制,并有线程的语法机制,能更方便的实现面向对象编程四大特性的编程语言
- 面向对象开发 包括面向对象分析OOA,面向对象设计OOD 面向对象编程OOP
ts关于private public static的含义
- private:声明后只能在类的内部访问,如果是contractor用了private修饰符的话 不能被外部实例化
- static:声明后只能通过类本身访问到,也就是修饰符修饰的函数内部this指向的是整个类
- public:声明类的属性可以在穿参的时候进行简写
单例模式
保证一个类只有一个实例,并提供一个访问它的全局访问点。
class Boll{
private _instance = undefined
private contructor(){};
public static getInstance()=>{
if(this._instance===undefined){
this._instance = new Boll()
}
return this._instance;
}
}
//单例模式
class Modal {
private state: String;
static instance: any;
private constructor() {
this.state = "hide";
}
show() {
if (this.state === "show") {
console.log("already show");
return;
}
this.state = "show";
}
hide() {
if ((this.state = "hide")) {
console.log("already hide");
return;
}
this.state = "hide";
}
static getInstance() {
if (!this.instance) {
this.instance = new Modal();
}
return this.instance;
}
}
let p = Modal.getInstance();
p.show();
p.show();
优点:
- 划分命名空间,减少全局变量
- 增强模块性,把自己的代码组织在一个全局变量名下,放在单一位置,便于维护
- 只会实例化一次,简化了代码的调试和维护
缺点:
- 由于单例模式是一种单点访问,所以它又可能会导致模块间的强耦合,从而不利于单元测试。无法单元测试一个调用了来自单例的方法的类,而只能把它与那个单例作为一个单元一起测试
观察者模式
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变的时候,所有依赖于它的对象都得到通知并被自动更新,当一个对象的改变需要同时改其他对象,并且它不知道具体多少对象需要改变的时候,就应该考虑使用观察者模式。
优点:
- 支持简单的广播通信,自动通知所有已经订阅过的对象
- 目标对象与观察者之间的抽象耦合关系能单独扩展和重用
- 增加了灵活性
- 观察者模式所做的事件就是解耦,让耦合的双方都依赖于抽象,而不是依赖与具体。
class Subject {
state: Number;
observers: Observer[];
//初始化
constructor() {
this.state = 0;
this.observers = [];
}
getState() {
return this.state;
}
//被观察者改变状态的时候需要调用观察者的update方法更新
setState(state:Number): void {
this.state = state;
this.notifyAllObservers()
}
//检测到state发生变化的时候需要从observers的list中遍历调用update
notifyAllObservers(): void {
this.observers.forEach((observer) => {
observer.update();
});
}
//每次构造一个观察者的时候都需要塞一个新的观察者的实例到被观察者的数组中
attach(observer:Observer) {
this.observers.push(observer);
}
}
class Observer {
name: String;
subject: Subject;
// 初始化的时候需要调用attach 将当前的实例后push到被观察者的观察者列表中
constructor(name:String, subject:Subject) {
this.name = name;
this.subject = subject;
this.subject.attach(this);
}
//被观察者state更新的时候 会调用观察者实例的这个方法,从而获取被观察者最新的state
update() {
console.log(`${this.name} update,state:${this.subject.getState()}`);
}
}
let s = new Subject();
let p1 = new Observer("p1", s);
let p2 = new Observer("p2", s);
s.setState(10);
工厂模式
工厂模式定义一个用于创建对象的接口,这个接口由子类决定实例化哪个类。该模式使一个类的实例化延迟到了子类。而子类可以重写接口方法以便创建的时候指定自己的对象类型
适用场景:
- 如果不想让某个子系统与较大的那个对象之间进行强耦合,而是想运行时从许多子系统中进行挑选化,那么使用工厂模式是一个不错的选择
- 将new操作简单封装,遇到new的时候就应该考虑是否使用工厂模式
- 需要依赖具体环境创建不同实例,这些实例都有相同的行为,这个时候可以使用工厂模式,简化实现过程,同时也可以减少每种对象所需要的代码量,有利于消除对象之间的耦合,提供较大的灵活性。
class Product{
constructor(name){
this.name = name
}
init(){
console.log('init')
}
}
class Factory{
create(name){
return new Product(name)
}
}
修饰器模式
可以在不改变原有的类或者对象的前提下,增强对象的功能,是一种实现继承的替代方案,对被修饰的类或者方法进行包装扩展,满足不同用户的需求
优点:
- 装饰类和被装饰类只关心自己的核心业务,实现了解耦
- 方便动态的扩展功能,且提供了比继承更多的灵活性
class Phone{
create(){
console.log('手机')
}
}
class Decorator{
phone:Phone
//初始化的时候需要创建被修饰的类的实例
constructor(phone:Phone){
this.phone = phone
}
//增加原有类或者对象的扩展功能
addExtend(){
this.phone.create()
this.createExtend()
}
//需要修饰起添加的功能
createExtend(){
console.log('手机壳')
}
}
let phone = new Phone()
let d1 = new Decorator(phone)
d1.addExtend()
组合模式
将对象组合成树形结构,以表示 整体-部分的层次结构,通过对象的多态表现,使得用户对单个对象和组合对象使用的一致性
type order = Tran|Hotel
class Tran{
create():void{
console.log('火车')
}
}
class Hotel{
create():void{
console.log('酒店')
}
}
class Componse{
orderList:order[]
// 初始化创建一个订单列表
constructor(){
this.orderList = []
}
// 每次购买的时候都接受一个订单的实例,然后塞进列表中,需要链式调用,所以返回的是当前的Componse的实例
buy(order:order):Componse{
this.orderList.push(order)
console.log(this.orderList)
return this
}
// 组合后创建的话需要依次调用每个订单的实例的创建方法,来创建订单。最后返回Componse的实例
create():Componse{
this.orderList.forEach(i=>{
i.create()
})
return this
}
}
let tran = new Tran()
let hotel = new Hotel()
let componse = new Componse()
componse.buy(tran).buy(hotel).create()
中介者模式
接触对象与对象之间的耦合关系。增加一个中介者对象后,所有的相关对象都通过终结者来通信,而不是互相引用,所以当一个对象发生变化的时候,只需要通知中介者对象即可。中介者使得对象之间的耦合松散,而且可以独立的改变他们之间的交互。中介者模式使得网状的多对多的关系变成了单一的一对一多的关系(类似观察者模式,但是是单向的,由中介者统一关系)
优点:
- 使各个对象之间的耦合松动,而且可以独立的改变他们之间的交互
- 中介者和对象一对多的关系取代了对象之间的网状多对多的关系,路线更加清晰
- 如果对象之间的负责耦合度导致维护困难,而且耦合度随着项目变化很快,需要中介者重构代码
class A{
count:number
constructor(){
this.count = 0
}
// 这边掉A的修改数据的时候需要判断当前是否需要通过中介者改变b的count,需要的话 要调用中介者的方法进行改变而不是直接掉b的实例改变 避免耦合在一起
setCount(count:number,m?:Mediator){
this.count = count
if(m){
m.setB(count)
}
}
}
class B{
count:number
constructor(){
this.count = 0
}
setCount(count:number,m?:Mediator){
this.count = count
if(m){
m.setA(count)
}
}
}
class Mediator{
a:A
b:B
// 中介者需要接受当前需要承接中介的实例
constructor(a:A,b:B){
this.a = a
this.b = b
}
// 需要通过中介者改变的count
setA(count:number){
this.a.setCount(count - 1)
}
setB(count:number){
this.b.setCount(count + 1)
}
}
let a = new A()
let b = new B()
let m = new Mediator(a,b)
a.setCount(10,m)
console.log(b.count)
适配器模式
讲一个类的接口转换成另一个接口,以满足用户的需求,使类之间接口不兼容的问题通过适配器解决。
优点:
- 可以让任何两个没有关联的类一起运行
- 提高了类的复用性
- 适配对象,适配库,适配数据
缺点:
- 额外对象的创建,非直接调用,存在开销
- 如果没必要使用适配器模式的话,可以考虑重构
class Plug{
constructor(){}
create(){
return 'iPhone'
}
}
class Target{
plug:Plug
// 初始化的时候 创建需要被适配的类的实例
constructor(){
this.plug = new Plug()
}
// 在这里进行被适配的类的接口的封装,进行增强或者改造操作
addFunction(){
console.log(`${this.plug.create()}和一些其他的功能`)
return this.plug.create() + '和一些其他的功能'
}
}
let t = new Target()
t.addFunction()