Typescript: Добавить интерфейсы поддержки для определения статических методов

Созданный на 13 янв. 2017  ·  43Комментарии  ·  Источник: microsoft/TypeScript

Версия TypeScript: 2.0.3

Код

interface Foo {
public static myStaticMethod(param1: any);
}

Ожидаемое поведение:
Нет ошибок
Фактическое поведение:
Неподдерживаемый

Question

Самый полезный комментарий

@aluanhaddad Я считаю, что ваш код - это очевидный обходной путь.

interface JsonSerializable {
      public static fromJson(obj: any);
      public toJson(): string;
}

Что вы видите более четко?

Все 43 Комментарий

Чего вы хотите этим достичь? можете подробнее рассказать о сценарии?

Я полагаю, что классы abstract - это то, что вы ищете.

@mhegazy
Пример:

interface JsonSerializable {
      public static fromJson(obj: any);
      public toJson(): string;
}

@rozzzly В этом примере abstract class недействителен.

Я думаю, это было бы здорово! Java добавила эту функцию в последней версии.

@Serginho
Думаю, вам это может показаться интересным:

interface JsonSerializableStatic<C extends new (...args) => JsonSerializable<C>> {
  fromJson(json: string): JsonSerializable<C>;
}

interface JsonSerializable<C extends new (...args) => any> {
  toJson: () => string;
  constructor: C;
}

interface A extends JsonSerializable<typeof A> { }
class A implements JsonSerializable<typeof A> {

  constructor(readonly id: number, readonly name: string) { }
  toJson() { return JSON.stringify(this); }

  static fromJson(json: string): A {
    const obj = JSON.parse(json);
    return new A(obj.id, obj.name);
  }
}

const a = new A(1, 'Charlize');

const json = a.toJson();

const y = A.fromJson(json);
console.info(a, json, y);
console.info(new a.constructor(1, 'Theron'));
const m = new A.prototype.constructor(1, 'Charlize Theron');
console.info(m);

@aluanhaddad Я считаю, что ваш код - это очевидный обходной путь.

interface JsonSerializable {
      public static fromJson(obj: any);
      public toJson(): string;
}

Что вы видите более четко?

@aluanhaddad Я считаю, что ваш код - это очевидный обходной путь.

interface JsonSerializable {
общедоступная статика fromJson (obj: any);
общедоступный toJson (): строка;
}
Что вы видите более четко?

Действительно, но мой вариант - не обходной путь, это оправдание вашего варианта использования. Имея статическую десериализацию и сериализацию экземпляров, реализованную для каждого класса, инкапсулированную и безопасную для типов.
Ваше заявление:

interface JsonSerializable {
     public static fromJson(obj: any);
     public toJson(): string;
}

не имеет значения, поскольку он в основном повторно объявляет интерфейс объекта JSON. Это абсолютно не требует статических методов интерфейса.

Вы теряете концепцию интерфейса. class A implements JsonSerializable должен заставить меня реализовать оба метода. Но на самом деле заставляет меня реализовать:

toJson: () => string;
constructor: new (...args) => JsonSerializableStatic<C>;

Это непонятное решение.

Нет никаких технических причин для запрета определять статические методы в интерфейсах.

@Serginho Я не утверждаю, что ситуация идеальная. Я просто пытался проиллюстрировать, что это можно выразить.

@aluanhaddad Давай! Открой свой разум. Как вы думаете, машинописный текст должен разрешать использование статических методов в интерфейсах? это было реализовано в Java 8 в последней версии, так что я не думаю, что несу чушь.

@Serginho Я не думаю, что это особенно хорошо подходит для TypeScript. Интерфейсы должны определять функциональность, которую предоставляет объект. Эта функция должна быть переопределяемой и взаимозаменяемой (поэтому методы интерфейса виртуальные). Статика - это параллельная концепция динамического поведения / виртуальных методов. Мне кажется, что переплетение этих двух вещей не является правильным с точки зрения дизайна.

Как уже писал @aluanhaddad , в TypeScript уже есть механизм для выражения того, что вы хотите. Большая разница в том, что в этом случае вы рассматриваете класс как объект, который остается логически непротиворечивым. С моей точки зрения, предлагаемый вами подход не особенно хорошо подходит для TypeScript (и разработки JavaScript), поскольку классы - это своего рода хакеры / граждане второго сорта. Преобладающие в настоящее время шаблоны рассчитывают на утиное программирование / программирование форм, а не на жесткие классы.

