SOLID原则

  • 单一职责原则(SRP):组件(函数、类、模块)必须专注于单一的任务
  • 开/闭原则(OCP):设计时考虑扩展性,但是扩展时最少地修改原有代码
  • 里氏替换原则(LSP): 只要继承的是同一个接口,程序里任意一个类都可以被其他的类替换。在替换完成后,不需要其他额外的工作程序就能像原来一样运行。
  • 接口隔离原则(ISP):将非常大的接口拆分成小的具体的接口,使客户端只需要关心它们需要到接口
  • 依赖反转原则(DIP):表明一个方法应遵从依赖于抽象(接口) 而不是一个实例(类)的概念

class Person {public name : stringpublic surname : stringpublic email : stringconstructor(name: string, surname: string, email: string) {this.email = emailthis.name = namethis.surname = surname}greet() {console.log('Hi')}
}
const me : Person = new Person('Remo', 'Jansen', 'remo.jasen@qq.com')

constructor是一个特殊的方法,在用new关键字创建一个类的实例的时候用到。声明了一个变量me,用来存储Person类的实例。new关键字使用了Person类的constructor方法返回一个类型为Person的对象

当一个类不遵循SRP,拥有太多属性或者太多方法,称这种对象为God对象。这里如果要对email进行验证最好的方法不是在Person类中加上validateEmail方法,而是通过声明一个Email类。

class Person {public name : stringpublic surname : stringpublic email : Emailconstructor(name: string, surname: string, email: Email) {this.email = emailthis.name = namethis.surname = surname}greet() {console.log('Hi')}
}
const me : Person = new Person('Remo', 'Jansen', 'remo.jasen@qq.com')class Email {public email : stringconstructor(email : string){if (this.validateEmail(email)) {this.email = email} else {throw new Error('Invalid email'))}}validateEmail(email : string){var re = /\S+@\S+\.\S+/return re.test(email)}
}

确保每一个类都只有单一的职责,可以更容易看出一个类做了哪些事情、如何去扩展/改进他,可以通过提高其的抽象等级来改进。如,当使用Email类时,不需要意识到validateEmail方法的存在,那么这个方法就是外部不可见。

class Email {private email : stringconstructor(email : string){if (this.validateEmail(email)) {this.email = email} else {throw new Error('Invalid email'))}}private validateEmail(email : string){var re = /\S+@\S+\.\S+/return re.test(email)}get() : string {return this.email}
}
var email = new Email('remo.jasen@qq.com')

接口

在面向对象的语言中,interface常被用来定义一个不包含数据和逻辑代码但函数签名定义了行为的抽象类型

实现一个接口可以看做是签署了一份协议。接口好比协议,但我们签署他(实现)时,必须遵守他的规则。接口的规则是方法和属性的签名,必须实现他们。

TS中接口不是严格遵守上面协议:

  • 接口可以扩展其他接口或者类
  • 接口可以定义数据和行为而不只是行为

关联、聚合和组合

关联

那些有联系但是他们的对象有独立的生命周期,并且没有从属关系的类之间的关系,称关联。如老师和学生,二者可以有关联关系但是都有自己的生命周期(都可以被独立地创建和删除),所以当老师离开学校时,不必删除任何学生,学生们离开学校时也不必删除任何老师

聚合

将拥有独立生命周期,但是有从属关系,并且子对象不能从属于其他对象的关系成为聚合。如手机和电池,手机停止工作可以删除但是电池不需要删除,可以继续用。

组合

指没有独立生命周期,父对象被删除后子对象也被删除的对象间的关系。如问题和答案,一个问题可以有多个答案,并且一个答案不可以属于多个问题。如果删除问题,答案也会被自动删除。生命周期中依赖其他对象的也被称作弱实体

继承

继承即扩展已有的类。允许我们创建一个类(子类),从已有的类(父类) 上继承所有的属性和方法,子类可以包含父类中没有的属性和方法。

class Person {public name : stringpublic surname : stringpublic email : Emailconstructor(name: string, surname: string, email: Email) {this.email = emailthis.name = namethis.surname = surname}greet() {console.log('Hi')}
}
class Teacher extends Person {teach() {console.log('Welcome to class')}
}

可以使用super达到子类能提供父类同名方法的特殊实现,即方法重写。

