Typescript: Permitir visibilidade nos construtores

Criado em 13 mar. 2015  ·  42Comentários  ·  Fonte: microsoft/TypeScript

Acho que é um padrão bastante comum ter um método de fábrica estático para criar uma classe e o construtor dessa classe sendo privado, de modo que você não pode instanciar a classe a menos que use o método de fábrica.

Fixed Suggestion help wanted

Comentários muito úteis

Bem, o mesmo se aplica a funções privadas nas aulas, certo? Não vejo por que não conseguimos obter um erro de compilação para acessar um construtor privado.

Todos 42 comentários

Uma vez que todas as "classes" são na verdade apenas funções e não existe uma função que não pode ser chamada, em qualquer lugar em que a classe esteja visível, você também pode usar o operador new nela.

No entanto, você pode tornar a classe não pública (por exemplo, em um módulo) e exportar uma interface para a classe. Isso abusa do fato de que as interfaces podem estender classes, mas não são diretamente instanciadas nem extensíveis.

Bem, o mesmo se aplica a funções privadas nas aulas, certo? Não vejo por que não conseguimos obter um erro de compilação para acessar um construtor privado.

@billccn Eu não gosto de matar pedidos de "JS permite, então você não será capaz de esconder".
Uma coisa é proteger completamente em TS e JS gerado, outra coisa é proteger isso a ponto de gerar JS. Como você explicou, a proteção total não é possível, mas deve ser possível ter uma visibilidade diferente verificada pelo compilador.
Se você não gosta de modificadores de visibilidade, use o público padrão em todos os lugares; há outros que acham esse conceito útil.

Sim, acho que se os campos privados forem implementados apenas como uma verificação do compilador, então provavelmente podem ser estendidos aos construtores. No entanto, a solução alternativa baseada na interface já funciona.

: +1: Há momentos em que quero forçar o programador a usar os métodos de fábrica para facilitar a leitura do código. A solução alternativa baseada na interface cria muito ruído no código.

Acho que apenas a verificação do compilador é o caminho a percorrer.

Aceito, aceitando PRs

Para esclarecer, uma classe com um construtor privado pode ser extensível?

ou seja, isso geraria um erro?

class A {
    private constructor() {
    }
}

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

em caso afirmativo, permitiríamos isso:

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

    }
}

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

Da experiência de desenvolvedor não JS, esse é o comportamento esperado.

No primeiro exemplo, B deve ser um erro porque sua super chamada implícita é ilegal. Portanto, uma classe com private constructor é efetivamente sealed / final .

No segundo exemplo, a declaração de A deve ser um erro porque todas as sobrecargas de um construtor, e sua implementação, devem ter a mesma visibilidade (mesma regra que temos para métodos).

Veja também # 471. É realmente necessário permitir que os construtores sejam privados ou serão protegidos?

@benliddicott às vezes é útil para forçar um singleton ou para forçar o programador a usar um dos métodos estáticos para criar um objeto, porque às vezes pode ser mais legível.

Veja aqui .

@dsherret protected atende a todas essas necessidades.

Mas você não pode ter certeza de que um usuário downstream nunca terá uma necessidade legítima de herdar de sua classe. O único efeito de private é evitar que seus usuários downstream atendam a uma necessidade que você não antecipou.

@benliddicott Às vezes, a única coisa que você deseja é que uma classe não seja extensível. Refiro-me ao item 15 eficaz do Java para minimizar a mutabilidade, especialmente:

"2. Certifique-se de que a classe não pode ser estendida. Isso evita que subclasses descuidadas ou maliciosas comprometam o comportamento imutável da classe, comportando-se como se o estado do objeto tivesse mudado. A prevenção da subclasse geralmente é realizada tornando a classe final, mas é uma alternativa que discutiremos mais tarde.

Atualmente não há suporte para final / sealed no TypeScript, portanto, um construtor privado é a única maneira de obter uma classe imutável da perspectiva do sistema de tipos. (Embora eu recomende que as pessoas também congelem o objeto no construtor.)

@billccn , a opinião desse autor é interessante. Assim é a ideia de que a opinião do redator da biblioteca deve se sobrepor à opinião do usuário da biblioteca. Minha própria experiência mostra que os escritores de bibliotecas não sabem quando usar private, e usam-no em excesso, causando dor de cabeça para os usuários, simplesmente porque eles acreditam que sabem como sua biblioteca será usada, quando na verdade não sabem.