Думаю, вы также можете открыть свой разум и попробовать разные стили программирования. Мир не начинается и не заканчивается ООП. Вы можете найти программирование с помощью функций, простых объектов и (и в некоторой степени) прототипов приятным и даже лучше. Такой стиль намного больше соответствует тому, для чего изначально был разработан JavaScript. Кстати, я согласен с тем, что JS постепенно переходит к шаблонам в стиле ООП (по крайней мере, на уровне комитета), но это из-за огромного давления людей, которые не чувствуют себя комфортно с разными парадигмами программирования и методами разработки. Для меня это глубоко огорчает и разочаровывает.

Как сказал @gcnew , интерфейс описывает форму отдельного объекта, а не его класс. Но нет причин, по которым мы не можем использовать интерфейс для описания формы самого класса, поскольку классы тоже являются объектами.

import assert = require("assert");

interface JsonSerializableStatic<JsonType, InstanceType extends JsonSerializable<JsonType>> {
    fromJson(obj: JsonType): InstanceType;
}
interface JsonSerializable<JsonType> {
    toJson(): JsonType;
}

interface PointJson { x: number; y: number; }
class Point /*static implements JsonSerializableStatic<PointJson, Point>*/ {
    static fromJson(obj: PointJson): Point {
        return new Point(obj.x, obj.y)
    }

    constructor(readonly x: number, readonly y: number) {}

    toJson(): PointJson {
        return { x: this.x, y: this.y };
    }
}
// Hack for 'static implements'
const _: JsonSerializableStatic<PointJson, Point> = Point;

function testSerialization<JsonType, InstanceType extends JsonSerializable<JsonType>>(cls: JsonSerializableStatic<JsonType, InstanceType>, json: JsonType) {
    const instance: InstanceType = cls.fromJson(json);
    const outJson: JsonType = instance.toJson();
    assert.deepEqual(json, outJson);
}
testSerialization(Point, { x: 1, y: 2 });

Несоблюдение подписи toJson или fromJson приводит к ошибке компиляции. (К сожалению, ошибка находится в const _ а не в методе.)

Вероятно, связанный (поскольку он касается набора статических методов): # 5863.


@aluanhaddad : В вашем примере есть ошибка: JsonSerializable constructor член на самом деле будет ссылаться на свойство экземпляра с именем constructor , которое при вызове с new будет вернуть JsonSerializableStatic . Это будет означать, что (new ((new X()).constructor)).fromJson({}) должно работать. Причина, по которой он компилируется успешно, заключается в том, что interface A extends JsonSerializable<typeof A> объявляет реализацию действительной без фактической проверки. Например, это компилируется без ошибок:

interface I { m(): void; }
interface A extends I { }
// No compile error
class A implements I { em() {} }

@Serginho Не пользователь Java, но не похоже, что язык позволяет вам определять интерфейс для статической стороны класса (это означает, что классы, реализующие интерфейс, должны будут реализовать статический метод для соответствия). Java позволяет вам определять статический метод с телом в интерфейсе, эквивалент TypeScript которого будет:

interface I { ... }
namespace I {
   export function interfaceStaticMethod() {}
}

Что, если бы вы пытались создать завод по производству универсальных продуктов?

interface Factorizable {
  static factory<U>(str: string): U
}

class Foo {
  private data: string[] = []
  bar<T extends Factorizable>(): T[] {
    return this.data.map(T.factory);
  }
}

class Bar implements Factorizable {
  static factory(str: string): Bar {
    // ...
  }
}

// Usage
var x = new Foo();
var y: Bar[] = x.bar();

Я не уверен в синтаксисе interface который я предлагаю здесь, но я знаю, что синтаксис «использования» - это то, чего я пытаюсь достичь. Это шаблон, который я часто использую в Swift (используя protocols ), и я думаю, что он был бы действительно хорош в TypeScript. Хотя я не являюсь разработчиком языка или разработчиком компилятора, я не уверен, соответствует ли он предполагаемому направлению TypeScript или его можно реализовать.

Класс не должен implement интерфейс. вы просто определяете интерфейс, и проверки структурного типа будут кэшировать любые проблемы на сайте использования, например:

