Реализация безопасного клона в машинописи с использованием polymorphi c без использования типов - PullRequest
0 голосов
/ 29 апреля 2020

В запросе pull для добавления polymorphi c this приведен один вариант использования - метод клонирования. Однако, когда я попытался реализовать это, я столкнулся с той же проблемой, что и shelby3, отмеченный в цепочке комментариев :

, как может polymorphi c clone (): это может быть реализовано, если это не законно назначить экземпляр подкласса этому ??? Похоже, вместо этого компилятору нужно будет вывести это наследование из типов Entity clone () как абстрактный метод, даже если база имеет реализацию, чтобы гарантировать, что каждый подкласс обеспечивает уникальную реализацию. ".

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

См. Этот пример, без приведения <this> код не скомпилируется, а также не может обнаружить, что c.clone() имеет неправильный тип:

interface Cloneable {
    clone() : this
}

 class ClassA implements Cloneable {
     a: number = 0;

    clone() : this {
         let result = new ClassA();
         result.a = this.a;
         return <this>result;
    }
}

class ClassB extends ClassA {
    b: number = 0;

    clone() : this {
        let result = new ClassB();
        result.a = this.a;
        result.b = this.b;
        return <this>result;
    }

}

class ClassC extends ClassB {
    c: number = 0;

    // missing clone method 
}

function test() {
    const a = new ClassA();
    const b = new ClassB();
    const c = new ClassC();
    const aClone = a.clone(); // aClone : ClassA
    const bClone = b.clone(); // bClone : ClassB
    const cClone = c.clone(); // typescript thinks cClone is ClassC, but is actually ClassB

    alert(cClone.constructor.name); // outputs ClassB

}

test();

1 Ответ

1 голос
/ 29 апреля 2020

Я согласен с вами, что не существует очевидного безопасного типа для переопределения clone() или какого-либо метода, который возвращает что-либо, зависящее от polymorphi c this.


Polymorphi c this подобен универсальному параметру типа c, который указывается только тогда, когда у вас есть конкретный экземпляр класса. Как и параметр типа generi c, компилятору становится трудно проверить присвоение такого типа, когда он еще не указан, например, внутри реализации такой функции:

function similar<This extends { x: string }>(): This {
    return { x: "" }; // error
}

(правильно) ошибка, потому что компилятор не уверен, будет ли {x: ""} фактически назначаться тому, что позже будет указано This. Если вы хотите, чтобы вышесказанное компилировалось без ошибок, вам нужно использовать утверждение типа.

Это в основном то, что вы должны делать при реализации clone() в каждом классе, и использование такого утверждения типа подавляет ошибку в обмен на вполне реальную возможность того, что кто-то придет позже (с подкласс) и нарушить ваш заявленный тип.


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

class ClassA implements Cloneable {
    a: number = 0;

    // add this constructor
    constructor() {
        if (!this.constructor.prototype.hasOwnProperty("clone")) {
            throw new Error("HEY YOU! " + this.constructor.name +
                " is MISSING a clone() implementation!  FIX THAT!!")
        }
    }

    clone(): this {
        let result = new ClassA();
        result.a = this.a;
        return <this>result;
    }
}

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

Error: HEY YOU! ClassC is MISSING a clone() implementation!  FIX THAT!!

Так что, по крайней мере, любой потенциальный разработчик подкласса получит боевой шанс заметить, что происходит, пока есть некоторое тестирование во время выполнения. Конечно, безопасность типов времени компиляции была бы лучше, поэтому ?‍♂️


Другой подход - попытаться придумать реализацию базового класса clone(), которая ведет себя полиморфно и не должны быть подклассами. Это все еще что-то вроде fr agile, в том смысле, что некоторый подкласс может сделать некоторые странные вещи, которые реализация clone() базового класса не ожидала, или подкласс может go опередить и переопределить clone(), что мы не делаем хочу (и мы не можем предотвратить это с помощью чего-то вроде final ... , забавно читать об этом ), но это по крайней мере работает для вашего примера кода:

class Clonable {
    clone(): this {
        const ret: this = Object.create(this.constructor.prototype);
        Object.assign(ret, this);
        return ret;
    }
}

Мы делаем Clonable базовый класс вместо интерфейса, так как реализация идет с ним. Тогда ваши подклассы ничего не делают для clone():

class ClassA extends Clonable {
    a: number = 0;
}

class ClassB extends ClassA {
    b: number = 0;
}

class ClassC extends ClassB {
    c: number = 0;
}

И ваши тесты работают так, как вы хотите:

    const c = new ClassC();
    const cClone = c.clone(); // cClone : ClassC
    alert(cClone.constructor.name); // outputs ClassC

Это лучшее, что я могу сделать. Надеюсь, поможет; удачи!

Детская площадка ссылка на код

...