Typescript: Autoriser la visibilité sur les constructeurs

Créé le 13 mars 2015  ·  42Commentaires  ·  Source: microsoft/TypeScript

Je pense que c'est un modèle assez courant d'avoir une méthode de fabrique statique pour créer une classe et le constructeur de cette classe étant autrement privé afin que vous ne puissiez pas instancier la classe à moins que vous n'utilisiez la méthode de fabrique.

Fixed Suggestion help wanted

Commentaire le plus utile

Eh bien, la même chose s'applique aux fonctions privées dans les classes, non? Je ne vois pas pourquoi nous n'avons pas pu obtenir une erreur de compilation pour accéder à un constructeur privé.

Tous les 42 commentaires

Puisque toutes les «classes» ne sont en fait que des fonctions et comme il n'y a pas de fonction non appelable, partout où la classe est visible, vous pouvez également utiliser l'opérateur new dessus.

Vous pouvez cependant rendre la classe non publique (par exemple dans un module) et exporter une interface pour la classe. Cela abuse du fait que les interfaces peuvent étendre des classes mais ne sont pas directement instanciables ni extensibles.

Eh bien, la même chose s'applique aux fonctions privées dans les classes, non? Je ne vois pas pourquoi nous n'avons pas pu obtenir une erreur de compilation pour accéder à un constructeur privé.

@billccn Je n'aime pas tuer les requêtes par "JS permet alors vous ne pourrez pas vous cacher".
Une chose est de protéger complètement dans TS et JS généré, l'autre chose est de protéger cela au point de générer JS. Comme vous l'avez expliqué, la protection complète n'est pas possible, mais avoir une visibilité différente vérifiée par le compilateur devrait être possible.
Si vous n'aimez pas les modificateurs de visibilité, utilisez le public par défaut partout, il y en a d'autres qui trouvent ce concept utile.

Oui, je suppose que si les champs privés sont implémentés uniquement en tant que vérification du compilateur, alors il peut probablement être étendu aux constructeurs. Cependant, la solution de contournement basée sur l'interface fonctionne déjà.

: +1: Il y a des moments où je veux forcer le programmeur à utiliser les méthodes d'usine pour la lisibilité du code. La solution de contournement basée sur l'interface crée beaucoup de bruit dans le code.

Je pense que seule la vérification du compilateur est la voie à suivre.

Accepté, acceptant les PR

Pour clarifier, une classe avec un constructeur privé pourrait-elle être extensible?

ie Est-ce que cela jetterait une erreur?

class A {
    private constructor() {
    }
}

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

si tel est le cas, autoriserions-nous ceci:

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

    }
}

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

De l'expérience de développeur non-JS, c'est le comportement attendu.

Dans le premier exemple, B devrait être une erreur car son super appel implicite est illégal. Ainsi, une classe avec un private constructor est effectivement sealed / final .

Dans le second exemple, la déclaration de A doit être une erreur car toutes les surcharges d'un constructeur et son implémentation doivent avoir la même visibilité (même règle que nous avons pour les méthodes).

Voir aussi # 471. Est-il vraiment nécessaire de permettre aux constructeurs d'être privés ou est-ce que la protection le fera?

@benliddicott il est parfois utile de forcer un singleton ou de forcer le programmeur à utiliser l'une des méthodes statiques pour créer un objet, car parfois il peut être plus lisible.

Regardez ici .

@dsherret protected répond à tous ces besoins.

Mais vous ne pouvez pas être sûr qu'un utilisateur en aval n'aura jamais un besoin légitime d'hériter de votre classe. Le seul effet de private est d'empêcher vos utilisateurs en aval de répondre à un besoin que vous n'aviez pas anticipé.

@benliddicott Parfois, la seule chose que vous voulez, c'est qu'une classe ne soit pas extensible. Je vous renvoie à l'article 15 efficace de Java Minimize Mutability, en particulier:

"2. Assurez-vous que la classe ne peut pas être étendue. Cela empêche les sous-classes imprudentes ou malveillantes de compromettre le comportement immuable de la classe en se comportant comme si l'état de l'objet avait changé. Empêcher le sous-classement est généralement accompli en rendant la classe finale, mais là est une alternative que nous discuterons plus tard.