interface Factorizable<U> {
    factory(str: string): U
}

class Foo {
  private data: string[] = []
  bar<T>(factory: Factorizable<T>): T[] {
    return this.data.map(factory.factory);
  }
}

class Bar {
  static factory(str: string): Bar {
    // ...
  }
}

// Usage
var x = new Foo();
var y = x.bar(Bar); // Bar[]

@mhegazy - относительно хорошее решение. Спасибо за это! 🙏

Есть еще две вещи, которые доставляют мне дискомфорт (по общему признанию, ни одна из них не ограничивает шоу):

  1. Мы по-прежнему не можем объявить, что Bar явно реализует Factorizable или соответствует ему.

    • На практике я полагаю, что это действительно не проблема. Так как это было бы правдой, если интерфейс Factorizable изменится несовместимым образом, использование x.bar(Bar) начнёт давать ошибку, и тогда вы исправите изменения смещения.
  2. Для меня все еще является когнитивным бременем объявлять тип y в правой части задания.

    • Что еще более странно, этот синтаксис допускает такое поведение: var y: Baz[] = x.bar(Bar) . Очевидно, ошибка, но синтаксис позволяет разработчику чрезмерно ограничить проблему, определив тип возвращаемого значения в двух местах.

Мы по-прежнему не можем заявить, что Bar явно реализует Factorizable или соответствует ему.

Участвуют два типа: 1. функция-конструктор (например, статическая сторона класса) и 2. сторона экземпляра (что появляется при вызове new ). Смешивать эти два в одном типе неправильно. Теоретически у вас могут быть implements и static implements но на практике, как вы отметили, это редко используется, и предложение о реализациях действительно мало что добавляет. Проверка выполняется на сайте использования любым способом, независимо от того, есть ли у вас предложение implements или нет.

Для меня все еще является когнитивным бременем объявлять тип y в правой части задания.

Эти два значения означают разные вещи, var y = x.bar(Bar) объявляет новую переменную y того же типа, что и x.bar(Bar) ; где var y: Bar[] = x.bar(Bar) объявляет новую переменную y с типом Bar[] и проверяет, что тип x.bar(Bar) может быть назначен Bar[] .

Учитывая это, это скорее вопрос стиля. Лично я рекомендую не использовать аннотации типов явно, если в этом нет необходимости; пусть типы протекают через систему. Однако я встречал кодовые базы, где руководство по стилю является противоположным, где все имеет явную аннотацию типа.

@mhegazy спасибо за обсуждение / перспективу.

@ andy-hanson Спасибо, что нашли время поправить меня. Я исправил ошибку в своем примере.

Это также работает и показывает ошибку во время компиляции без дополнительного вызова функции:

interface Type<T> {
    new (...args: any[]): T;
}

/* static interface declaration */
interface ComparableStatic<T> extends Type<Comparable<T>> {
    compare(a: T, b: T): number;
}

/* interface declaration */
interface Comparable<T> {
    compare(a: T): number;
}

/* class decorator */
function staticImplements<T>() {
    return (constructor: T) => {}
}

@staticImplements<ComparableStatic<TableCell>>()   /* this statement implements both normal interface & static interface */
class TableCell { /* implements Comparable<TableCell> { */  /* not required. become optional */
    value: number;

    compare(a: TableCell): number {
        return this.value - a.value;
    }

    static compare(a: TableCell, b: TableCell): number {
        return a.value - b.value;
    }
}

Каков результат обсуждения?

Я столкнулся с этим, и я также хочу использовать static interface :

interface IDb {
  public static instance: () => Db,
}

Большинство людей забывают, что уже существуют _static_ интерфейсы в том смысле, что функция / класс конструктора уже имеет два интерфейса: интерфейс конструктора и интерфейс экземпляра.

interface MyFoo {
  method(): void;
}

interface MyFooConstructor {
  new (): MyFoo;
  prototype: MyFoo;
  staticMethod(): any;
}

const MyFoo = function MyFoo() {
  this.prop = '';
} as any as MyFooConstructor;

MyFoo.prototype = {
  method() { console.log(this); }
}

MyFoo.staticMethod = function () { /* do something static */ }

Если вы не используете классы _abstract_, значит, у вас уже есть возможности.

Спасибо @kitsonk за ответ.

Кажется, ваше объявление должно работать, но оно слишком подробное для случая.

