#api #typescript #interface
#API #typescript #интерфейс
Вопрос:
Я использую выборку внешнего API, а затем отображаю данные.
Чтобы быть гибким, я хочу полностью отделить API от своего кода и хочу работать с самостоятельно определенными структурами данных.
Вот краткий графический обзор:
Чтобы сделать задачу немного более конкретной, приведу пример:
Давайте предположим, что данные касаются людей:
API v1.0 выдает {"name": "John"}
, тогда как API v1.1 {"pName": "John"}
выдает.
Чтобы это незначительное изменение не нарушило мой код, я хочу иметь два внутренних интерфейса: один для синтаксического анализатора (который анализирует ответ API) и один для структуры самих данных:
interface IPersonDataStructure {
name : string;
}
interface IPersonDataParser {
parse(input: string) : IPersonDataStructure;
}
Затем я хочу иметь класс, который объединяет анализатор со структурой данных:
// This class uses any parser which implements IPersonDataParser
// And uses IPersonDataStructure
class Person {
}
И вот тут я застрял! Я не знаю, как объединить их вместе!
Мне не нравится идея экземпляра для каждого экземпляра пользователя:
let aPerson = new Person(new Parser(data))
Потому что анализатор должен быть без состояния (например, как функция).
Проблема в том, что TypeScript не позволял мне делать это с классами:
class Parser implements IPersonDataParser {
static public function parse(data : string) : IPersonDataStructure {
return {...}
}
}
class Person {
private _data : IPersonDataStructure;
constructor(data : string, parser : IPersonDataParser) {
this._data = parser.parse(data)
}
}
Обратные вызовы — это вариант, но только если я могу проверить их подпись.
Например, это неправильно проверяется:
type PersonDataParser = (data : string) => IPersonDataStructure;
// Whoops.. argument is missing!
let aParser = () => {
return {...}
}
let aPerson = new Person('data', aParser)
Любые предложения о том, как решить эту проблему?
Комментарии:
1. в чем именно заключается проблема, которую вы пытаетесь решить здесь?
2. Я хочу реализовать описанную функциональность чистым способом
3. На самом деле это не тот вопрос, который обрабатывает stackoverflow. Может быть, попробовать веб-сайт code review stack exchange? Здесь он будет генерировать множество решений, основанных на мнениях. 🙂
4. это, как говорится.. Я бы сказал, что это довольно чисто: <a rel=»nofollow noreferrer noopener» href=»https://www.typescriptlang.org/play/index.html#src=interface Parser {
(data: string): T
}interface PersonData { }
class Person {
constructor(private data: PersonData) { }}
let aParser: Parser = (data) => {
return data; // actual implementation here
}let aPerson = new Person(aParser(‘data’));» rel=»nofollow noreferrer»> typescriptlang.org/play /…
Ответ №1:
Во-первых, у вас может быть статический метод, и он должен удовлетворять интерфейсу — все с использованием вывода типов и структурных типов, например:
interface IPersonDataStructure {
name : string;
}
interface IPersonDataParser {
parse(input: string) : IPersonDataStructure;
}
class Parser {
public static parse(data : string) : IPersonDataStructure {
return { name: 'Steve' };
}
}
class Person {
private _data : IPersonDataStructure;
constructor(data : string, parser : IPersonDataParser) {
this._data = parser.parse(data)
}
}
let person = new Person('', Parser);
Я бы, вероятно, предпочел дизайн Person
, в котором просто представлялся человек, и для его создания не нужно было использовать картограф. Больше похоже на это…
interface IPersonDataStructure {
name : string;
}
class Person {
constructor(private data : IPersonDataStructure) {
}
}
class PersonMapper {
public static map(data: string): Person {
return new Person({
name: 'Steve'
});
}
}
let person = PersonMapper.map('...');
Если ваш номер версии является частью данных, вы можете использовать его для определения правильного сопоставления.
Ответ №2:
Почему бы не создать адаптер, который будет проверять, какое из двух свойств было возвращено из API?
interface ApiResponse {
name?: string;
pName?: string;
}
class Person {
public name: string;
constructor (name: string) {
this.name = name;
}
}
class ApiResponseAdapter {
private getName(response: ApiResponse): string {
if (response.pName) return pName;
if (response.name) return name;
// if neither are set, return null
return null;
}
public adapt(response: ApiResponse): Person {
let name = this.getName(response);
if (name === null) {
throw new Error("Invalid name for response: " JSON.stringify(response));
}
return new Person(name);
}
}
В качестве альтернативы у вас может быть базовый ApiResponse
интерфейс, который имеет реализации для обработки поведения:
interface ApiResponse {
name: string;
}
class Api_V1_0_Response implements ApiResponse {
public name: string;
constructor (json: any) {
this.name = json["name"];
}
}
class Api_V1_1_Response implements ApiResponse {
public name: string;
constructor (json: any) {
this.name = json["pName"];
}
}
class Person {
public name: string;
constructor (name: string) {
this.name = name;
}
}
class ApiResponseAdapter {
public adapt(response: ApiResponse): Person {
return new Person(
response.name
);
}
}
Или сделайте еще один шаг и создайте абстрактный BaseApiResponse
класс, который расширен двумя другими:
interface ApiResponse {
name: string;
}
abstract class BaseApiResponse implements ApiResponse {
public name: string;
constructor (nameKey: string, json: any) {
this.name = json[nameKey];
}
}
class Api_V1_0_Response extends BaseApiResponse {
constructor (json: any) {
super("name", json);
}
}
class Api_V1_1_Response extends BaseApiResponse {
constructor (json: any) {
super("pName", json);
}
}
class Person {
public name: string;
constructor (name: string) {
this.name = name;
}
}
class ApiResponseAdapter {
public adapt(response: ApiResponse): Person {
return new Person(
response.name
);
}
}