Typescript: Permitir visibilidad a los constructores

Creado en 13 mar. 2015  ·  42Comentarios  ·  Fuente: microsoft/TypeScript

Creo que es un patrón bastante común tener un método de fábrica estático para crear una clase y que el constructor de esta clase sea privado de otra manera para que no pueda crear una instancia de la clase a menos que use el método de fábrica.

Fixed Suggestion help wanted

Comentario más útil

Bueno, lo mismo se aplica a las funciones privadas en las clases, ¿verdad? No veo por qué no pudimos obtener un error de compilación para acceder a un constructor privado.

Todos 42 comentarios

Dado que todas las "clases" son en realidad solo funciones y dado que no existe una función no invocable, en cualquier lugar donde la clase sea visible, también puede usar el operador new en ella.

Sin embargo, puede hacer que la clase no sea pública (por ejemplo, en un módulo) y exportar una interfaz para la clase. Esto abusa del hecho de que las interfaces pueden extender clases pero no son directamente instanciables ni extensibles.

Bueno, lo mismo se aplica a las funciones privadas en las clases, ¿verdad? No veo por qué no pudimos obtener un error de compilación para acceder a un constructor privado.

@billccn No me gusta eliminar solicitudes por "JS permite, entonces no podrás esconderte".
Una cosa es proteger completamente en TS y JS generado, otra cosa es proteger eso hasta el punto de generar JS. Como ha explicado, la protección completa no es posible, pero debería ser posible tener una visibilidad diferente verificada por el compilador.
Si no le gustan los modificadores de visibilidad, use público predeterminado en todas partes, hay otros que encuentran útil ese concepto.

Sí, supongo que si los campos privados se implementan solo como una verificación del compilador, entonces probablemente se pueda extender a los constructores. Sin embargo, la solución alternativa basada en la interfaz ya funciona.

: +1: Hay ocasiones en las que quiero forzar al programador a usar los métodos de fábrica para la legibilidad del código. La solución alternativa basada en la interfaz crea mucho ruido en el código.

Creo que solo la verificación del compilador es el camino a seguir.

Aceptado, aceptando RP

Para aclarar, ¿podría ser extensible una clase con un constructor privado?

es decir, ¿arrojaría un error?

class A {
    private constructor() {
    }
}

class B extends A { // Should there be an error at A saying: "Cannot extend private class 'A'"?
}

si es así, entonces permitiríamos esto:

class A {
    protected constructor(a?: any)
    private constructor() {

    }
}

class B extends A { // No error since 'A' has a non-private constructor
}

Desde la experiencia de un desarrollador que no es JS, ese es el comportamiento esperado.

En el primer ejemplo, B debería ser un error porque su super llamada implícita es ilegal. Entonces, una clase con private constructor es efectivamente sealed / final .

En el segundo ejemplo, la declaración de A debería ser un error porque todas las sobrecargas de un constructor, y su implementación, deben tener la misma visibilidad (la misma regla que tenemos para los métodos).

Vea también el # 471. ¿Es realmente necesario permitir que los constructores sean privados o lo hará protegido?

@benliddicott a veces es útil forzar un singleton o forzar al programador a usar uno de los métodos estáticos para crear un objeto, porque a veces puede ser más legible.

Vea aquí .

@dsherret protected satisface todas esas necesidades.

Pero no puede estar seguro de que un usuario intermedio nunca tendrá una necesidad legítima de heredar de su clase. El único efecto de private es evitar que los usuarios intermedios satisfagan una necesidad que no anticipaba.

@benliddicott A veces, lo único que quieres es que una clase no se pueda ampliar. Le recomiendo el artículo 15 de Effective Java Minimize Mutability, especialmente:

"2. Asegúrate de que la clase no se pueda extender. Esto evita que las subclases descuidadas o maliciosas comprometan el comportamiento inmutable de la clase al comportarse como si el estado del objeto hubiera cambiado. La prevención de subclases generalmente se logra haciendo que la clase sea final, pero no es una alternativa que discutiremos más adelante.

Actualmente no hay soporte para final / sealed en TypeScript, por lo que un constructor privado es la única forma de lograr una clase inmutable desde la perspectiva del sistema de tipos. (Sin embargo, recomiendo a las personas que también congelen el objeto en el constructor).

@billccn , la opinión de ese autor es interesante. También lo es la idea de que la opinión del escritor de la biblioteca debe prevalecer sobre la del usuario de la biblioteca. Mi propia experiencia ha sido que los escritores de bibliotecas no saben cuándo usar la biblioteca privada y la usan en exceso, causando dolores de cabeza a los usuarios, simplemente porque creen que saben cómo se usará su biblioteca, cuando en realidad no lo saben.

Pero en lugar de un lenguaje estático como Java, una comparación más adecuada sería Perl, otro lenguaje dinámico: http://www.perlmonks.org/?node_id=437623

