前端理解控制反转ioc
工作一直都是写前端,而且都是偏业务的。相关的框架代码层面的了解还是有很大的缺失。一直想多写些维护性,可读性强的代码。
这段时间对控制反转ioc,这样的设计有了一个初步的了解。
前端为弱语言,平时代码的时候主要是过程化的思路去解决问题。虽然也会定义一些class,但是和面向对象还是存在很大的差别的。
平时写的偏业务,也不需要抽象,一般也就直接写个实现类,再这个基础上面再进行扩展。主要是不存在类型检测之类的,可以随意一些,相对的错误也不大好发现。
控制反转ioc主要是用于解耦方面,下面看下解耦的最基本的原则
依赖倒置原则(Dependence Inversion Principle,DIP):
A. 上层模块不应该依赖于下层模块,它们共同依赖于一个抽象。
B. 抽象不能依赖于具象,具象依赖于抽象。
简单点就是面向接口编程,具体实现具体类可以更换,只要是实现了某个接口就行。听起来像是鸭子类型
“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
举个场景,比如当前页面结构
Page {
Header
Main {
SideBar
MainContent {
detail
comment
}
}
简易代码如下
1 class Detail {} 2 class Comment { 3 constructor() { 4 console.log('a'); 5 } 6 } 7 class SideBar {} 8 class Header{} 9 class MainContent { 10 constructor() { 11 this.detail = new Detail(); 12 this.comment = new Comment(); 13 } 14 } 15 16 class Main { 17 constructor() { 18 this.sideBar = new SideBar(); 19 this.mainContent = new MainContent(); 20 } 21 } 22 23 class Page { 24 constructor() { 25 this.header = new Header(); 26 this.main = new Main(); 27 } 28 } 29 30 new Page();
我们的评论变了,换成了递归回复console.log('b'),最直接的就是修改Comment类的实现,但是我们的Comment存在a,b,c,d模式,并且同时都可能存在的,看用户的选择的该怎么处理呢??
我们可能就需要考虑在Page上面增加一个commentModel的参数,一直往下面传下去,把Comment类改成一个工厂,根据commentModel的参数返回不同的实现
1 class Detail {} 2 class SideBar {} 3 class Header{} 4 class CommentA { 5 constructor() { 6 console.log('a'); 7 } 8 } 9 class CommentB { 10 constructor() { 11 console.log('a'); 12 } 13 } 14 class CommentFactory { 15 constructor(commentModel) { 16 if (commentModel === 'a') { 17 return new CommentA(); 18 } else if (commentModel === 'b') { 19 return new CommentB(); 20 } 21 } 22 } 23 class MainContent { 24 constructor(commentModel) { 25 this.detail = new Detail(); 26 this.comment = new CommentFactory(commentModel); 27 } 28 } 29 class Main { 30 constructor(commentModel) { 31 this.sideBar = new SideBar(); 32 this.mainContent = new MainContent(commentModel); 33 } 34 } 35 class Page { 36 constructor(commentModel = 'a') { 37 this.commentModel = commentModel; 38 this.header = new Header(); 39 this.main = new Main(commentModel); 40 } 41 } 42 new Page();
上面可以看到为了解决上面的需求,我们差不多所有的相关的类都改了一遍,可我们只是想让comment灵活配置,就这么麻烦么???所以我们就需要依赖倒置,让代码结构解耦
1 class Detail {} 2 class SideBar {} 3 class Header{} 4 class CommentA { 5 constructor() { 6 console.log('a'); 7 } 8 } 9 class CommentB { 10 constructor() { 11 console.log('a'); 12 } 13 } 14 class CommentFactory { 15 constructor(commentModel) { 16 if (commentModel === 'a') { 17 return new CommentA(); 18 } else if (commentModel === 'b') { 19 return new CommentB(); 20 } 21 } 22 } 23 class MainContent { 24 constructor(detail, comment) { 25 this.detail = detail; 26 this.comment = comment; 27 } 28 } 29 class Main { 30 constructor(sideBar, mainContent) { 31 this.sideBar = sideBar; 32 this.mainContent = mainContent; 33 } 34 } 35 class Page { 36 constructor(header, main) { 37 this.header = header; 38 this.main = main; 39 } 40 } 41 var mainContent = new MainContent(new Detail(), new CommentFactory('a')); 42 var main = new Main(new SideBar(), mainContent); 43 new Page(new Header, main);
依赖倒置后,我们只需要传入具体的实现类。对于类的定义,其实是依赖了接口,
现在项目中都是通过把page引用往内部传递,或者把page对象最为全局对象去使用,这样处理影响了代码的可维护性,公用性,耦合性
复杂项目中的依赖关系错综复杂,所以我们就需要引入控制反转控制器(Container)和依赖注入,让Container 管理 具体的对象,通过依赖注入,在代码层面绑定 变量和对象实例的关系,自动创建
下面就通过 typescript + reflect-metadata + inversify 来实现上面的代码
接口定义,大部分绑定都是接口,而不是具体的实现
//interfaces.ts 接口定义 export interface IComment { } export interface IDetail { } export interface ISideBar { } export interface IHeader { } export interface IComment { } export interface IMainContent { detail: IDetail; comment: IComment; } export interface IMain { sideBar: ISideBar; mainContent: IMainContent; } export interface IPage { header: IHeader; main: IMain; }
//types.ts类型,其实可以理解为字符串 const TYPES = { Detail: Symbol.for("Detail"), SideBar: Symbol.for("SideBar"), Header: Symbol.for("Header"), Comment: Symbol.for("Comment"), MainContent: Symbol.for("MainContent"), Main: Symbol.for("Main"), Page: Symbol.for("Page"), }; export { TYPES };
//entities.ts 具体实现类 import { injectable, inject } from "inversify"; import "reflect-metadata"; import { IComment, IDetail, ISideBar, IHeader, IMainContent, IMain, IPage} from "./interfaces"; import { TYPES } from "./types"; @injectable() class Detail implements IDetail{} @injectable() class SideBar implements ISideBar{} @injectable() class Header implements IHeader{} @injectable() class CommentA implements IComment{ constructor() { console.log('a'); } } @injectable() class CommentB implements IComment { constructor() { console.log('b'); } } @injectable() class MainContent implements IMainContent { @inject(TYPES.Detail) detail: IDetail; //依赖注入 @inject(TYPES.Comment) comment: IComment; } @injectable() class Main implements IMain { @inject(TYPES.SideBar) sideBar: ISideBar; @inject(TYPES.MainContent) mainContent: IMainContent; } @injectable() class Page implements IPage { @inject(TYPES.Header) header: IHeader; @inject(TYPES.Main) main: IMain; } export { CommentA, CommentB, Detail, SideBar, Header, MainContent, Main, Page };
//inversify.config.ts 控制器定义,接口绑定对应的实现类 import { Container } from "inversify"; import { TYPES } from "./types"; import { IComment, IDetail, ISideBar, IHeader, IMainContent, IMain, IPage } from "./interfaces"; import { CommentA, CommentB, Detail, SideBar, Header, MainContent, Main, Page } from "./entities"; const containerA = new Container(); containerA.bind<IComment>(TYPES.Comment).to(CommentA); containerA.bind<IDetail>(TYPES.Detail).to(Detail); containerA.bind<ISideBar>(TYPES.SideBar).to(SideBar); containerA.bind<IHeader>(TYPES.Header).to(Header); containerA.bind<IMainContent>(TYPES.MainContent).to(MainContent); containerA.bind<IMain>(TYPES.Main).to(Main); containerA.bind<IPage>(TYPES.Page).to(Page); const containerB = new Container(); containerB.bind<IComment>(TYPES.Comment).to(CommentB); containerB.bind<IDetail>(TYPES.Detail).to(Detail); containerB.bind<ISideBar>(TYPES.SideBar).to(SideBar); containerB.bind<IHeader>(TYPES.Header).to(Header); containerB.bind<IMainContent>(TYPES.MainContent).to(MainContent); containerB.bind<IMain>(TYPES.Main).to(Main); containerB.bind<IPage>(TYPES.Page).to(Page); export { containerA, containerB };
//index.ts import { containerA, containerB } from "./inversify.config"; import { TYPES } from "./types"; import { IPage } from "./interfaces"; containerA.get<IPage>(TYPES.Page); containerB.get<IPage>(TYPES.Page);
当然我们也可以通过注入工厂方法的方式去修改下。
控制反转其实还有一个场景就是测试,A以来B,只是B的值简单处理,但是要构造一个具体的B对象很麻烦。我们可以通过ioc,把B对象替换成一个简单对象来对A进行详细测试。