И я просто попробовал классы _abstract_, но похоже, что они не поддерживают static с abstract .

[ts] 'static' modifier cannot be used with 'abstract' modifier.

@zixia , это проблема # 14600

Да, давайте проголосуем.

Кто-нибудь захочет ответить на мой вопрос здесь: http://stackoverflow.com/questions/44047874/dynamically-modify-typescript-classes-through-a-generic-function

Я думаю, что вся эта проблема была бы решена путем указания статических элементов в интерфейсах, и если эта проблема будет закрыта, потому что она не нужна , мне бы очень хотелось увидеть, как ее решить.

@grantila Я ответил на ваш вопрос. Как упоминалось ранее в этом вопросе, если у вас нет дополнительных требований, не упомянутых там, это легко решить, рассматривая классы как объекты.

@ Enet4 Я обновил вопрос, он был слишком упрощен. К сожалению, настоящая проблема не может быть решена с помощью взлома Object.defineProperty() . Который кстати, хак. Я хочу убедиться, что не ошибаюсь в написании make - в основном правильная статическая проверка.

Это настоящая проблема, которую я имею, код, который я начал переносить на TypeScript, но теперь сохранил как JavaScript, так как в настоящее время я не могу переписать такое огромное количество кода, которое было бы необходимо в противном случае.

Я хочу убедиться, что не ошибаюсь в написании make - в основном правильная статическая проверка.

Статическая проверка здесь может только продолжаться. Но если сейчас вас беспокоит только то, что вы устанавливаете правильное свойство, то приведение к типу производителя и установка свойства оттуда, похоже, решит эту проблему.

@ Enet4 , это рабочее решение, спасибо. Я думаю, что эту проблему (13462) следует рассмотреть еще раз, поскольку решения, подобные вашему, использующие приведение типов, на самом деле небезопасны по типу, и если это единственный способ решить проблему работы с типом класса как значением, мы теряем большую часть гибкости динамического языка.

решения, подобные вашему, использующие приведение типов, на самом деле небезопасны по типу, и если это единственный способ решить ситуацию, когда тип класса используется как значение, мы теряем большую часть гибкости динамического языка.

@grantila В свою защиту, что является дискуссионным. : wink: Ваш вариант использования отличается от представленных в этом выпуске тем, что ваш тип класса может (или нет) предоставлять метод в зависимости от условий выполнения. И ИМО, это более опасно, чем приведение типов, представленное в моем ответе, который был выполнен только для того, чтобы разрешить вставку поля в объект. В этом смысле результирующий тип класса C & Maker<T> должен оставаться совместимым со всем остальным, полагающимся на C .

Я также попытался представить, где статические методы в интерфейсах могут вам здесь помочь, но, возможно, мне что-то не хватает. Даже если бы в вашем интерфейсе было что-то вроде static make?(... args: any[]): self , его нужно было бы проверить во время выполнения перед вызовом. Если вы хотите продолжить это обсуждение, давайте рассмотрим возможность сделать это в другом месте, чтобы уменьшить шум. : Little_smiling_face:

Значит, мы не можем ввести статические фабричные методы проверки в классах, реализующих один и тот же интерфейс?

Мой вариант использования:

interface IObject {
    static make(s: string): IObject;
}

class A implements IObject{
    static make(s: string): IObject {
        // Implementation A...
    }
}

class B implements IObject{
    static make(s: string): IObject {
        // Implementation B...
    }
}

A.make("string"); // returns A
B.make("string"); // returns B

Я не хочу писать новый фабричный класс только для этого.

@ tyteen4a03 Удалите IObject из этого примера, и он будет компилироваться. См. Также https://github.com/Microsoft/TypeScript/issues/17545#issuecomment -319422545

@ andy-ms Да, очевидно, это работает, но весь смысл проверки типов - это проверка типов. Вы всегда можете снизить безопасность типов настолько, чтобы скомпилировать каждый вариант использования, но при этом игнорируется тот факт, что это запрос функции, причем не такой уж безумный.

Да, очевидно, это работает, но весь смысл проверки типов - это проверка типов.

Это длинный длинный длинный поток о том, как статическая сторона класса является отдельным интерфейсом для стороны экземпляра, а implements указывает на сторону экземпляра. @andy-ms указывал @ tyteen4a03, как заставить фрагмент кода работать, потому что это было _неправильно_, не отказываться от проверки типов.

