根据类中替换的扩展方法更改返回值

时间:2019-02-03 16:12:22

标签: typescript

我有一个FileHandler类,然后是一个扩展原始类的FileOrNullHandler类。

问题是FileHandlerFileOrNullHandler的继承方法被卡在原始类的创作返回类型中。

export class FileHandler {
    static async readFileFromFileQuery (fq: FileQuery): Promise<File> {
        const { path, encoding, flag } = FileQueryHandler.make(fq);
        const content = await promisify(fs.readFile)(path, { encoding, flag })
        return { path, encoding, flag, content };
    }
    static async readFile (a: Path | FileQuery, b?: Omit<FileQuery, 'path'>): Promise<File> {
        if (typeof a === 'string') a = FileQueryHandler.getFromPath(a, b);
        return this.readFileFromFileQuery(a);
    }
    static async readFiles (a: (Path | FileQuery)[] | Directory, b?: Omit<FileQuery, 'path'>): Promise<File[]> {        
        if (a instanceof Array) return Promise.all(a.map(p => this.readFile(p, b)));
        return this.readFiles(PathHandler.getFromDirectory(a), b);
    }
    static async readFilesFromDirectory(a: Path | FileQuery, b?: Omit<FileQuery, 'path'>): Promise<File[]> {
        const ps = await DirectoryHandler.readDirectory(a);    
        if (typeof a === 'string') return await (this).readFiles(ps, b);
        return await this.readFiles(ps, a);
    }
}

export class FileOrNullHandler extends FileHandler {
    static async readFileFromFileQuery (fq: FileQuery): Promise<File | null> {
        return orNull(() => FileHandler.readFileFromFileQuery(fq));
    }
}

我在这里看到了两种获取正确类型的选项之一。

  1. 根据this设置原始方法的相对返回类型。 (可能不可能)
  2. 覆盖ReturnType中的方法FileOrNullHandler

1 个答案:

答案 0 :(得分:1)

OOP的一般租户是派生类应该能够替换基类。在这种情况下,派生类无法替换基类,因为派生类具有基类客户不希望期望的返回类型(null)。

那只蜜蜂说我们可以很接近您想要的东西。

首先,我不会使用仅带有静态方法的类,而是会创建具有实例方法的类,并声明该实例类型的const并将其导出以供人们用作单例。

第二,我将在通用的基类中移动通用的功能,其中readFileFromFileQuery方法是抽象的,而类是通用的。这样一来,我们可以插入返回File的版本或返回File | null的版本,而无需违反Typescript(和OOP)一致性规则。

最终的解决方案看起来像这样(我填写了一些类型,不确定它们的实际定义是什么,但是我添加了使代码无错误所需的最低限度要求):

abstract class _FileHandlerBase<T> {
  abstract readFileFromFileQuery(fq: FileQuery): Promise<T>;
  async readFile(a: Path | FileQuery, b?: Omit<FileQuery, 'path'>): Promise<T> {
    if (typeof a === 'string') a = FileQueryHandler.getFromPath(a, b);
    return this.readFileFromFileQuery(a);
  }
  async readFiles(a: (Path | FileQuery)[] | Directory, b?: Omit<FileQuery, 'path'>): Promise<T[]> {
    if (a instanceof Array) return Promise.all(a.map(p => this.readFile(p, b)));
    return this.readFiles(PathHandler.getFromDirectory(a), b);
  }
  async readFilesFromDirectory(a: Path | FileQuery, b?: Omit<FileQuery, 'path'>): Promise<T[]> {
    const ps = await DirectoryHandler.readDirectory(a);
    if (typeof a === 'string') return await (this).readFiles(ps, b);
    return await this.readFiles(ps, a);
  }
}
export class _FileHandler extends _FileHandlerBase<File> {
  async readFileFromFileQuery(fq: FileQuery): Promise<File> {
    const { path, encoding, flag } = FileQueryHandler.make(fq);
    const content = await promisify(fs.readFile)(path, { encoding, flag })
    return { path, encoding, flag, content };
  }
}
export const FileHandler = new _FileHandler();

export class _FileOrNullHandler extends _FileHandlerBase<File | null> {
  async readFileFromFileQuery(fq: FileQuery): Promise<File | null> {
    return orNull(() => FileHandler.readFileFromFileQuery(fq));
  }
}

export const FileOrNullHandler = new _FileOrNullHandler();

FileHandler.readFileFromFileQuery(null!) // Promise<File>
FileOrNullHandler.readFileFromFileQuery(null!) // Promise<File | null>

FileHandler.readFiles(null!) // Promise<File[]>
FileOrNullHandler.readFiles(null!) // Promise<(File | null)[]>

// Some assumptions
type Path = string;

interface FileQuery {
  path: string, flag?: string, encoding?: string | null
}
export interface File {
  path: string, flag?: string, encoding: string | undefined | null, content: Buffer | string
}
interface Directory {
  path: string, isDir: true
}

declare var FileQueryHandler: {
  make(fq: FileQuery): FileQuery
  getFromPath(s: string, b?: Omit<FileQuery, 'path'>): FileQuery;
}

declare var DirectoryHandler : {
  readDirectory(a: Path | FileQuery) : FileQuery[]
}
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>

function orNull<T>(fn: () => T) {
  try {
    return fn();
  } catch (e) {
    return null;
  }
}
declare const PathHandler: {
  getFromDirectory(d: Directory): FileQuery[];
}