Una de las razones por las que perl no tiene modificadores de acceso como público, privado y protegido es porque se reconoce que a menudo obstaculizan la realización del trabajo: lo que imaginó el diseñador original no tiene nada que ver con lo que quieres hacer con él. De la misma manera, diseñe para tener flexibilidad, aunque es posible que no vea que lo necesita ahora, la próxima persona que venga puede ver que es increíblemente útil para resolver ese nuevo problema y bendecirá su genio por desarrollar esa flexibilidad ;-)

y:

Perl no tiene un encaprichamiento con la privacidad impuesta. Preferiría que te quedaras fuera de su sala porque no te invitaron, no porque tenga escopeta

http://www.perlmonks.org/?node_id=1096925

De hecho, JavaScript es el mismo y, sí, TypeScript es el mismo, en casi todos los aspectos. En mecanografiado, puede acceder a miembros privados sin problemas, utilizando el nombre de escape-hatch: obj["propertyName"] .

Si, como escritor de una biblioteca, sospecha que no es prudente llamar a un método o heredar de un objeto, dígale al usuario que no es prudente. Pero no los evite; después de todo, es posible que sepan mejor que usted.

No entiendo la discusión sobre "protegido es suficiente". Si TS tiene el concepto de visibilidad y puedo aplicar este concepto a los constructores, la respuesta es "no es suficiente".

Si se permiten modificadores de acceso en el constructor, creo que debería tener un comportamiento coherente con otros modificadores y permitir privado.

Los miembros privados en general son sin duda útiles. Le permiten organizar y refactorizar los detalles de implementación de una clase sin preocuparse por causar efectos secundarios fuera de la clase.

Con los constructores privados, es posible que desee obligar a los desarrolladores de mi equipo a programar de cierta manera. Por ejemplo, es posible que desee obligarlos a usar el método estático aquí porque es más legible y obligarlos a no extender esta clase:

class Currency {
    private constructor(private value: number, private type: CurrencyType) {
    }

    static fromNumberAndType(value: number, type: CurrencyType) {
        return new Currency(value, type);
    }

    static fromString(str: string) {
        const value = ...,
              type  = ...;

        return new Currency(value, type);
    }

    // ... omitted ...
}

// error:
const badCurrency = new Currency(5.66, CurrencyType.USD);
// ok:
const goodCurrency1 = Currency.fromNumberAndType(5.66, CurrencyType.USD);
const goodCurrency2 = Currency.fromString("5.66 USD");

Con los constructores privados, es posible que desee obligar a los desarrolladores de mi equipo a programar de cierta manera.

Eso es un problema de gestión, no de diseño de lenguaje.

@benliddicott Lo similar que puedes decir sobre los descriptores de tipo en las variables :) Si no te gusta la función, usa JS simple. O
Use TS y cree algunas reglas similares a pelusa que prohíban el uso de privado en el constructor. Parafraseando su último comentario: "Eso es un problema de herramientas, no un problema de diseño de lenguaje".

@benliddicott, si no es posible hacer algo, no tendré que devolverlo cuando esté mal después de revisar el código. Eso ahorra tiempo.

Decirle al compilador exactamente cómo debe usarse el código correctamente es un activo que brinda la retroalimentación adecuada al desarrollador que lo usa mientras está programando.

@dsherret No, es una restricción arbitraria que habilita Architecture astronauts : -1:

@jbondc ¿Es este "Astronautas de arquitectura" algún argumento razonable? ¿Intentas ofender o elogiar a las personas que quieren esta función?

@jbondc No creo que el término "astronauta de la arquitectura" sea relevante aquí. ¿No es eso hablar de personas que pasan más tiempo pensando en arquitectura que escribiendo código ? La decisión de utilizar un constructor privado puede ser rápida y sencilla, como utilizar casi cualquier función en un lenguaje como TypeScript.

Además, no creo que sea "arbitrario" porque puede ayudar a prevenir el mal uso del código. Tal vez quiera recordarme a mí mismo oa mi equipo que no usemos el constructor y, en su lugar, use uno de los métodos estáticos o fuerce el uso de un método estático para hacer cumplir un singleton. Es rápido y simple y obtengo los comentarios adecuados del compilador. Si eso es arbitrario, entonces podría argumentar que muchos aspectos de un idioma son arbitrarios. ¿Quizás tienes más que decir acerca de que es arbitrario que no expresaste en tu comentario?

Esta es una característica del lenguaje que si a la gente no le gusta, es fácil de no usar.

@dsherret ¿ No es suficiente para documentar en lugar de imponer otra restricción?

Complica significativamente la herencia múltiple, consulte el n. ° 4805 (que a mí me parece una ocurrencia tardía en este momento). Ya he expresado algunos de mis pensamientos en el n. ° 3578, así que no me molestaré en volver a hacerlo. Salió un poco fuerte con astronaut , no es mi intención ofender a nadie.

Las clases de

Es mucho más agradable tener un código autodocumentado que escribirlo externamente o en un comentario. Esa es una de las razones por las que me gustan todas las restricciones de TypeScript y, esencialmente, lo que estamos pidiendo en esta función: solo otra forma de documentar el código utilizando el lenguaje mismo.

