#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 проверяет их реализацию.