Actuellement, il n'y a pas final support sealed dans TypeScript, donc un constructeur privé est le seul moyen d'obtenir une classe immuable du point de vue du système de types. (Cependant, je recommande aux gens de geler également l'objet dans le constructeur.)

@billccn , l'opinion de cet auteur est intéressante. Il en va de même pour l'idée que l'opinion du rédacteur de la bibliothèque devrait l'emporter sur celle de l'utilisateur de la bibliothèque. Ma propre expérience a été que les rédacteurs de bibliothèques ne savent pas quand utiliser le privé et le surutilisent, ce qui cause des maux de tête aux utilisateurs, simplement parce qu'ils croient savoir comment leur bibliothèque sera utilisée, alors qu'en fait ils ne le font pas.

Mais plutôt qu'un langage statique comme Java, une comparaison plus appropriée serait Perl, un autre langage dynamique: http://www.perlmonks.org/?node_id=437623

L'une des raisons pour lesquelles perl n'a pas de modificateurs d'accès tels que public, privé et protégé, est qu'il est reconnu qu'ils empêchent souvent de faire le travail: ce qui était envisagé par le concepteur d'origine n'a rien à voir avec ce que vous voulez en faire. De la même manière, concevez de la flexibilité - bien que vous ne pensiez peut-être pas que vous en avez besoin maintenant, la prochaine personne à venir peut voir qu'il est incroyablement pratique de résoudre ce nouveau problème, et bénira votre génie pour développer cette flexibilité ;-)

et:

Perl n'a pas d'engouement pour la confidentialité imposée. Il préférerait que vous restiez en dehors de son salon parce que vous n'étiez pas invité, pas parce qu'il a un fusil de chasse

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

En fait, JavaScript est le même, et - oui - TypeScript est le même, à presque tous les égards. En tapuscrit, vous pouvez accéder aux membres privés très bien - en utilisant le bien nommé escape-hatch: obj["propertyName"] .

Si, en tant qu'écrivain de bibliothèque, vous pensez qu'il n'est pas judicieux d'appeler une méthode ou d'hériter d'un objet, dites à l'utilisateur qu'il n'est pas judicieux. Mais ne les empêchez pas - ils savent peut-être mieux que vous après tout.

Je ne comprends pas la discussion sur «la protection étant suffisante». Si TS a le concept de visibilité et que je peux appliquer ce concept aux constructeurs, la réponse est "ce n'est pas suffisant".

Si les modificateurs d'accès sont autorisés sur le constructeur, je pense qu'il devrait avoir un comportement cohérent avec les autres modificateurs et autoriser private.

Les membres privés en général sont certainement utiles. Ils vous permettent d'organiser et de refactoriser les détails d'implémentation d'une classe sans vous soucier de provoquer des effets secondaires en dehors de la classe.

Avec les constructeurs privés, je pourrais vouloir forcer les développeurs de mon équipe à programmer d'une certaine manière. Par exemple, je pourrais vouloir les forcer à utiliser la méthode statique ici car elle est plus lisible et les forcer à ne pas étendre cette classe:

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");

Avec les constructeurs privés, je pourrais vouloir forcer les développeurs de mon équipe à programmer d'une certaine manière.

C'est un problème de gestion et non un problème de conception de langage.

@benliddicott La même chose que vous pouvez dire à propos des descripteurs de type sur les variables :) Si vous n'aimez pas la fonctionnalité, utilisez simplement JS. OU
Utilisez TS et créez des règles de type lint qui interdisent l'utilisation de private sur le constructeur. Pour paraphraser votre dernier commentaire: "C'est un problème d'outillage et non un problème de conception de langage".

@benliddicott si quelque chose n'est pas possible, je n'aurai pas à le renvoyer en cas de problème après avoir effectué une révision du code. Cela fait gagner du temps.

Dire au compilateur exactement comment le code doit être utilisé correctement est un atout qui donne le retour approprié au développeur qui l'utilise lors de la programmation.

@dsherret Non, c'est une contrainte arbitraire qui autorise Architecture astronauts : -1:

@jbondc Cet argument "astronautes d'architecture" est-il un argument raisonnable? Vous essayez d'offenser ou de féliciter les personnes qui veulent cette fonctionnalité?

@jbondc Je ne crois pas que le terme «astronaute d'architecture» soit pertinent ici. N'est-ce pas parler de gens qui passent plus de temps à penser à l'architecture qu'à écrire du code ? La décision d'utiliser un constructeur privé peut être simple et rapide, comme l'utilisation de presque toutes les fonctionnalités d'un langage tel que TypeScript.

De plus, je ne pense pas que ce soit «arbitraire» car cela peut aider à éviter une mauvaise utilisation du code. Peut-être que je veux me rappeler ou rappeler à mon équipe de ne pas utiliser le constructeur et d'utiliser à la place l'une des méthodes statiques ou de forcer l'utilisation d'une méthode statique pour appliquer un singleton. C'est rapide et simple et j'obtiens les bons commentaires du compilateur. Si c'est arbitraire, vous pouvez affirmer que de nombreux aspects d'une langue sont arbitraires. Peut-être avez-vous plus à dire sur l'arbitraire que vous n'avez pas exprimé dans votre commentaire?