Мой вариант использования, позволяющий статическим методам использовать универсальный параметр класса, предназначен для классов миксинов. Я создаю структуру сущностей, которая использует аннотации для определения столбцов, баз данных и т.д., и хотел бы, чтобы в мои классы сущностей была добавлена ​​статическая функция, которая обеспечила бы удобный доступ к правильно типизированному репозиторию.

class RepositoryMixin<T> {
    public static repository(): EntityRepository<T> {
        return new EntityRepository<T>(Object.getPrototypeOf(this));
    }
}

@mixin(RepositoryMixin)
class Entity implements RepositoryMixin<Entity> {
    public id: number;
}

Entity.repository().save(new Entity());

@rmblstrp Можете ли вы показать, как бы вы использовали предложенную функцию в своем примере? Желательно как-то поддающееся проверке?

@rmblstrp , который не требует этой функции. Фактически, поскольку вы используете декоратор, вы действительно можете _ его тип_, чтобы убедиться, что аннотированные классы предоставляют необходимые статические методы. Вам не нужны и то, и другое, и на самом деле это будет излишне.

Здравствуйте, я не хочу быть вне темы или вне рамок этого разговора.
Однако, поскольку вы обсуждали различные парадигмы программирования (следует ли использовать ООП или функциональное?), Я хочу конкретно поговорить о статических фабриках, которые обычно используются для создания соединения с базой данных или предоставления каких-либо услуг.
Во многих языках, таких как PHP и Java, статические фабрики были устаревшими в пользу внедрения зависимостей. Внедрение зависимостей и контейнеры IOC стали популярными благодаря таким фреймворкам, как Symfony и Spring.
В Typescript есть замечательный контейнер IOC под названием InversifyJS.

Это файлы, в которых вы можете увидеть, как все работает.
https://github.com/Deviad/virtual-life/blob/master/models/generic.ts
https://github.com/Deviad/virtual-life/blob/master/service/user.ts
https://github.com/Deviad/virtual-life/blob/master/models/user.ts
https://github.com/Deviad/virtual-life/blob/master/utils/sqldb/client.ts
https://github.com/Deviad/virtual-life/blob/master/bootstrap.ts

Я не говорю, что это идеальное решение (и, вероятно, оно оставляет некоторые сценарии нераскрытыми), но оно работает также с React, есть некоторые примеры, на которые вы уже можете посмотреть.

Кроме того, я предлагаю вам посмотреть это видео о функциональном программировании, которое охватывает этот аспект, о том, какой из них лучше: https://www.youtube.com/watch?v=e-5obm1G_FY&t=1487s
СПОЙЛЕР: никто не лучше, это зависит от проблемы, которую вы хотите решить. Если вы имеете дело с пользователями, классами, учителями, ООП будет лучше моделировать вашу проблему с помощью объектов.
Если вам нужен синтаксический анализатор, который сканирует веб-сайт, возможно, лучше использовать генераторы функций, которые возвращают частичные результаты и т. Д. :)

@Deviad @aluanhaddad С момента публикации я использую InversifyJS, и это было абсолютно здорово и определенно намного лучше. На момент написания статьи я только начал использовать Typescript / Node после того, как раньше работал с PHP / C #. Просто потребовалось немного времени, чтобы познакомиться со средой и доступными пакетами.

Какой у этого статус? Почему вы продолжаете закрывать нерешенные вопросы в репо?

Я считаю, что это один из тех случаев, когда проблема должна быть явно помечена как «wontfix», поскольку выбор отсутствия статических методов в интерфейсах является намеренным.

@ enet4 , я новичок, но это совсем не понятно. Читая этот и другие связанные с этим вопросы, мне кажется, что это в основном следующие вопросы:

А. Это сложно
Б. Нам не нравится (каждый) конкретный синтаксис, который мы видели до сих пор.
C. Небольшое меньшинство не считает, что это вообще возможно, и предпочло бы удалить нынешний странный способ сделать это.

Если это действительно задумано, и ответственные лица этого не хотят, они должны написать общедоступный документ и связать его с этим и другими потоками. Это сэкономит нам много времени, если мы будем оставаться в подвешенном состоянии.

Мы уже ссылались на номер 14600 в этой ветке, и это проблема, которую необходимо решить для запроса функции.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги