# 面向切面编程 SOLID

SOLID 是面向对象设计的 5 大重要原则的首字母缩写,当我们设计类和模块时,遵守 SOLID 原则可以让软件更健壮和稳定。

# SOLID

  • 单一职责原则(SRP)
  • 扩展开放修改封闭原则(OCP)
  • 里氏替换原则(LSP) 子类可以替换基类
  • 接口隔离原则(ISP)
  • 依赖倒置原则(DIP)

oop 是静态的抽象 aop 是动态抽象

# 单一职责原则(SRP)

// bad
class UserSettings {
  constructor(user) {
    this.user = user;
  }
  changeSettings(settings) {
    if (this.verifyCredentials()) {
      // ...
    }
  }
  verifyCredentials() {
    // ...
  }
}
// good
class UserAuth {
  constructor(user) {
    this.user = user;
  }
  verifyCredentials() {
    // ...
  }
}
class UserSetting {
  constructor(user) {
    this.user = user;
    this.auth = new UserAuth(this.user);
  }
  changeSettings(settings) {
    if (this.auth.verifyCredentials()) {
      // ...
    }
  }
}

# 扩展开放修改封闭原则(OCP)

class AjaxAdapter extends Adapter {
  constructor() {
    super();
    this.name = "ajaxAdapter";
  }
  request(url) {}
}
class NodeAdapter extends Adapter {
  constructor() {
    super();
    this.name = "nodeAdapter";
  }
  request(url) {}
}
class HttpRequester {
  constructor(adapter) {
    this.adapter = adapter;
  }
  fetch(url) {
    return this.adapter.request(url).then((response) => {
      // 传递response并return
    });
  }
}

# 接口隔离原则(ISP)

接口隔离原则认为:多个特定客户端接口要好于一个宽泛用途的接口

interface IDataAccess {
  void OpenConnection();
  void CloseConnection();
}
interface ISqlDataAccess: IDataAccess{
  void ExecuteSqlCommand();
}
interface IOracleDataAccess: IDataAccess{
  void ExecuteOracleCommand();
}
class SqlDataAccess: ISqlDataAccess {

}

# 依赖倒置原则(DIP)

解耦

  • 高层模块不应该依赖于底层模块,二者应该依赖于抽象
  • 抽象不应该依赖于细节,细节应该依赖于抽象

# 依赖注入 DI

# 控制反转 IOC

