Можно ли написать дженерики таким образом, чтобы обеспечить безопасность типов? - PullRequest
1 голос
/ 07 января 2020

Допустим, у меня есть несколько классов, которые имеют похожий метод. В этом случае набор сервисов, имеющих метод saveModel:

public async saveModel(newModel: IModel): Promise<IModel> {
    return await newModel.save();
}

Общий метод c, который я создал, выглядит следующим образом:

public async saveModel<P extends Document>(newModel: P): Promise<P> {
    return await newModel.save();
}

Все мои сервисы ( те, которые используют этот метод) расширяют класс, который содержит этот обобщенный метод c, поэтому теперь для любой из этих служб я могу вызвать answerService.saveModel(newAnswer), и он сохранит ответ. Все было замечательно, пока я не понял, что могу также поместить туда объект типа IQuestion. И объект типа IDinosaur, если я хотел, до тех пор, пока он расширяет интерфейс документа Mon goose.

Есть ли какой-либо способ для меня, чтобы заставить определенные интерфейсы использоваться в каждой службе? Как мне убедиться, что answerService сохраняет только объекты с типом IAnswer? Нужно ли, чтобы answerService реализовывал интерфейс с такой сигнатурой saveModel, например:

interface IAnswerService {
    saveModel(newModel: IAnswer): Promise<IAnswer>;
}

Это скелет того, как выглядит все это:

EntityService.ts

import { Document } from 'mongoose';
export class EntityService implements IEntityService {
  public async saveModel<P extends Document>(newModel: P): Promise<P> {
    try {
      const savedModel = await newModel.save();
      return savedModel;
    } catch (err) {
      console.log(err);
      return null;
    }
  }
}

answer.service.ts

import { Answer, IAnswer } from '@models/answer.model';
import { EntityService } from '@classes/EntityService';

class AnswerService extends EntityService {
  private static instance: AnswerService;

  private constructor() {
    super();
  }

  static getInstance(): AnswerService {
    if (!AnswerService.instance) {
      AnswerService.instance = new AnswerService();
    }

    return AnswerService.instance;
  }
}

const answerService = AnswerService.getInstance();
export default answerService;

answer.model.ts

import mongoose, { Schema, Document, Model } from 'mongoose';

export interface IAnswer extends Document {
  answerText: string;
  posterID: string;
}

const AnswerSchema: Schema = new Schema({
  answerText: {type: String, required: true},
  posterID: {type: String, required: true}
}, {
  minimize: false
});

export const Answer: Model<IAnswer> = mongoose.model<IAnswer>('Answer', AnswerSchema);

Все остальные интерфейсы наследуются от того же, что и IAnswer (Модель, Схема, Документ ).

И использование службы ответов в целом выглядит следующим образом:

import answerService from 'answer.service';
import { Answer } from 'answer.model';

const answer = new Answer({
    answerText: 'Stuff',
    posterID: '123456789'
})

answerService.saveModel(answer);

1 Ответ

2 голосов
/ 07 января 2020

Насколько я могу судить, вы хотите, чтобы каждый подкласс EntityService обрабатывал определенный подтип Document. Если это так, то вы не хотите, чтобы saveModel() было обобщенным c в P extends Document; Вы хотите, чтобы весь класс EntityService был обобщенным c в P. И каждый подкласс EntityService должен указывать этот типовой параметр generi c. Например:

// P moved to EntityService from saveModel    
export class EntityService<P extends Document> {
  public async saveModel(newModel: P): Promise<P> {
    try {
      const savedModel = await newModel.save();
      return savedModel;
    } catch (err) {
      console.log(err);
      return null!;
    }
  }
}

// subclass sets specific value for P   
class AnswerService extends EntityService<IAnswer> {
  private static instance: AnswerService;   
  private constructor() {
    super();
  }    
}

Затем, если у вас есть answer типа IAnswer и document, о которых известно, что только тип Document, то AnswerService будет разрешать только Вы saveModel(answer), а не saveModel(document):

AnswerService.getInstance().saveModel(answer); // okay
AnswerService.getInstance().saveModel(document); // error, Document is not IAnswer

Хорошо, надеюсь, это поможет; удачи!

площадка Ссылка на код

...