Il s'agit d'une fonctionnalité de langage que si les gens n'aiment pas, il leur est facile de ne pas l'utiliser.

@dsherret Pas assez pour documenter au lieu d'appliquer encore une autre contrainte?

Cela complique considérablement l'héritage multiple, voir # 4805 (ce qui me semble être une réflexion après coup pour le moment). J'ai déjà exprimé certaines de mes pensées dans le # 3578, donc je ne prendrai pas la peine de le refaire. Sorti un peu fort avec astronaut , je ne veux offenser personne.

Les classes

C'est beaucoup plus agréable d'avoir du code auto-documenté que de l'écrire en externe ou dans un commentaire. C'est l'une des raisons pour lesquelles j'aime toutes les restrictions de TypeScript et essentiellement ce que nous demandons dans cette fonctionnalité - juste une autre façon de documenter le code en utilisant le langage lui-même.

Eh bien, voici une autre façon d'écrire votre exemple:

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);
    }
}

Moins OO-ish mais vous avez en fait une vraie classe privée.

@jbondc C'est génial que vous ayez trouvé votre chemin pour des cours privés! Laissez les autres utiliser une autre approche (classe exportable avec constructeur privé). Vous pouvez maintenant observer qu'il peut y avoir des fonctionnalités qui satisfont les besoins du groupe A et ne perturbent pas le travail du groupe B. :)

+1

+1

Est-ce toujours sur le radar? Le PR est trop vieux!

+1

+1

+1

: +1: peut être utile pour le modèle de conception d'usine

Toutes nos excuses s'il s'agit d'une répétition ou juste en retard à la fête, mais nous utilisons le modèle suivant pour obtenir des constructeurs privés:

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.

Notez que cela peut être encore plus arrangé en utilisant le modificateur readonly récemment fusionné.

@myitcv c'est plutôt cool de l'appliquer pour l'instant ... probablement la meilleure façon mentionnée à mon avis. C'est encore trop verbeux et prend quelques secondes pour comprendre ce qui se passe, c'est pourquoi un modificateur d'accès serait toujours très agréable sur les constructeurs.

Personnellement, je marque juste tous mes futurs constructeurs privés avec des commentaires // todo: make private once supported et n'appelle pas les constructeurs. Ce sera bien une fois que cette fonctionnalité sera disponible afin d'obtenir une application et une meilleure documentation avec un modificateur d'accès.

@dsherret

C'est encore trop verbeux et prend quelques secondes pour comprendre ce qui se passe

D'accord. Nous ne souffrons pas trop de cette charge cognitive car ces classes sont générées par du code. Ainsi, l'interface avec le programmeur est en fait agréable et simple.

Salut, cette fonctionnalité va-t-elle être publiée dans une future version de dactylographié?
À partir de maintenant, essayer de déclarer un constructeur privé ou protégé me donne cette erreur dans le typographie 1.8.10:
erreur TS1089: le modificateur «privé» ne peut pas apparaître sur une déclaration de constructeur.

Ahh, tant pis. Je viens de découvrir la feuille de route qui stipule que cette fonctionnalité sera incluse dans TypScript 2.0.

Ahh, tant pis. Je viens de découvrir la feuille de route qui stipule que cette fonctionnalité sera incluse dans TypScript 2.0.

De plus, le jalon défini sur TypeScript 2.0 et le libellé Fixed indiqueraient qu'il est inclus. Tout ce qui a une étiquette de Fixed est généralement inclus dans master et disponible via npm install typescript@next .

J'utilise TypeScript 2.0.2 RC et j'obtiens toujours TS1089 lorsque j'essaye de créer un constructeur private . Est-ce que je fais mal ou est-ce que ce n'est tout simplement pas réglé?

J'utilise TypeScript 2.0.2 RC et j'obtiens toujours TS1089 lorsque j'essaye de créer un constructeur privé. Est-ce que je fais mal ou est-ce que ce n'est tout simplement pas réglé?

Ça marche pour moi. assurez-vous que votre alias de commande pointe vers la bonne version et que votre éditeur est mis à jour pour utiliser la dernière version TS.

J'ai trouvé le problème. C'était la faute de gulp-typescript qui utilisait la mauvaise version de tsc malgré mes spécifications dans package.json et ce qui a été résolu sur le PATH .

Pour toute autre personne ayant ce problème, ma solution était de modifier mon gulpfile.js et ...

  1. require TypeScript avant gulp-typescript comme suit:
// Tool-Chain: Scripts
var tsc = require("typescript");
var typescript = require('gulp-typescript');
  1. Fournissez le compilateur comme remplacement lors de la définition de ma tâche de construction:
// 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)))
});
Cette page vous a été utile?
0 / 5 - 0 notes