typescript 模块_如何使用TypeScript装饰器编写模块化代码并解决横切关注点

typescript 模块

If you have worked with a framework like Angular you have used syntax such as @Component, @HostListenerand and @viewchild. These are known as TypeScript decorators. They are one of the most powerful yet under-used features of TypeScript.

如果您使用过类似Angular的框架,则可以使用诸如@Component @HostListenerand@Component @HostListenerand@viewchild @Component语法。 这些被称为TypeScript装饰器。 它们是TypeScript最强大但未充分使用的功能之一。

In this article we will discuss:

在本文中,我们将讨论:

  • What decorators are

    什么是装饰
  • How to implement your own decorators

    如何实现自己的装饰器
  • Leveraging decorators to write clean modular code

    利用装饰器编写干净的模块化代码
  • Aspect Oriented Programming and decorators

    面向方面的编程和装饰器
  • Cross-cutting-concerns and solving them with decorators

    横切问题,并用装饰器解决

首先-什么是装饰器? (First - What is a Decorator?)

A decorator is a function that allows us to annotate our code or hook into its behavior (similar to meta-programming).

装饰器是允许我们注释代码或挂钩其行为的函数(类似于元编程)。

Decorators are very good at creating abstractions. When a piece of logic needs to be duplicated in many places we can use a decorator for it. Decorators also solve problems that are hard to solve with inheritance (more on this later).

装饰者非常擅长创建抽象。 当需要在许多地方复制逻辑时,可以使用装饰器。 装饰器还解决了难以通过继承解决的问题(稍后会详细介绍)。

There are 4 types of Decorators in TypeScript and they are as follows:

TypeScript中有4种类型的Decorator,它们如下:

类装饰器 (Class Decorators)

A Class Decorator is declared before a class declaration. A class decorator is applied to the constructor of the class and can be used to observe, modify, or replace a class definition.

在类声明之前声明类装饰器。 类装饰器应用于类的构造函数,可用于观察,修改或替换类定义。

Here’s an example:

这是一个例子:

@robot
class Greeter {
 private greeting: string;
 constructor(message: string) {
   this.greeting = message;
 }
 greet() {
   return "Hello, " + this.greeting;
 }
}
 
function robot(constructor: T) {
 return class extends constructor {
   greeting = "from  Robot :-{";
 };
}
let k = new Greeter("shadid");
console.log(k.greet());

In this example we are overriding the constructor of our Greeter class. Our robot decorator function is just like a normal function. However, it takes in a class constructor as an argument. We can use this constructor that is being passed in as a parameter and override its functionality.

在此示例中,我们将重写Greeter类的构造函数。 我们的robot装饰器功能就像普通功能一样。 但是,它将类构造函数作为参数。 我们可以使用此构造函数作为参数传入,并覆盖其功能。

方法装饰器 (Method Decorator)

A Method Decorator is declared before a method declaration. It is applied to the Property Descriptor for the method, and can be used to observe, modify, or replace a method definition.

在方法声明之前声明方法装饰器。 它应用于方法的属性描述符,并且可以用于观察,修改或替换方法定义。

Here’s an example:

这是一个例子:

declare type MethodDecorator = (
 target: Object,
 propertyKey: string | symbol,
 descriptor: TypedPropertyDescriptor
) => TypedPropertyDescriptor | void;
 
class Robot {
 greeting: string;
 constructor(message: string) {
   this.greeting = message;
 }
 
 @logMethod("Greet")
 greet() {
   return "Hello, " + this.greeting;
 }
}
 
function logMethod(message: string): MethodDecorator {
 console.log(`${message} evaluated`);
 return function(
   target: Object,
   propertyKey: string,
   descriptor: PropertyDescriptor
 ): void {
   console.log(`${message} called`);
 };
}
 
let bot = new Robot("I am robo, nice to meet you :)");
console.log(bot.greet());

Here we first defined a MethodDecorator type. We created a method decorator called logMethod. This decorator function is a factory function that returns another function of type MethodDecorator. While using decorators it is a common practice to use a factory function. The returned function is where we will usually apply our logic.

在这里,我们首先定义了MethodDecorator类型。 我们创建了一个名为logMethod的方法装饰器。 此装饰器函数是一个工厂函数,它返回另一个类型为MethodDecorator的函数。 使用装饰器时,通常使用工厂功能。 返回的函数通常是我们应用逻辑的地方。