class Teacher extends Person {public subjects : string[]constructor(name : string, surname : string, email : Eamil, subjects : string[]) {super(name, surname, email)this.subjects = subjects}greet() {super.greet()console.log(`I teach ${this.subjects}`)}teach() {console.log('Welcome to class')}
}
const teacher = new Teacher('remo', 'jansen', new Email('remo.jasen@qq.com'), ['math', 'physics'])

可以声明一个类继承一个已经继承别的类的类,可以访问其父类的所有方法和属性

class SchoolPrincipal extends Teacher {mangeTeachers() {console.log('We need to help students to get better result!')}
}
const principal = new SchoolPrincipal('remo', 'jansen', new Eamil('remo.jasen@qq.com'), ['math', 'physics'])
principal.greet()
principal.teach()
principal.mangeTeachers()

不推荐有过多层级的继承。维护开发会十分复杂。一般推荐继承树深度(DIT)在0-4之间。

混合

TS不支持多重继承,即一个类只能继承自一个类。这种设计会导致钻石问题,如在多个父类中存在相同方法,调用会有歧义。

引入混合,混合是多重继承的替代,但是功能有一些限制

class Mammal { // 哺乳动物breathe() : string {return 'I am alive'}
}
class WingedAnimal { // 飞行动物fly() : string {return 'I can fly'}
}
class Bat implements Mammal, WingedAnimal {breathe : () => stringfly : () => string
}function applyMixins(derivedCtor: any, baseCtors: any[]) {baseCtors.forEach(baseCtor => {Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {if(name !== 'constructor') {derivedCtor.prototype[name] = baseCtor.prototype[name]}})})
}applyMixins(Bat, [Mammal, WingedAnimal])const bat = new Bat()
bat.breathe()
bat.fly()

限制:

  • 只能在继承树上继承一级的方法和属性
  • 如果多个父类有同名方法,只会继承最后一个类中的方法(即之前继承会被后面的覆盖)

范型类

范型类可以办我们避免重复代码

class User {public name : stringpublic password : string
}class NotGenericUserRepository {private _url : stringconstructor(url :string) {this._url = url}public getAsync() {return Q.Promise((resolve : (users : User[]) => void, reject) => {$.ajax({url: this._url,type: 'GET',dataType: 'json',success: data => {var users = <User[]>data.itemresolve(users)},error: e => {reject(e)}})})}
}var notGenericUserRepository = new NotGenericUserRepository('./demo/shared/user.json')
notGenericUserRepository.getAsync().then(function(users : User[]){console.log('notGenericUserRepository => ', users)})

如果还要请求一个不同于User的其他类型实例的列表,会重复写很多代码。可能会想到用any类型,但是这样的话就失去了TypeScript在编译器中提供的类型检查的保护。更好的方式是创建一个范型。

class GenericRepository<T> {private _url  : stringconstructor(url : string) {this._url = url}public getAsync() {return Q.Promise((resolve: (entities : T[]) => void, reject) => {$.ajax({url: this._url,type: 'GET',dataType: 'json',sucess: data => {var list = <T[]>data.itemsresolve(list)},error: e => {reject(e)}})})}
}
var userRepository = new GenericRepository<User>('./demos/shared/user.json')
userRepository.getAsync().then((users : User[]) => {console.log('userRepository => ', users)})
var talkRepository = new GenericRepository<Talk>('./demos/shared/talks.json')
talkRepository.getAsync().then((talk : Talk[]) => {console.log('talkRepository => ', talks)})

范型约束

对于一些新需求,需要增加一些变更来验证通过Ajax请求到的数据并且只有在验证有效后返回。一些可行的解决方案是在范型或函数内使用typeof操作符来验证参数范型T的类型。