Mas ao invés de uma linguagem estática como Java, uma comparação mais adequada seria Perl, outra linguagem dinâmica: http://www.perlmonks.org/?node_id=437623

Uma das razões pelas quais perl não tem modificadores de acesso, como público, privado e protegido, é porque é reconhecido que muitas vezes eles atrapalham a realização do trabalho: o que foi imaginado pelo designer original não tem nada a ver com o que você quer fazer com isso. Da mesma forma, projete para ter flexibilidade - embora você possa não se ver precisando disso agora, a próxima pessoa pode ver que é incrivelmente útil resolver esse novo problema e abençoará seu gênio por desenvolver essa flexibilidade ;-)

e:

Perl não tem uma paixão por privacidade forçada. Ele preferiria que você ficasse fora da sala porque não foi convidado, não porque ele tem uma espingarda

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

Na verdade, o JavaScript é o mesmo e - sim - o TypeScript é o mesmo, em quase todos os aspectos. No texto datilografado, você pode acessar membros privados perfeitamente - usando a porta de escape apropriadamente chamada: obj["propertyName"] .

Se, como escritor de biblioteca, você suspeita que não é aconselhável chamar um método ou herdar de um objeto, diga ao usuário que não é aconselhável. Mas não os evite - eles podem saber melhor do que você, afinal.

Eu não entendo a discussão sobre "protegido ser suficiente". Se TS tem o conceito de visibilidade e posso aplicar esse conceito a construtores, a resposta é "não é suficiente".

Se modificadores de acesso são permitidos no construtor, então acho que ele deve ter um comportamento consistente com outros modificadores e permitir private.

Membros privados em geral são úteis. Eles permitem que você organize e refatore os detalhes de implementação de uma classe sem se preocupar em causar efeitos colaterais fora da classe.

Com construtores privados, posso querer forçar os desenvolvedores da minha equipe a programar de uma determinada maneira. Por exemplo, eu posso querer forçá-los a usar o método estático aqui porque é mais legível e forçá-los a não estender esta 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");

Com construtores privados, posso querer forçar os desenvolvedores da minha equipe a programar de uma determinada maneira.

Esse é um problema de gerenciamento, não um problema de design de linguagem.

@benliddicott O semelhante que você pode dizer sobre descritores de tipo em variáveis ​​:) Se você não gostar do recurso, use apenas JS. OU
Use TS e crie algumas regras semelhantes a lint que proíbam o uso de private no construtor. Para parafrasear seu último comentário: "Isso é um problema de ferramenta, não de design de linguagem".

@benliddicott se algo não for possível fazer, eu não terei que enviar de volta quando estiver errado após fazer uma revisão do código. Isso economiza tempo.

Dizer ao compilador exatamente como o código deve ser usado corretamente é um recurso que fornece o feedback adequado ao desenvolvedor que o usa enquanto está programando.

@dsherret Não, é uma restrição arbitrária que autoriza Architecture astronauts : -1:

@jbondc Esse "astronautas de arquitetura" é um argumento razoável? Você tenta ofender ou elogiar as pessoas que desejam esse recurso?

@jbondc Não acredito que o termo "astronauta da arquitetura" seja relevante aqui. Isso não é falar sobre pessoas que passam mais tempo pensando em arquitetura do que escrevendo código ? A decisão de usar um construtor privado pode ser rápida e simples, como usar quase qualquer recurso em uma linguagem como o TypeScript.

Além disso, não acho que seja "arbitrário" porque pode ajudar a prevenir o uso indevido do código. Talvez eu queira lembrar a mim mesmo ou a minha equipe de não usar o construtor e, em vez disso, usar um dos métodos estáticos ou forçar o uso de um método estático para impor um singleton. É rápido e simples e recebo o feedback adequado do compilador. Se isso for arbitrário, você pode argumentar que muitos aspectos de uma linguagem são arbitrários. Talvez você tenha mais a dizer sobre ser arbitrário do que você não expressou em seu comentário?

Este é um recurso de linguagem que, se as pessoas não gostarem, é simples para elas não usarem.

@dsherret Não é o suficiente para documentar em vez de impor outra restrição?

Isso complica significativamente a herança múltipla, consulte o nº 4805 (o que para mim parece uma reflexão tardia agora). Já expressei alguns dos meus pensamentos no # 3578, então não vou me preocupar em fazer isso novamente. Saiu um pouco forte com astronaut , não quero ofender ninguém.

