Как создать аналог метода функционального интерфейса Java по умолчанию в TypeScript?

#javascript #java #typescript

#javascript #java #typescript

Вопрос:

Существует проект, который реализует Java(FX) API в TypeScript / JavaScript (Script4J). И теперь я хочу изменить функциональный интерфейс Comparator. Это решение по умолчанию:

 export interface Comparator<T> {
    (o1: T, o2: T): number;
}
  

Такое решение позволяет добавлять компараторы в качестве функций со стрелками (например, java lambda), например:

 let comparator: Comparator<number> = (n1: number, n2: number): number => {
    return n1 - n2;
};
let treeMap: SortedMap<number, string> = new TreeMap<number, string>(comparator);
  

Как вы видите, код очень чистый. Теперь мне нужно добавить в интерфейс TypeScript Comparator следующий метод (Java-код):

 default Comparator<T> reversed() {
    return Collections.reverseOrder(this);
}
  

Каков наилучший способ сделать это, учитывая, что я не могу изменить API?

Ответ №1:

В typescript нет прямого эквивалента реализациям метода интерфейса по умолчанию. Вы можете использовать функцию ea, которая присваивает значение по умолчанию функции сравнения:

 export interface Comparator<T> {
    (o1: T, o2: T): number;
    reversed() : any // not sure what return type should be, maybe (o1: T, o2: T) => number ?
}

function comparatorWithDefault<T>(fn: (o1: T, o2: T) => number) {
    function reversed(this: Comparator<T>) {
        return Collections.reverseOrder(this);
    }
    return Object.assign(fn, { reversed });
}

let comparator: Comparator<number> = comparatorWithDefault((n1: number, n2: number): number => {
    return n1 - n2;
})
let treeMap: SortedMap<number, string> = new TreeMap<number, string>(comparator);
  

В отличие от Java, добавление элемента в Comparator приведет к нарушению большого количества существующего кода.

Комментарии:

1. Не могли бы вы прокомментировать мое решение?

2. @Pavel_K Это немного похоже на Java-решение в TS. Но, эй, если это вас устраивает, в этом нет ничего плохого, IMO.

Ответ №2:

После некоторых размышлений я решил, что единственный способ достичь максимального Java API — это использовать абстрактные классы вместо интерфейсов. Это мое решение:

 function applyMixins(derivedCtor: any, baseCtors: any[]) {
    baseCtors.forEach(baseCtor => {
        Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
             if (name !== 'constructor') {
                derivedCtor.prototype[name] = baseCtor.prototype[name];
            }
        });
    }); 
}

type ComparatorFunc<T> = (o1: T, o2: T) => any;

abstract class Comparator<T> {

    public static lambda<T>(func: ComparatorFunc<T>): Comparator<T> {
        return new class extends Comparator<T> {
            public compare(o1: T, o2: T): number {
                return func(o1, o2);
            }
        }
    }

    public abstract compare(o1: T, o2: T): number;
}

//VAR 1 - lambda
let lambdaComparator: Comparator<number> = Comparator.lambda((n1: number, n2: number) => { return n1 - n2;});
console.log("Lambda comparator");
console.log(lambdaComparator.compare(100, 50));

//VAR 2 - full implementation
class MyComparator implements Comparator<number> {

    public compare(o1: number, o2: number): number {
        return o1 - o2;
    }
}
applyMixins (MyComparator, [Comparator]);

let classComparator: MyComparator = new MyComparator();
console.log("Class comparator");
console.log(classComparator.compare(100, 50));
  

Преимущества:

  • Мы очень близки к Java API.
  • Названия методов видны.
  • Мы можем использовать функции со стрелками для быстрого создания экземпляра класса.
  • Мы можем создать класс, который реализует несколько интерфейсов.
  • Поскольку все классы, реализующие интерфейсы, проходят через одну функцию (applyMixins), мы можем добавить поддержку методов, которые могут проверять, реализует ли определенный экземпляр определенный интерфейс (класс легко проверяется через instanceof).

Недостатки:

  • Вызывайте applyMixins для каждого класса при каждом запуске программы / открытии страницы.
  • Необходимо показывать пустые методы по умолчанию (reversed:()=> Comparator ) в каждой реализации, поскольку TypeScript проверяет их реализацию.