物业装饰 (Property Decorator)

The Property Decorator is applied to a Property definition. You are probably noticing a pattern already . Below is an example:

属性装饰器应用于属性定义。 您可能已经注意到一种模式。 下面是一个示例:

class Greeter {
 @logGreet("Logging Greetings")
 greeting: string;
 ....
}
 
function logGreet(formatString: string) {
 console.log(formatString);
}

参数装饰器 (Parameter Decorator)

As the name suggests, this decorator is applied to the parameters of methods. Below is an example:

顾名思义,此装饰器应用于方法的参数。 下面是一个示例:

class Greeter {
 ...
 public makeRobotNoise(
   @logParameter("Param for method robot noise") person: Person
 ) {
   this._directReports.push(person);
 }
}
 
function logParameter(message: string): ParameterDecorator {
 console.log(`${message} logParam factory`);
 return function(
   target: Object,
   propertyKey: string,
   parameterIndex: number
 ): void {
   console.log(`${message} called from return function`);
 };
}

These are some of the basic examples of decorators. Now let's work though a real world project together where we can implement decorators to cut down repeated functionality in our code.

这些是装饰器的一些基本示例。 现在,让我们一起完成一个真实的项目,在这里我们可以实现装饰器,以减少代码中的重复功能。

应用我们所学 (Applying what we've learned)

如何实现自己的装饰器 (How to implement your own decorators)

We will be building a REST api server with Node.js, Express and TypeScript. We will structure our code for scalability and proper testing. We will also look at a concept called Aspect Oriented Programming (AOS) and apply a layered architecture.

我们将使用Node.js,Express和TypeScript构建REST api服务器。 我们将为可伸缩性和适当的测试构造代码。 我们还将研究称为面向方面编程(Aspect Oriented Programming,AOS)的概念,并应用分层体系结构。

Alright let's dive in.

好吧,让我们潜入。

Get the starter code from the following directory. I have setup a TypeScript project with Express for you to follow along.

从以下目录获取入门代码。 我已经使用Express设置了TypeScript项目,以供您遵循。

https://github.com/Shadid12/entity-framework/tree/intro_1

https://github.com/Shadid12/entity-framework/tree/intro_1

Let's go over the project together.

让我们一起来看一下这个项目。

src/app.ts

src/app.ts

import APIServer from "./APIServer";
 
export const apiServer = new APIServer();
 
apiServer.start();

app.ts is our application entry point. You can see that we are creating an instance of the APIServer class and calling the start() method. APIServer class encapsulates all our server related code. Let's take a look inside of it.

app.ts是我们的应用程序入口点。 您可以看到我们正在创建APIServer类的实例并调用start()方法。 APIServer类封装了我们所有与服务器相关的代码。 让我们看一下它的内部。

import * as http from "http";
import express, { Express } from "express";
import bodyParser from "body-parser";
 
export default class APIServer {
    private _app: Express;
    private _server: http.Server;
 
 
    constructor() {
        this._app = express();
 
        // Set port
        this._app.set("port", process.env.PORT || 3000);
 
        // Add Middleware
        this.configureMiddleware();
    }
 
 }

First we import all the necessary libraries such as express, bodyParser and http. Then we create the class. We also declare two private variables _app and _server. In our constructor we are creating the express instance and setting it to _app. We are also calling the configuration method. This method will take care of the express configuration code.

首先,我们导入所有必需的库,例如express,bodyParser和http。 然后我们创建类。 我们还声明了两个私有变量_app_server 。 在我们的构造函数中,我们正在创建express实例并将其设置为_app 。 我们还调用了配置方法。 此方法将处理快速配置代码。

public configureMiddleware() {
        // Setup body parsing - required for POST requests
        this._app.use(bodyParser.json());
        this._app.use(bodyParser.urlencoded({ extended: true }));
 
        // Setup CORS
        this.app.use(function (req, res, next) {
            res.setHeader("Access-Control-Allow-Origin", "*");
            res.setHeader("Access-Control-Allow-Credentials", "true");
            res.setHeader("Access-Control-Allow-Methods", "GET,HEAD,OPTIONS,POST,PUT");
            res.setHeader("Access-Control-Allow-Headers", "Access-Control-Allow-Origin,Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers,Authorization");
            next();
        });
    }

In addition to this we also set up two getters to get our express and server instances.

除此之外,我们还设置了两个getter以获取我们的express和服务器实例。

/**
     * Getters
     */
 
    get app(): Express {
        return this._app;
    }
 
    get server(): http.Server {
        return this._server;
    }

Finally we have the start method that starts our express server.

最后,我们有了启动快速服务器的启动方法。

/**
     * Public Methods
     */
    public start() {
        // Start the server instance
        this._server = this._app.listen(this._app.get("port"), () => {
            console.log("Server is running on port " + this._app.get("port"));
        });
    }

So far so good . Next, we know that we would like to do CRUD (Create, Read, Update, Delete) operations with our API.

到目前为止一切顺利。 接下来,我们知道我们想使用我们的API进行CRUD(创建,读取,更新,删除)操作。

利用装饰器编写干净的模块化代码 (Leveraging decorators to write clean modular code)

Let’s say we need Books and Authors resources in our API. So normally we would create book routes, book model and a book controller to handle the CRUD operations for books. Then we would repeat the same process for Authors.

假设我们需要API中的书籍和作者资源。 因此,通常我们将创建书籍路径,书籍模型和书籍控制器来处理书籍的CRUD操作。 然后,我们将对Authors重复相同的过程。

But let’s say in the future we add more resources such as Albums, Artists and Movies. We would have to repeat the same process of creating routes, controllers and models for each resource.

但可以说,将来我们会添加更多资源,例如AlbumsArtistsMovies 。 我们将不得不重复为每个资源创建路线,控制器和模型的相同过程。

But what if we could avoid repeating this code logic? That’s exactly what we are going to do with decorators.

但是,如果我们可以避免重复此代码逻辑,该怎么办? 这正是我们要与装饰者一起做的事情。

You can find the complete code for this section in the following link:

您可以在以下链接中找到此部分的完整代码:

https://github.com/Shadid12/entity-framework/tree/setting_routes_02

https://github.com/Shadid12/entity-framework/tree/setting_routes_02

Let’s revisit our app.ts file.

让我们重新访问我们的app.ts文件。

import APIServer from "./APIServer";
import { JsonDB } from "node-json-db";
import { Config } from "node-json-db/dist/lib/JsonDBConfig"
import Author from "./entities/Author";
 
export const apiServer = new APIServer();
 
export const db = new JsonDB(new Config("DB", true, true, '/'));
 
apiServer.start();

As you can see I have added a couple extra things in this file. First of all I added JsonDB as our database, which is what I will be using (however, feel free to use a database of your choice).

如您所见,我在此文件中添加了一些额外的内容。 首先,我将JsonDB添加为我们要使用的数据库(但是,请随意使用您选择的数据库)。

Next we create the file ./entities/Author.ts. Then we import Author class into our app.ts. Let’s take a look at the Author class.

接下来,我们创建文件./entities/Author.ts 。 然后我们将Author类导入到我们的app.ts 。 让我们看一下Author类。

import BaseEntity from './BaseEntity';
import { entity } from '../decorators/entity';
 
@entity("author")
export default class Author extends BaseEntity {
 
    id: string;
 
    firstName: string;
 
    lastName: string;
 
    email: string;
 
    alias: string;
}

As you can see our class extends a BaseEntity class. Our BaseEntity class doesn’t really have any functionality yet. However, we will implement it as we go. Here’s what it looks like:

如您所见,我们的类扩展了BaseEntity类。 我们的BaseEntity类实际上还没有任何功能。 但是,我们将继续执行它。 看起来是这样的:

export default class BaseEntity implements IEntity {
 
    getPersistenceObject(): any {
        // TODO: Implement this
    }
}

Now let’s go back to the Author class and you will notice that we have defined a class decorator called entity. Let’s take a look into the decorators/entity.ts file and see what’s happening.

现在让我们回到Author类,您会注意到我们已经定义了一个称为Entity的类装饰器。 让我们看一下decorators/entity.ts文件,看看发生了什么。

import "reflect-metadata";
 
export function entity(name: string) {
    return function (constructor: Function) {
        Reflect.defineMetadata("entity:name", name, constructor);
    }
}

We are creating a simple decorator factory here. Inside the function we are using the defineMetadata API from TypeScript to pass in the resource name. We will be using this metadata to dynamically generate routes for all of our resources.

我们在这里创建一个简单的装饰工厂。 在函数内部,我们使用TypeScript中的defineMetadata API来传递资源名称。 我们将使用此元数据为所有资源动态生成路由。

Let’s go back to our apiServer class and add another method called addEntity.

让我们回到我们的apiServer类,并添加另一个名为addEntity方法。

import * as http from "http";
import express, { Express } from "express";
import bodyParser from "body-parser";
import BaseEntity from "./entities/BaseEntity";
import EntityRouter from "./EntityRouter";
 
export default class APIServer {
    … 
    public addEntity(clazz) {
       const name = Reflect.getMetadata("entity:name", clazz);
       let entityRouter = new EntityRouter(name, clazz);
       this._app.use(`/${name}`, entityRouter.router);
    }
}

In this method we are accepting a class as a parameter. We are then using the Reflect.getMetadata(); to get the resource name. This is why we created the @entity decorator. We pass in this name to our EntityRouter class to create the dynamic routes.

在此方法中,我们接受类作为参数。 然后,我们使用Reflect.getMetadata(); 获取资源名称。 这就是为什么我们创建@entity装饰器的原因。 我们将此名称传递给EntityRouter类以创建动态路由。

Let’s take a look at the EntittyRouter class.

让我们看一下EntittyRouter类。

import BaseEntity, { EntityTypeInstance } from "./entities/BaseEntity";
import express, { Router, Request, Response } from "express";
import { db } from "./app";
 
export default class EntityRouter {
 
    private _router: Router;
 
    get router(): Router {
        return this._router;
    }
 
    constructor(public name: string, private classRef: EntityTypeInstance) {
        this._router = express.Router();
        this.addEntityRoutes();
    }
 
    addEntityRoutes() {
        // CREATE
        this._router.post('/', (req, res) => {
            this.createEntity(req, res);
        });
 
        // READ all
        this._router.get('/', (req, res) => {
            this.fetchAllEntities(req, res);
        });
 
        // READ one
        this._router.get('/:id', (req, res) => {
            this.fetchEntity(req, res);
        });
 
        // UPDATE
        this._router.put('/:id', (req, res) => {
            this.updateEntity(req, res);
        });
 
        // DELETE
        this._router.delete('/:id', (req, res) => {
            this.deleteEntity(req, res);
        });
    }
 
    private fetchAllEntities(req: Request, res: Response) {
        let data = {}
        data = db.getData(`/${this.name}`);
        res.json(data);
    }
 
    private fetchEntity(req: Request, res: Response) {
        let data = {}
        data = db.getData(`/${this.name}/${req.params.id}`);
        res.json(data);
    }
 
    private createEntity(req: Request, res: Response) {
        // TODO: Implement
    }
 
    private updateEntity(req: Request, res: Response) {
        // TODO: Implement
    }
 
    private deleteEntity(req: Request, res: Response) {
        db.delete(`/${this.name}/${req.params.id}`);
        res.json({});
    }
 
}

Here we have a constructor that is accepting the resource name and a class reference for our resource. We have also setup the routes/CRUD operations to be dynamic. Now finally in our app.ts we have to add this resource in.

在这里,我们有一个接受资源名称的构造函数和资源的类引用。 我们还将路由/ CRUD操作设置为动态的。 现在终于在我们的app.ts我们必须添加此资源。

...
import Author from "./entities/Author";
 
export const apiServer = new APIServer();
 
export const db = new JsonDB(new Config("DB", true, true, '/'));
+apiServer.addEntity(Author);
 
apiServer.start();

Now let’s create some test data to test our API. Create a file called ./DB.json and add the following records.

现在,让我们创建一些测试数据来测试我们的API。 创建一个名为./DB.json的文件,并添加以下记录。

{
    "author": {
        "8a91a0f4-39c9-4d54-aff0-b636a667c560": {
            "id": "8a91a0f4-39c9-4d54-aff0-b636a667c560",
            "firstName": "Steven",
            "lastName": "Hawking",
            "email": "[email protected]",
            "alias": "sh"
        },
        "18583e4a-1e4e-487b-9004-13b195ec42bc": {
            "id": "18583e4a-1e4e-487b-9004-13b195ec42bc",
            "firstName": "Steven",
            "lastName": "Hawking",
            "email": "[email protected]",
            "alias": "sh"
        }
    },
    "book": {
        "349a7c69-75ac-45e1-b423-88172829df60": {
            "id": "349a7c69-75ac-45e1-b423-88172829df60",
            "title": "A brief History of Time",
            "author": "Steven Hawking",
            "authorId": "2020202020"
        }
    }
}

Now if you do a GET request http://localhost:3000/author you will get a list of authors. Also doing GET http://localhost:3000/author/18583e4a-1e4e-487b-9004-13b195ec42bc should give you the specific user.

现在,如果您执行GET请求http://localhost:3000/author您将获得一个作者列表。 同样执行GET http://localhost:3000/author/18583e4a-1e4e-487b-9004-13b195ec42bc应该可以为您提供特定的用户。

Now if we add another resource, for instance a book resource, all we have to do is define our book entity and add it to api.ts. Let’s do that now.

现在,如果我们添加其他资源(例如图书资源),我们要做的就是定义我们的图书实体并将其添加到api.ts 。 现在开始吧。

import APIServer from "./APIServer";
import { JsonDB } from "node-json-db";
import { Config } from "node-json-db/dist/lib/JsonDBConfig"
import Author from "./entities/Author";
+import Book from "./entities/Book";
 
export const apiServer = new APIServer();
 
export const db = new JsonDB(new Config("DB", true, true, '/'));
apiServer.addEntity(Author);
+apiServer.addEntity(Book);
 
apiServer.start();

And make a Book class:

并制作一本书课:

import BaseEntity from './BaseEntity';
import { entity } from '../decorators/entity';
 
@entity("book")
export default class Book extends BaseEntity {
    id: string;
 
    title: string;
 
    author: string;
 
    authorId: string;
}

And that’s it. Your Book resource now will work as expected.

就是这样。 您的图书资源现在可以正常使用了。

We have a read operation for both of our resources. Now let’s implement the Create, Update and Delete as well. Let’s go back to our Author class and couple new decorators.

我们对这两个资源都有读取操作。 现在,让我们也实现创建,更新和删除。 让我们回到Author类,并添加几个装饰器。

import BaseEntity from './BaseEntity';
import { entity, id, persist } from '../decorators/entity';
 
@entity("author")
export default class Author extends BaseEntity {
 
    @id
    id: string;
 
    @persist
    firstName: string;
 
    @persist
    lastName: string;
 
    @persist
    email: string;
 
    @persist
    alias: string;
}

In our entity.ts we will add these new decorators like below.

在我们的entity.ts我们将像下面一样添加这些新的装饰器。

export function persist(target: any, propertyKey: string) {
    let objectProperties: string[] = Reflect.getMetadata("entity:properties", target) || [];
    if (!objectProperties.includes(propertyKey)) {
        objectProperties.push(propertyKey);
        Reflect.defineMetadata("entity:properties", objectProperties, target);
    }
}
 
export function id(target: any, propertyKey: string) {
    Reflect.defineMetadata("entity:id", propertyKey, target);
}

The persists decorator makes sure a certain key is available in the entity in order to save. The id decorator will be used for the uniqueId key for that object. Let’s go back to our EntityRouter class and populate the createEntity function.

persists装饰器确保实体中某个键可用以便保存。 id装饰器将用于该对象的uniqueId键。 让我们回到EntityRouter类,并填充createEntity函数。

private createEntity(req: Request, res: Response) {
        let newEntity = EntityFactory.fromPersistenceObject(req.body, this.classRef);
 
        const idProperty = Reflect.getMetadata("entity:id", newEntity);
        newEntity[idProperty] = uuid.v4();
        db.push(`/${this.name}/${newEntity[idProperty]}`, newEntity.getPersistenceObject());
        res.status(200).json(newEntity);
    }

In the code above we are creating a new entity instance with method fromPersistenceObject. Here’s the implementation for EntityFactory.

在上面的代码中,我们正在使用fromPersistenceObject方法创建一个新的实体实例。 这是EntityFactory的实现。

export class EntityFactory {
 
    static fromPersistenceObject(obj: Object, type: EntityTypeInstance): T {
        let output = new type();
        const persistedProperties: string[] = Reflect.getMetadata("entity:properties", output) || [];
        const idProperty = Reflect.getMetadata("entity:id", output);
        const props = Object.keys(obj);
        for (const prop of props) {
            if (persistedProperties.includes(prop) || prop == idProperty) {
                output[prop] = obj[prop];
            } else {
                throw new Error("Property not defined in class.");
            }
        }
        return output;
    }
}

In this method we are accepting request object and entity type as parameters. We are making sure that request object properties exist in our class definition. If a property doesn't exist it will throw an error. Now let’s implement the getPersistenceObject in our BaseEntity class. This method returns an object with the properties and value of an entity.

在这种方法中,我们接受请求对象和实体类型作为参数。 我们确保在类定义中存在请求对象属性。 如果属性不存在,则会引发错误。 现在让我们来实现getPersistenceObject我们BaseEntity类。 此方法返回具有实体属性和值的对象。

export default class BaseEntity implements IEntity {
 
    getPersistenceObject(): any {
        let output = {};
        const persistedProperties = Reflect.getMetadata("entity:properties", this);
        const idProperty = Reflect.getMetadata("entity:id", this);
        output[idProperty] = this[idProperty];
        for (const prop of persistedProperties) {
            if (this[prop]) {
                output[prop] = this[prop];
            }
        }
        return output;
    }
 
}

Let’s also implement the update and delete methods for our entity. Let’s add the following methods to our EntityRouter class.

让我们还为我们的实体实现updatedelete方法。 让我们将以下方法添加到EntityRouter类中。

private updateEntity(req: Request, res: Response) {
        // Does entity exist with ID
        let data = {}
        try {
            data = db.getData(`/${this.name}/${req.params.id}`);
        } catch (err) {
            res.status(404).json({ error: "Object does not exist" });
            return;
        }
 
        // Update Object with new values
        let updatedData = req.body;
        let updatedObj = EntityFactory.fromPersistenceObject(data, this.classRef);
        const propKeys = Object.keys(updatedData);
        for (const propKey of propKeys) {
            updatedObj[propKey] = updatedData[propKey];
        }
 
        // Save and Return data
        db.push(`/${this.name}/${req.params.id}`, updatedData, false);
        data = db.getData(`/${this.name}/${req.params.id}`);
        res.json(data);
    }
 
    private deleteEntity(req: Request, res: Response) {
        db.delete(`/${this.name}/${req.params.id}`);
        res.json({});
    }

That’s all. Our CRUD now should be fully operational. Feel free to checkout the code for this project up to this point in the following link.

就这样。 我们的CRUD现在应该可以全面运行了。 在下面的链接中,请随时检查该项目的代码。

https://github.com/Shadid12/entity-framework/tree/setting_routes_02

https://github.com/Shadid12/entity-framework/tree/setting_routes_02

Feel free to create a couple of records and observe them in the database. For our sample app it is just getting stored in the DB.json file.

随意创建几个记录并在数据库中观察它们。 对于我们的示例应用程序,它只是存储在DB.json文件中。

跨领域关注 (Cross cutting concerns)

When architecting enterprise applications we often break them into multiple layers. For example if we are building a REST API we can have the following layers

在设计企业应用程序时,我们经常将它们分为多个层次。 例如,如果我们正在构建REST API,则可以具有以下层

  1. Service Layer: This is responsible for communicating between our API and different client side applications.

    服务层 :负责在我们的API和不同的客户端应用程序之间进行通信。

  2. Business Rules and Logic Layer: This is where we perform our business rules and logic based on the incoming request data.

    业务规则和逻辑层:在此处,我们根据传入的请求数据执行业务规则和逻辑。

  3. Data Access and Storage: This layer deals with data access, reading and writing data in our database.

    数据访问和存储:该层处理数据访问,读取和写入数据库中的数据。

We would like to keep these layers separate, but there will be some code logic that will apply to all of them. For example we would want to utilize authentication logic, validation logic or logging logic among every layer of our code.

我们希望将这些层分开,但是会有一些代码逻辑适用于所有这些层。 例如,我们希望在代码的每一层之间使用身份验证逻辑,验证逻辑或日志记录逻辑。

In software design these are sometimes called horizontal dependencies. Here’s an image to visualize this concept.

在软件设计中,这些有时称为水平依存关系。 这是形象化此概念的图像。

This dependency problem is known as Cross Cutting Concern. We can apply decorators to solve these types of problems. The paradigm of using decorators to solve these types of problems is called aspect oriented programming (AOP).

这个依赖问题被称为Cross Cross Concern 。 我们可以应用装饰器来解决这些类型的问题。 使用装饰器解决这类问题的范例称为面向方面的编程(AOP)。

AOP is a programming paradigm that focuses on increasing modularity by separating cross cutting concerns.

AOP是一种编程范例,其重点是通过分离横切关注点来提高模块化。

面向方面的编程和装饰器 (Aspect Oriented Programming and decorators)

In our application we want to apply AOP paradigm and separate cross cutting logic such as validation and authorization. Let’s see how we can implement validation logic to our code using decorators. The complete code for this part is available in the following github link.

在我们的应用程序中,我们希望应用AOP范式和单独的交叉逻辑,例如验证和授权。 让我们看看如何使用装饰器对代码实施验证逻辑。 以下github链接中提供了此部分的完整代码。

https://github.com/Shadid12/entity-framework/tree/validators_03

https://github.com/Shadid12/entity-framework/tree/validators_03

Let’s go over the data validation code. We want to make sure that our Actor model has a proper email and certain fields are not empty. Let’s create these two additional decorator functions required and isEmail.

让我们看一下数据验证代码。 我们要确保我们的Actor模型具有正确的电子邮件,并且某些字段不为空。 让我们创建required这两个附加装饰器函数和isEmail

In our decorator folder we create a new file called validators.ts.

在装饰器文件夹中,我们创建一个名为validators.ts的新文件。

export function required(target: any, propertyKey: string) {
    addValidation(target, propertyKey, requiredValidatior);
}
 
export function isEmail(target: any, propertyKey: string) {
    addValidation(target, propertyKey, emailValidatior);
}

We created the decorator functions isEmail() and required(). We are passing in the target object and the propertyKey in these functions. Both of these functions then call the addValidation() with target, propertyKey and validation function.

我们创建了装饰器函数isEmail()required() 。 我们在这些函数中传入目标对象和propertyKey。 然后,这两个函数都使用target,propertyKey和addValidation()函数调用addValidation()

requiredValidatior() is the function that is responsible for encapsulating the code that makes sure that property must be included and emailValidatior() contains the logic to check if the email is valid.

requiredValidatior()是负责封装代码的函数,该代码可确保必须包含属性,并且emailValidatior()包含检查电子邮件是否有效的逻辑。

Here’s how we implement these functions:

这是我们实现这些功能的方式:

import validator from "validator";
 
….
 
function requiredValidatior(target: any, propertyKey: string): string | void {
    let value = target[propertyKey];
    if (value) {
        return;
    }
    return `Property ${propertyKey} is required.`
}
 
function emailValidatior(target: any, propertyKey: string): string | void {
    let value = target[propertyKey];
    if (value == null) {
        return;
    }
    const isValid = validator.isEmail(value);
    if (!isValid) {
        return `Property ${propertyKey} must be a valid email.`
    }
    return;
}
Side Note: I am using a npm package called validator to check if email is valid.
旁注:我正在使用一个称为验证程序的npm软件包来检查电子邮件是否有效。

Finally we have our addValidation() method. Let’s dissect this method:

最后,我们有了addValidation()方法。 让我们剖析此方法:

function addValidation(target: any, propertyKey: string, validator: ValidationFunction, validationOptions?: any) {
    // Make sure we have the list of all properties for the object
    let objectProperties: string[] = Reflect.getMetadata("validation:properties", target) || [];
    if (!objectProperties.includes(propertyKey)) {
        objectProperties.push(propertyKey);
        Reflect.defineMetadata("validation:properties", objectProperties, target);
    }
 
    // Make sure we capture validation rule
    let validators: ValidationRule[] = Reflect.getMetadata("validation:rules", target, propertyKey) || [];
    let validationRule = {
        validator: validator,
        validationOptions: validationOptions
    };
    validators.push(validationRule);
    Reflect.defineMetadata("validation:rules", validators, target, propertyKey);
}

This method takes in target, propertyKey, and validation function as params. First it checks if those properties exist in the object. Then it adds all the validators into an array. A single property can have multiple validators, that’s why we have this array. Finally it defines these metadata with reflection

此方法将target,propertyKey和验证函数作为参数。 首先,它检查对象中是否存在那些属性。 然后将所有验证器添加到数组中。 一个属性可以有多个验证器,这就是我们拥有此数组的原因。 最后,它通过反射定义了这些元数据

Now if we go back to our Author class and add these decorators to certain properties we will be able to capture those validation behaviors from any part of our code.

现在,如果我们返回到Author类,并将这些装饰器添加到某些属性中,我们将能够从代码的任何部分捕获这些验证行为。

For this example let’s take a look at the Author class again.

对于此示例,让我们再次看一下Author类。

@entity("author")
export default class Author extends BaseEntity {
 
    @id
    id: string;
 
    @persist
    @required
    firstName: string;
 
    @persist
    lastName: string;
 
    @persist
    @isEmail
    @required
    email: string;
 
    @persist
    alias: string;
}

As you can see in the code above some of our properties now have additional decorators for data validation.

正如您在上面的代码中看到的,我们的某些属性现在具有用于数据验证的附加装饰器。

Now let’s say we want to validate our data from the service layer. In our little application the EntityRouter class is the barebones service layer.

现在,我们要验证服务层中的数据。 在我们的小应用程序中, EntityRouter类是准系统服务层。

Now let’s say we want to validate data before we save it. We can achieve that through the following code:

现在,我们要在保存数据之前先对其进行验证。 我们可以通过以下代码实现:

EntityRoutes.ts

EntityRoutes.ts

private createEntity(req: Request, res: Response) {
        let newEntity = EntityFactory.fromPersistenceObject(req.body, this.classRef);
        
        let errorMap = validate(newEntity);
        if (Object.keys(errorMap).length > 0) {
            const output = { errors: errorMap };
            res.status(400).json(output);
            return;
        }
 
        const idProperty = Reflect.getMetadata("entity:id", newEntity);
        newEntity[idProperty] = uuid.v4();
        db.push(`/${this.name}/${newEntity[idProperty]}`, newEntity.getPersistenceObject());
        res.status(200).json(newEntity);
    }

Validators.ts

Validators.ts

export function validate(object: any) {
    const keys = Reflect.getMetadata("validation:properties", object) as string[];
    let errorMap = {};
 
    if (!keys || !Array.isArray(keys)) {
        return errorMap;
    }
 
    for (const key of keys) {
        const rules: ValidationRule[] = Reflect.getMetadata("validation:rules", object, key) as ValidationRule[];
        if (!Array.isArray(rules)) {
            continue;
        }
        for (const rule of rules) {
            const errorMessage = rule.validator(object, key, rule.validationOptions);
            if (errorMessage) {
                errorMap[key] = errorMap[key] || [];
                errorMap[key].push(errorMessage);
            }
        }
    }
 
    return errorMap;
}

The validate() function itself invokes all the validator functions. You can see the benefits of using these decorators now. We have modularized our validation logic completely. We have complete separation of concerns. Under the hood, the Angular framework also uses decorators similar to our validators.

validate()函数本身会调用所有验证函数。 您现在可以看到使用这些装饰器的好处。 我们已经完全将验证逻辑模块化。 我们将关注点完全分开。 在幕后,Angular框架还使用类似于我们的验证器的装饰器。

We can also use decorators to implement authorization/logging logic and handle cross cutting concerns. I have implemented a simple authentication logging to the project. If you are interested you can take a look at the code in the following link.

我们还可以使用装饰器来实现授权/记录逻辑并处理交叉问题。 我已经对该项目实施了简单的身份验证日志记录。 如果您有兴趣,可以查看以下链接中的代码。

https://github.com/Shadid12/entity-framework

https://github.com/Shadid12/entity-framework

这就是包装! (And that's a wrap!)

I hope this article provided you with a good understanding of decorators.

希望本文能使您对装饰者有一个很好的了解。

We talked about how decorators can help us cut down repeated code. We looked at examples of defining metadata and built our own decorators similar to those used in popular frameworks such as Angular. We also looked at how to address cross cutting concerns in larger applications.

我们讨论了装饰器如何帮助我们减少重复的代码。 我们看了定义元数据的示例,并构建了自己的装饰器,类似于流行的框架(例如Angular)中使用的装饰器。 我们还研究了如何解决大型应用程序中的交叉问题。

I hope you enjoyed this article, feel free to leave your valuable feedback. Until next time

希望您喜欢这篇文章,随时留下宝贵的反馈意见。 直到下次time

翻译自: https://www.freecodecamp.org/news/what-are-typescript-decorators-how-to-leverage-them-to-write-modular-code-and-solve-cross-cutting-concerns/

typescript 模块

你可能感兴趣的:(python,java,vue,设计模式,js,ViewUI)