Classes @jbondc com construtores privados não poderiam ser herdadas. Eles são essencialmente lacrados e, portanto, a herança, quanto mais a herança múltipla, não deve funcionar com isso.

É muito mais agradável ter um código autodocumentado do que escrevê-lo externamente ou em um comentário. Esse é um dos motivos pelos quais gosto de todas as restrições do TypeScript e, essencialmente, do que estamos pedindo neste recurso - apenas outra maneira de documentar o código usando a própria linguagem.

Bem, aqui está outra maneira de escrever seu exemplo:

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, mas você realmente tem uma aula particular 'genuinamente' real.

@jbondc Que bom que você encontrou seu caminho para as aulas particulares! Deixe que outros usem outra abordagem (classe exportável com construtor privado). Agora você pode observar que pode haver recursos que satisfaçam as necessidades do grupo A e não interrompam o trabalho do grupo B. :)

+1

+1

Isso ainda está no radar? O PR é hella stale!

+1

+1

+1

: +1: pode ser útil para o padrão de design de fábrica

Pedimos desculpas se isso for uma repetição ou apenas atrasar para a festa, mas estamos usando o seguinte padrão para conseguir construtores 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.

Observe que isso pode ser organizado ainda mais usando o modificador readonly recentemente mesclado.

@myitcv é muito legal aplicá-lo por enquanto ... provavelmente a melhor maneira mencionada na minha opinião. Ainda é muito prolixo e leva alguns segundos para entender o que está acontecendo, e é por isso que um modificador de acesso ainda seria muito bom em construtores.

Pessoalmente, estou apenas marcando todos os meus futuros construtores privados com // todo: make private once supported comentários e não chamando os construtores. Será bom quando esse recurso vier para obter alguma aplicação e melhor documentação com um modificador de acesso.

@dsherret

Ainda é muito prolixo e leva alguns segundos para entender o que está acontecendo

Acordado. Não sofremos muito com essa carga cognitiva porque essas classes são geradas por código. Portanto, a interface para o programador é realmente agradável e simples.

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

obrigado @AbubakerB!

Olá, este recurso será lançado em alguma versão futura do texto datilografado?
A partir de agora, tentar declarar um construtor privado ou protegido me dá este erro no texto datilografado 1.8.10:
Erro TS1089: o modificador 'privado' não pode aparecer em uma declaração do construtor.

Ahh, deixa pra lá. Acabei de descobrir o roteiro que afirma que esse recurso será incluído no texto datilografado 2.0.

Ahh, deixa pra lá. Acabei de descobrir o roteiro que afirma que esse recurso será incluído no texto datilografado 2.0.

Além disso, o marco sendo definido como TypeScript 2.0 e o rótulo Fixed indicam que ele está incluído. Qualquer coisa com o rótulo Fixed geralmente está incluída no mestre e disponível via npm install typescript@next .

Estou usando o TypeScript 2.0.2 RC e ainda obtenho o TS1089 quando tento fazer um construtor private . Estou fazendo errado ou simplesmente não foi corrigido?

Estou usando o TypeScript 2.0.2 RC e ainda obtenho o TS1089 quando tento fazer um construtor privado. Estou fazendo errado ou simplesmente não foi corrigido?

Está funcionando para mim. certifique-se de que seu alias de comando esteja apontando para a versão correta e que seu editor esteja atualizado para usar a versão TS mais recente.

Eu encontrei o problema. Foi a falha de gulp-typescript que estava usando a versão errada de tsc apesar das minhas especificações em package.json e o que foi resolvido em PATH .

Para qualquer outra pessoa com esse problema, minha solução foi editar meu gulpfile.js e ...

  1. require TypeScript antes do gulp-typescript da seguinte forma:
// Tool-Chain: Scripts
var tsc = require("typescript");
var typescript = require('gulp-typescript');
  1. Fornece o compilador como uma substituição ao definir minha tarefa de compilação:
// 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)))
});
Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

dlaberge picture dlaberge  ·  3Comentários

seanzer picture seanzer  ·  3Comentários

uber5001 picture uber5001  ·  3Comentários

Antony-Jones picture Antony-Jones  ·  3Comentários

blendsdk picture blendsdk  ·  3Comentários