抽象的外部API

时间:2016-10-14 11:19:40

标签: api typescript interface

我正在使用外部API提取,然后显示数据。

为了灵活,我想完全将API与我的代码分离,并希望使用自定义的数据结构。

这里有一个简短的图形概述: enter image description here

为了使这个任务更加具体,这里有一个例子:

我们假设数据是关于人的:

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 {

}

这就是我被困住的地方!我不知道如何将两者结合在一起!

我不喜欢每个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)

有关如何解决此问题的任何建议?

2 个答案:

答案 0 :(得分: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('...');

如果您的版本号是数据的一部分,您可以使用它来确定正确的映射。

答案 1 :(得分:1)

为什么不创建一个适配器来检查从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
        );
    }
}