//...
success:data => {var list : T[]var items = <T[]>data.itemsfor(var i = 0;i < items.length; i++) {if(items[i] instanceof User) {//validate user}if(items[i] instanceof Talk) {// validate talk}}resolve(list)
}
//...

上面的解法会导致每增加一个新的有效实例,就必须修改增加额外的逻辑。一个更好的解决方案是给要获取的实例增加一个isValid方法,它在实例通过的时候返回true

//...
success:data => {var list : T[]var items = <T[]>data.itemsfor(var i = 0;i < items.length; i++) {if(items[i].isValid()){//error//..}}resolve(list)
}
//...

还有一种方式遵循SOLID原则中的开/闭原则。通过范型约束解决,范型约束会约束允许作为范型参数中的T的类型。

// 声明接口
interface ValidatableInterface {isValid() : boolean
}
//实现接口,并实现isValid方法
class User implements ValidatableInterface {public name : stringpublic password : stringpublic isValid() : boolean {// user validationreturn true}
}
class Talk implements ValidatableInterface {public title : stringpublic description : stringpublic language : stringpublic url : stringpublic year : stringpublic isValid() : boolean {// talk validationreturn true}
}
// 声明一个范型仓库并加上范型约束
class GenericRepositorWithConstraint<T extends ValidatableInterface> {private _url = : stringconstructor(url : string) {this._url = url}public getAsync() {return Q.Promise((resolve : (talks : T[]) => void, reject) => {$.ajax({url: this._url,type: 'GET',dataType: 'json',success: data => {var items = <T[]>data.itemsfor(var i = 0; i < items.length; i++) {if(items[i].isValid()) {list.push(items[i])}}resolve(list)},error: e => {reject(e)}})})}
}
// 现在可以创建想要的任意多的仓库
var userRepository = new GenericRepositorWithConstraint<User>('./users.json')
userRepository.getAsync().then(function(users : User[]) {console.log(users)})

在范型约束中使用多重类型

interface IMyInterface {doSomething()
}
interface IMySecondInterface {doSomethingElse()
}
// 转变为超接口
interface IChildInterface extends IMyInterface, IMySecondInterface {}
class Example<T extends IChildInterface> {private genericProperty : TuseT() {this.genericProperty.doSomething()this.genericProperty.doSomethingElse()}
}

范型中new操作

function factoryNotWorking<T>() : T {return new T()// 找不到标识T,编译错误
}//要通过范型代码来创建新的对象,需要声明范型T拥有构造函数
// 即用type: { new(): T }代替type: T
function factory<T>() : T {var type: { new() : T }return new type()
}
var myClass: MyClass = factory<MyClass>()

遵循SOLID原则

里氏替换原则

派生类对象能够替换其基类对象被使用

// 将一些对象持久化到某种存储中
interface PersistanceServiceInterface {save(entity : any) : number
}
// 实现接口,使用cookie作为存储介质
class CookiePersitanceService implements PersistanceServiceInterface {save(entity : any) : number {var id = Math.floor((Math.random() * 100) + 1)// Cookie持久化逻辑return id}
}
//一个基于PersistanceServiceInterface依赖的类
class FavouritesController {private _persistanceService : PersistanceServiceInterfaceconstructor(persistanceService : PersistanceServiceInterface) {this._persistanceService = persistanceService}public saveAsFavourite(articleId : number) {return this._persistanceService.save(articleId)}
}//创建实例
var favController = new FavouritesController(new CookiePersitanceService())//LSP允许将依赖换成其他的实现,只要实现是基于同一个基类的
// 存储介质使用H5本地存储
class LocalStoragePersitanceService implements PersistanceServiceInterface {save(entity : any) :number {var id = Math.floor((Math.random() * 100) + 1)//本地存储持久化逻辑return id}
}// 现在可以在不需要对FavouritesController控制类做任何修改的情况下用他替换
var favController2 = new FavouritesController(new LocalStoragePersitanceService())

接口隔离原则

接口被用来声明两个或更多的应用组件间是如何互相操作和交换信息的。

接口隔离原则代表客户端不应强制依赖于他没有使用到的方法。在应用组件内声明API时,声明多个针对特定客户端的接口,要好于声明一个大而全的接口。


interface VehicleInterface {getSpeed() : numbergetVehicleType() : stringistaxPayed() : booleanisLightsOn() : booleanisLightsOff() : booleanstartEngine() : voidacelerate() : numberstopEngine() : voidstartRadio() : voidplayCd : voidstopRadio() : void
}

优化方案

interface VehicleInterface {getSpeed() : numbergetVehicleType() : stringistaxPayed() : booleanisLightsOn() : boolean
}
interface LightsInterface {isLightsOn() : booleanisLightsOff() : boolean
}
interface RadioInterface {startRadio() : voidplayCd() : voidstopRadio() : void
}

依赖反转原则

一个方法应该遵从依赖于抽象而不是一个实例。

命名空间

如果在写一个大型应用,在代码量增加的时候需要引入某种代码组织方案避免命名冲突,并使代码更加容易跟踪和理解。可以用命名空间包裹那些有联系的接口、类和对象。

namespace app {export namespace models {//可以简写 app.modelsexport class UserModel {//...}export class TalkModel {//...}}
}var user = new app.models.UserModel()
var talk = new app.models.TalkModel()

模块

与命名空间的区别:在声明了所有的模块之后,我们不会使用<script>标签引入它们,而是通过模块加载器来加载。

模块加载器是在模块加过程中为我们提供更好控制能力的工具,可以优化加载任务(如异步加载或合并多个模块到单一文件)。

使用<script>标签的方式并不被推荐,因为当浏览器发现一个<script>标签时,它不会使用异步请求加载这个文件。应该尽可能地尝试异步加载文件,因为这样能显著提高Web程序的网络性能。

常用模块加载器:

  • RequireJS:  RequireJS使用了一个被称作异步模块定义的语法(AMD)
  • Browserify:该语法被称作CommonJS
  • SystemJS:一个通用模块加载器。支持所有的模块定义语法(ES6、AMD、UMD)

TS允许选择在运行环境中使用哪一种模块定义语法

tsc --module commonjs main.ts // CommonJS
tsc --module amd main.ts // AMD
tsc --module umd main.ts // UMD
tsc --module system main.ts // SystemJS

在程序设计阶段只能是选择两种模块定义语法

  • 外部模块语法
  • ES6模块语法

设计阶段和运行时使用的模块语法可以不一样

ES6模块

class UserModel {//...
}
export { UserModel }

export class UserModel {//...
}

别名输出

class UserModel {//...
}
export { UserModel as User } // UserModel输出为User

一个export输出所有同名定义

interface UserModel {//...
}
class UserModel {//...
}
export { UserModel } //输出接口和函数

引入

import { UserModel } from './models.js'

导出多个实体

class UserValidator {//...
}
class TalkValidator {//...
}
export { UserValidator, TalkValidator }

导入


import { UserValidator, TalkValidator } from './validation.ts'

外部模块语法——仅在设计阶段可用

一旦编译成JS,将会被转换成AMD、CommonJS、UMD或SystemJS。应避免使用这种语法而应使用新的ES6语法代替。

导入

import User = require('./user_class')

直接导出

export class User {// ...
}

赋值导出

class User {//...
}
export = User

AMD模块定义语法——仅在运行时使用

初始的外部模块语法编译成AMD

define(['require', 'exports'], function(require, exports) {var UserModel = (function () {function UserModel() {}return UserModel})()return UserModel
})

define函数第一个参数为数组,包含了依赖的模块名列表。第二个参数是一个回调函数,这个函数将在所有依赖全部加载完成时执行一次。

CommonJS模块定义语法——仅在运行时使用

class User {//...
}
export = User

生成CommonJS

var UserModel = (function () {function UserModel() {//...}return UserModel
})()
module.exports = UserModel

上面的CommonJS模块不需要任何修改就能被NodeJS程序使用import和require关键字加载

import UserModel = require('./UserModel')
var user = new UserModel()

当尝试在浏览器中使用require时,会抛出异常,因为require未被定义。可以使用Browserify解决

1.安装

npm i -g browserify

2.将所有的CommonJS模块打包成一个JS文件

browserify main.js -o bundle.js

3.在<script>标签中引入bundle.js

UMD模块定义语法——仅在运行时使用

如果要发布一个JS库或者框架,需要将TS编译成CommonJS和AMD。

定义通用模块

(function (root, factory) {if (typeof exports === 'object') {module.exports = factory(require('b)) //CommonJS} else if (typeof define === 'function' && define.amd) {// AMDdefine(['b'], function(b) {return (root.returnExportGlobal = factory(b))})} else {//全局变量root.returnExportGlobal = factory(root.b)}
}(this, function(b) {//真正的模块return {}
}))

其他方法实现UMD

1.使用--compile umd标识

2.使用模块加载器,比如Browserify

SystemJS模块定义——仅在运行时使用

SystemJS可以使在不兼容ES6的浏览器上,更加贴近它们语义地使用ES6模块定义方法。

循环依赖

类似A依赖B,B依赖A

Learning TypeScript 0x3 面向对象编程相关推荐

  1. 面向对象编程的灾难:是时候考虑更新换代了!

    全文共13316字,预计学习时长26分钟 图片来源:Unsplash/Jungwoo Hong 许多人认为面向对象编程是计算机科学的珍宝,代码组织的最终解决方案,所有问题的终极回答,编写程序的唯一真正 ...

  2. ruby 新建对象_Ruby面向对象编程的简介

    ruby 新建对象 by Saul Costa 由Saul Costa Object-oriented programming (OOP) is a programming paradigm orga ...

  3. python面向对象编程(封装与继承)

    一. 面向过程编程语言 "面向过程"(Procedure Oriented)是一种以过程为中心的编程思想.分析出解决问题所需要的步 骤,然后用函数把这些步骤一步一步实现,使用的时候 ...

  4. Java面向对象编程(高级)

    面向对象编程(高级) 类变量和类方法 01: package ChildDemo;public class Child {private String name;public static int c ...

  5. JavaScript学习随记——面向对象编程(继承)

    @Example:基于原型链的继承 <!DOCTYPE HTML> <html><head><meta http-equiv="Content-Ty ...

  6. 了解使用JavaScript进行面向对象编程的基础(并增强您的编码…

    by Kris Baillargeon 通过克里斯·拜伦 学习使用JavaScript进行面向对象编程的基础知识(并增强您的编码能力!) (Learn the basics of object-ori ...

  7. ruby 新建对象_Ruby面向对象编程简介

    ruby 新建对象 by Nishant Mishra 由Nishant Mishra Ruby面向对象编程简介 (An Introduction to Object-Oriented Program ...

  8. python基础 面向对象编程

    目录 面向对象编程 1.什么是面向对象编程? 面向过程编程思想 面向对象编程思想 2.什么是类?如何定义. 类: 对象 3.如何产生对象? 调用类时的事件: 4.对象属性的查找顺序: 5.类内部的函数 ...

  9. 面向对象编程 面向过程编程_面向对象的编程真的是死定了

    面向对象编程 面向过程编程 重点(Top highlight) Programming in the 1960s had a big problem: computers weren't that p ...

最新文章

  1. 设置VSCode自动保存
  2. 独家 | 高季尧:定制化优化算法的应用与威力(附PPT)
  3. 在 Mac OS X 上安装 TensorFlow
  4. DOM对象和内置对象(中)
  5. Bat 多个执行操作选择
  6. mysql的多master调度_innodb中master线程的调度的算法改进(mysql 5.6.26)
  7. flink 6-检查点和水位线
  8. Java8之Stream详解
  9. linux 安装simg2img,linux可执行文件执行时提示No such file or directory(docker环境中运行的ubuntu镜像)...
  10. 学习笔记: 委托解析和封装,事件及应用
  11. 关于HD-SDI原理设计、PCB设计汇总
  12. android6.0华为刷机包,华为畅享6官方rom刷机包_华为畅享6原版系统包_升级包
  13. jQuery 遍历 - find() 方法
  14. 视频与编解码的技术邂逅,碰撞出的高清罗曼史
  15. 福州大学866信号与系统初试经验分享
  16. irobot擦地机器人故障_Irobot Braava380t擦地机器人 操作使用说明
  17. ppt编辑器android,ppt编辑器
  18. docker Docs
  19. java和区块链哪个难_java 区块链中设计合理的难度系数
  20. There was an error while executing `VBoxManage`, a CLI used by Vagrant for controlling VirtualBox.错误

热门文章

  1. QBC以及QBE例子
  2. hibernate QBC和QBE精讲与案列分析(下)
  3. 算法笔记--极大极小搜索及alpha-beta剪枝
  4. 阿翔编程学-Axis传递Pojo对象
  5. Linux 查看CPU信息,机器型号,内存等信息
  6. NFT老炮CryptoPunks解析与实现
  7. HBase轻松入门之HBase架构图解析
  8. 移动端前台页面需要注意的几点
  9. docker 中 --privileged 参数
  10. 使用OpenSSL生成自己服务器的证书