Inversion of Control,是面向对象编程中的一种设计原则,可以用来减低计算机代码之间爱你的耦合度。其中最常见的方式叫依赖注入(Dependency Injection,简称 DI),还有一种方式叫做依赖查找(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它,也可以说,依赖被注入到对象中。

# 面向切面编程 AOP

是面向对象编程的 OOP 的延续

# inversifyjs

InversifyJS 是一个轻量的(4kb)控制反转容器(IOC),可用于编写 TypeScript 和 JavaScript 应用。它使用类构造函数去定义和注入它的依赖,

# 应用

npm install inversify reflect-metadata --save
// tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "lib": ["es6"],
    "types": ["reflect-metadata"],
    "module": "commonjs",
    "moduleResolution": "node",
    "experomentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}
// app.ts
import { InversifyKoaServer } from "inversify-koa-utils";
import "reflect-metadata";
import { Container } from "./ioc";
import "./ico/loader"; // 所有文件的加载器
/// 创建一个基本容器
const container = new Container();
let server = new InversityKoaServer(container);
// 启动容器
let app = server.build();
app.listen(3000, () => {
  console.log("Inversity 实践SOLID系统启动成功");
});
// ico/index.ts
import { Container, injectable, inject } from "inversify";
import * as Router from "koa-router";
import { interface, controller, httpGet } from "inversify-koa-utils";
export { interface, Container, Router, controller, httpGet };
// ico/loader.ts
import "../controllers/IndexController";
// controllers/IndexController.ts
// interface
import { Router, controller, interface, httpGet } from "../ioc";
import TYPES from "../constant/tags";
export default class IndexController implements interfaces.Controller {
  private indexService; // 类型
  constructor(@inject(TYPES.IndexService) indexService) {
    this.indexService = indexService;
  }
  @httpGet("/")
  private async index(
    ctx: Router.IRouterContext,
    next: () => Promise<any>
  ): Promise<any> {
    const result: string = this.indexService.getUser();
    ctx.body = await ctx.render("index");
  }
}
// constant
const TAGS = {
  IndexService: Symbol.for("IndexService"),
};
export default TAGS;
export interface IIndex {
  getUser(id: String):object{

  }
}

# awilix

构造注入 Nodejs 的 IOC 容器

npm i awilix
npm i awilix-koa  // ---- koa

# 容器参数

  • createContainer:创建容器,在项目启动时执行
  • InjectionMode: 注入模式,awilix 注入依赖的模式
  • asClass: 将注入的文件作为一个类注入,即需要实例化
  • asFunction: 将注入的文件作为一个方法,无需实例化
  • asValue: 将注入的文件作为常量访问
  • Lifetime: 生命周期。默认单例模式(SINGLETON)
  • dbModels: 容器的数据访问层,将数据库的模型的实例赋值

# 加载类文件

  • loadModules(path, options): 第一个参数是文件路径的数组,第二个参数是对传入依赖的一些配置:formatName: 'camelCase',类名导出的格式,默认驼峰式。这个意思就是说,不管文件名怎么写,最后导出时使用的类名都是驼峰式
  • resolveOptions: 注入方法等
resolveOptions: {
  injectionMode: InjectionMode.CLASSIC,
  lifetime: Lifetime.SINGLETON,
  register: asClass
}
// index.js
const awilix = require("awilix");
const container = awilix.createContainer({
  injectionMode: awilix.InjectionMode.PROXY,
});
class UserController {
  constructor(opts) {
    this.userService = opts.userService;
  }
  getUser(ctx) {
    return this.userService.getUser(ctx.params.id);
  }
}
container.register({
  userController: awilix.asClass(UserController),
});
const makeUserService = ({ db }) => {
  return {
    getUser: (id) => {
      return db.query(`select * from users where id=${id}`);
    },
  };
};
container.register({
  userService: awilix.asFunction(makeUserService),
});

function Database(connectionString, timeout) {
  this.conn = connectToYourDatabaseSomehow(connectionString, timeout);
}
Database.prototype.query = function(sql) {
  return this.conn.rawSql(sql);
};
container.register({
  db: awilix.asClass(Database).classic(),
});
contianer.register({
  connectionString: awilix.asValue(process.env.CONN_STR),
  timeout: awilix.asValue(1000),
});
router.get("/api/users/:id", container.resolve("userController").getUser);
router.get("/api/users/:id", container.cradle.userController.getUser);
// app.js
const { loadControllers } = require("awilix-koa");
const { asClass, asValue, Lifetime, createContainer } = require("awilix");
// 容器
const container = createContainer();

// 要注入的所有类装载到container中
container.loadModules([__dirname + "/services/*.js"], {
  // 制定以下当前的注入函数是以什么形式
  // 例如  IndexService.js 为文件名
  // IndexController.js文件里的 constructor({indexService}){}   indexService 为形式
  formatName: "camelCase", // 驼峰
  resolverOptions: {
    lifetime: Lifetime.SCOPED, // 单例 生命周期
  },
});
// 每一次的请求都去创建
app.use(scopePerRequest(container));
// 自动去装载路由
app.use(loadControllers(__dirname + "/controllers/*.js"));
// indexController.js
import { route, GET } from "awilix-koa";
@route("/")
class IndexController {
  constructor({ indexService }) {
    this.indexService = indexService;
  }
  @route("/list")
  actionIndex() {
    return async (ctx, next) => {
      const data = this.indexService.getData();
      ctx.body = await ctx.render("books/pages/index", {
        data,
      });
    };
  }
  @route("/add")
  @GET()
  actionAdd() {
    return async (ctx, next) => {
      ctx.body = await ctx.render("books/pages/add");
    };
  }
}
export default IndexController;
  • 添加装饰器
// 第一步:npm
npm i @babel/plugin-proposal-decorators --save-dev
// 第二步:.babelrc
{
  "plugins": ["@babel/plugin-proposal-decorators"]
}