Bueno, aquí hay otra forma de escribir tu ejemplo:

module Currency {
    export enum Type {
        CAD = 1.3,
        USD = 1,
    }

    class PrivateCurrency {
        constructor(private value: number, private type: Type) { }
    }

    export function fromNumberAndType(value: number, type: Type) {
        return new PrivateCurrency(value, type);
    }

    export function fromString(str: string) {
        let value = 10;
        let type = Type.CAD;
        return new PrivateCurrency (value, type);
    }
}

Menos OO-ish, pero en realidad tienes una clase privada 'genuinamente' real.

@jbondc ¡ Qué bueno que hayas encontrado tu camino para las clases privadas! Deje que otros usen otro enfoque (clase exportable con constructor privado). Ahora puede observar que puede haber características que satisfagan las necesidades del grupo A y no interrumpan el trabajo del grupo B. :)

+1

+1

¿Sigue esto en el radar? ¡Las relaciones públicas están muy añejas!

+1

+1

+1

: +1: puede ser útil para patrones de diseño de fábrica

Disculpas si esto es una repetición o simplemente llega tarde a la fiesta, pero estamos usando el siguiente patrón para lograr constructores privados:

interface ExampleBuilder {
    Instance(): Example;
}

export interface Example {
    Val(): number;
}

export let Example: ExampleBuilder = class ExampleImpl {
    constructor(v: number) {
    }

    Val(): number {
        return 42;
    }

    static Instance(): Example {
        return new ExampleImpl(2);
    }
};

let x = Example.Instance(); // OK: x has type Example
let y = new Example(5);     // ERROR: Cannot use 'new' with an expression whose type lacks a call or construct signature.

Tenga en cuenta que esto se puede arreglar aún más usando el modificador readonly recientemente combinado.

@myitcv, eso es genial para hacer cumplir por ahora ... probablemente la mejor manera mencionada en mi opinión. Sin embargo, todavía es demasiado detallado y se necesitan unos segundos para comprender qué está pasando, por lo que un modificador de acceso aún sería muy bueno para los constructores.

Personalmente, solo estoy marcando todos mis futuros constructores privados con comentarios // todo: make private once supported y no llamando a los constructores. Será bueno una vez que esta característica esté disponible para obtener un poco de cumplimiento y una mejor documentación con un modificador de acceso.

@dsherret

Sin embargo, todavía es demasiado detallado y se necesitan unos segundos para comprender lo que está sucediendo.

Convenido. No sufrimos demasiado por esa carga cognitiva porque estas clases se generan mediante código. Entonces, la interfaz para el programador es realmente agradable y simple.

corregido por https://github.com/Microsoft/TypeScript/pull/6885

gracias @AbubakerB!

Hola, ¿esta función se lanzará en alguna versión futura de mecanografiado?
A partir de ahora, intentar declarar un constructor privado o protegido me da este error en el mecanografiado 1.8.10:
error TS1089: el modificador 'privado' no puede aparecer en una declaración de constructor.

Ahh, no importa. Acabo de enterarme de la hoja de ruta que establece que esta característica se incluirá en mecanografiado 2.0.

Ahh, no importa. Acabo de enterarme de la hoja de ruta que establece que esta característica se incluirá en mecanografiado 2.0.

Además, si se establece el hito en TypeScript 2.0 y la etiqueta Fixed indicaría que está incluido. Cualquier cosa con una etiqueta de Fixed generalmente se incluye en el maestro y está disponible a través de npm install typescript@next .

Estoy usando TypeScript 2.0.2 RC y todavía obtengo TS1089 cuando intento hacer un constructor private . ¿Lo estoy haciendo mal o simplemente no se ha solucionado?

Estoy usando TypeScript 2.0.2 RC y todavía obtengo TS1089 cuando intento hacer un constructor privado. ¿Lo estoy haciendo mal o simplemente no se ha solucionado?

está funcionando para mí. asegúrese de que el alias de su comando apunte a la versión correcta y que su editor esté actualizado para usar la última versión de TS.

Encontré el problema. Fue culpa de gulp-typescript que estaba usando la versión incorrecta de tsc pesar de mis especificaciones en package.json y lo que se resolvió en PATH .

Para cualquier otra persona que tenga este problema, mi solución fue editar mi gulpfile.js y ...

  1. require TypeScript antes de gulp-typescript de la siguiente manera:
// Tool-Chain: Scripts
var tsc = require("typescript");
var typescript = require('gulp-typescript');
  1. Proporcione el compilador como anulación al definir mi tarea de compilación:
// Task(s): Build TypeScript Outputs
var tsconfig = typescript.createProject("path to tsconfig", { typescript: tsc });
gulp.task('build:scripts', function () {
    let ts = tsconfig.src()
                     .pipe(sourcemaps.init())
                     .pipe(typescript(tsconfig));

    return ts.js.pipe(sourcemaps.write(".")).pipe(gulp.dest(path.join(outputs.root, outputs.scripts)))
});
¿Fue útil esta página
0 / 5 - 0 calificaciones