Абстрактные внешние API

#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
        );
    }
}