Membros privados no CoffeeScript?

84

Alguém sabe como tornar membros privados e não estáticos no CoffeeScript? Atualmente estou fazendo isso, que usa apenas uma variável pública começando com um sublinhado para esclarecer que não deve ser usada fora da classe:

class Thing extends EventEmitter
  constructor: (@_name) ->

  getName: -> @_name

Colocar a variável na classe a torna um membro estático, mas como posso torná-la não estática? É mesmo possível sem ficar "chique"?

thejh
fonte

Respostas:

20

É mesmo possível sem ficar "chique"?

É triste dizer, mas você tem que ser chique .

class Thing extends EventEmitter
  constructor: (name) ->
    @getName = -> name

Lembre-se: "É apenas JavaScript."

matyr
fonte
1
... e então você tem que fazer isso como faria em JS. Fácil de esquecer quando está escondido atrás de todo aquele açúcar, obrigado!
thejh
4
Isso é realmente privado? Você ainda pode acessá-lo fora da classe. a = Thing ('a') então a.getName () retorna o valor e a.getName = -> 'b' o define.
Amir
4
@Amir: namesó é visível de dentro do fechamento do construtor. Olhe para esta essência: gist.github.com/803810
thejh
13
Também é importante notar que @getName = -> nameparece quebrar qualquer herança possível da getNamefunção.
Kendall Hopkins,
12
Esta resposta está errada: aqui, getNameé público e namesó pode ser acessado pela função do construtor, portanto, não é realmente "privado" para o objeto.
tothemario
203

classes são apenas funções, portanto, criam escopos. tudo definido dentro deste escopo não será visível de fora.

class Foo
  # this will be our private method. it is invisible
  # outside of the current scope
  foo = -> "foo"

  # this will be our public method.
  # note that it is defined with ':' and not '='
  # '=' creates a *local* variable
  # : adds a property to the class prototype
  bar: -> foo()

c = new Foo

# this will return "foo"
c.bar()

# this will crash
c.foo

coffeescript compila isso no seguinte:

(function() {
  var Foo, c;

  Foo = (function() {
    var foo;

    function Foo() {}

    foo = function() {
      return "foo";
    };

    Foo.prototype.bar = function() {
      return foo();
    };

    return Foo;

  })();

  c = new Foo;

  c.bar();

  c.foo();

}).call(this);
Vitaly Kushner
fonte
9
Deve-se notar que essas variáveis ​​privadas não estão disponíveis para subclasses.
Ceasar Bautista de
45
Também deve-se notar que os métodos 'privado' terá de ser chamado assim foo.call(this), a fim de thisser exemplo da função. É por isso que tentar emular a herança clássica em JavaScript fica complicado.
Jon Wingfield
3
Outra desvantagem é que você não terá acesso a métodos "privados" para teste de unidade ..
nuc
16
Os métodos privados @nuc são detalhes de implementação que são testados por meio dos métodos públicos que os chamam, o que significa que os métodos privados não devem ser testados por unidade. Se um método privado parece que deve ser testável por unidade, então talvez deva ser um método público. Veja este post para uma boa explicação também stackoverflow.com/questions/5750279/…
mkelley33
2
Também deve ser observado que você precisará definir suas variáveis ​​"privadas" acima de onde elas são usadas em funções "públicas". Caso contrário, o CoffeeScript ficará confuso e criará novas vardeclarações internas que irão ocultá-los.
Andrew Miner,
11

Eu gostaria de mostrar algo ainda mais sofisticado

class Thing extends EventEmitter
  constructor: ( nm) ->
    _name = nm
    Object.defineProperty @, 'name',
      get: ->
        _name
      set: (val) ->
        _name = val
      enumerable: true
      configurable: true

Agora você pode fazer

t = new Thing( 'Dropin')
#  members can be accessed like properties with the protection from getter/setter functions!
t.name = 'Dragout'  
console.log t.name
# no way to access the private member
console.log t._name
Tim Wu
fonte
2

Há um problema com a resposta de Vitaly: você não pode definir variáveis ​​que deseja que sejam exclusivas para o escopo, se você criar um nome privado dessa forma e depois alterá-lo, o valor do nome mudará para cada instância da classe, então há uma maneira de resolver esse problema

# create a function that will pretend to be our class 
MyClass = ->

    # this has created a new scope 
    # define our private varibles
    names = ['joe', 'jerry']

    # the names array will be different for every single instance of the class
    # so that solves our problem

    # define our REAL class
    class InnerMyClass 

        # test function 
        getNames: ->
            return names;

    # return new instance of our class 
    new InnerMyClass

Não é impossível acessar a matriz de nomes de fora, a menos que você use getNames

Teste isso

test = new MyClass;

tempNames = test.getNames()

tempNames # is ['joe', 'jerry']

# add a new value 
tempNames.push 'john'

# now get the names again 
newNames = test.getNames();

# the value of newNames is now 
['joe', 'jerry', 'john']

# now to check a new instance has a new clean names array 
newInstance = new MyClass
newInstance.getNames() # === ['joe', 'jerry']


# test should not be affected
test.getNames() # === ['joe', 'jerry', 'john']

Javascript compilado

var MyClass;

MyClass = function() {
  var names;
  names = ['joe', 'jerry'];
  MyClass = (function() {

    MyClass.name = 'MyClass';

    function MyClass() {}

    MyClass.prototype.getNames = function() {
      return names;
    };

    return MyClass;

  })();
  return new MyClass;
};
iConnor
fonte
Eu amo essa implementação. Quaisquer desvantagens?
Erik5388
2

Aqui está uma solução que se baseia em várias das outras respostas aqui, além de https://stackoverflow.com/a/7579956/1484513 . Ele armazena as variáveis ​​de instância privada (não estáticas) em uma matriz de classe privada (estática) e usa um ID de objeto para saber qual elemento dessa matriz contém os dados pertencentes a cada instância.

# Add IDs to classes.
(->
  i = 1
  Object.defineProperty Object.prototype, "__id", { writable:true }
  Object.defineProperty Object.prototype, "_id", { get: -> @__id ?= i++ }
)()

class MyClass
  # Private attribute storage.
  __ = []

  # Private class (static) variables.
  _a = null
  _b = null

  # Public instance attributes.
  c: null

  # Private functions.
  _getA = -> a

  # Public methods.
  getB: -> _b
  getD: -> __[@._id].d

  constructor: (a,b,@c,d) ->
    _a = a
    _b = b

    # Private instance attributes.
    __[@._id] = {d:d}

# Test

test1 = new MyClass 's', 't', 'u', 'v'
console.log 'test1', test1.getB(), test1.c, test1.getD()  # test1 t u v

test2 = new MyClass 'W', 'X', 'Y', 'Z'
console.log 'test2', test2.getB(), test2.c, test2.getD()  # test2 X Y Z

console.log 'test1', test1.getB(), test1.c, test1.getD()  # test1 X u v

console.log test1.a         # undefined
console.log test1._a        # undefined

# Test sub-classes.

class AnotherClass extends MyClass

test1 = new AnotherClass 's', 't', 'u', 'v'
console.log 'test1', test1.getB(), test1.c, test1.getD()  # test1 t u v

test2 = new AnotherClass 'W', 'X', 'Y', 'Z'
console.log 'test2', test2.getB(), test2.c, test2.getD()  # test2 X Y Z

console.log 'test1', test1.getB(), test1.c, test1.getD()  # test1 X u v

console.log test1.a         # undefined
console.log test1._a        # undefined
console.log test1.getA()    # fatal error
Waz
fonte
2

Aqui é o melhor artigo que encontrei sobre o ajuste public static members, private static members, public and private members, e algumas outras coisas relacionadas. Ele cobre muitos detalhes e jsvs. coffeecomparação. E por razões históricas , aqui está o melhor exemplo de código dele:

# CoffeeScript

class Square

    # private static variable
    counter = 0

    # private static method
    countInstance = ->
        counter++; return

    # public static method
    @instanceCount = ->
        counter

    constructor: (side) ->

        countInstance()

        # side is already a private variable, 
        # we define a private variable `self` to avoid evil `this`

        self = this

        # private method
        logChange = ->
            console.log "Side is set to #{side}"

        # public methods
        self.setSide = (v) ->
            side = v
            logChange()

        self.area = ->
            side * side

s1 = new Square(2)
console.log s1.area()   # output 4

s2 = new Square(3)
console.log s2.area()   # output 9

s2.setSide 4            # output Side is set to 4
console.log s2.area()   # output 16

console.log Square.instanceCount() # output 2
Iam pluntico
fonte
1

Aqui está como você pode declarar membros privados e não estáticos no Coffeescript
Para referência completa, você pode dar uma olhada em https://github.com/vhmh2005/jsClass

class Class

  # private members
  # note: '=' is used to define private members
  # naming convention for private members is _camelCase

  _privateProperty = 0

  _privateMethod = (value) ->        
    _privateProperty = value
    return

  # example of _privateProperty set up in class constructor
  constructor: (privateProperty, @publicProperty) ->
    _privateProperty = privateProperty
Hung Vo
fonte
1

"classe" em scripts de café leva a um resultado baseado em protótipo. Portanto, mesmo se você usar uma variável privada, ela será compartilhada entre as instâncias. Você consegue fazer isso:

EventEmitter = ->
  privateName = ""

  setName: (name) -> privateName = name
  getName: -> privateName

.. leva a

emitter1 = new EventEmitter()
emitter1.setName 'Name1'

emitter2 = new EventEmitter()
emitter2.setName 'Name2'

console.log emitter1.getName() # 'Name1'
console.log emitter2.getName() # 'Name2'

Mas tome cuidado para colocar os membros privados antes das funções públicas, porque o script de café retorna as funções públicas como objeto. Veja o Javascript compilado:

EventEmitter = function() {
  var privateName = "";

  return {
    setName: function(name) {
      return privateName = name;
    },
    getName: function() {
      return privateName;
    }
  };
};
Stefan Dohren
fonte
0

Como o script de café é compilado em JavaScript, a única maneira de ter variáveis ​​privadas é por meio de fechamentos.

class Animal
  foo = 2 # declare it inside the class so all prototypes share it through closure
  constructor: (value) ->
      foo = value

  test: (meters) ->
    alert foo

e = new Animal(5);
e.test() # 5

Isso será compilado por meio do seguinte JavaScript:

var Animal, e;
Animal = (function() {
  var foo; // closured by test and the constructor
  foo = 2;
  function Animal(value) {
    foo = value;
  }
  Animal.prototype.test = function(meters) {
    return alert(foo);
  };
  return Animal;
})();

e = new Animal(5);
e.test(); // 5

É claro que isso tem as mesmas limitações que todas as outras variáveis ​​privadas que você pode ter com o uso de fechamentos, por exemplo, métodos recém-adicionados não têm acesso a eles, pois não foram definidos no mesmo escopo.

Ivo Wetzel
fonte
9
Esse é um tipo de membro estático. e = new Animal(5);f = new Animal(1);e.test()alerta um, quero cinco.
thejh
@thejh Oh, desculpe então, eu vejo o erro agora, acho que era tarde demais para pensar sobre essas coisas ontem.
Ivo Wetzel
@thejh Isso aconteceu comigo, tentei resolver esse problema na minha resposta.
iConnor
0

Você não pode fazer isso facilmente com classes CoffeeScript, porque elas usam o padrão de construtor Javascript para criar classes.

No entanto, você pode dizer algo assim:

callMe = (f) -> f()
extend = (a, b) -> a[m] = b[m] for m of b; a

class superclass
  constructor: (@extra) ->
  method: (x) -> alert "hello world! #{x}#{@extra}"

subclass = (args...) -> extend (new superclass args...), callMe ->
  privateVar = 1

  getter: -> privateVar
  setter: (newVal) -> privateVar = newVal
  method2: (x) -> @method "#{x} foo and "

instance = subclass 'bar'
instance.setter 123
instance2 = subclass 'baz'
instance2.setter 432

instance.method2 "#{instance.getter()} <-> #{instance2.getter()} ! also, "
alert "but: #{instance.privateVar} <-> #{instance2.privateVar}"

Mas você perde a grandeza das classes CoffeeScript, porque você não pode herdar de uma classe criada dessa forma de outra forma que não seja usando extend () novamente. instanceof irá parar de funcionar, e objetos criados desta forma consomem um pouco mais de memória. Além disso, você não deve usar mais as novas e super palavras - chave.

A questão é que os encerramentos devem ser criados sempre que uma classe é instanciada. Os fechamentos de membro em classes CoffeeScript puras são criados apenas uma vez - ou seja, quando o "tipo" de tempo de execução da classe é construído.

Jaakko Salomaa
fonte
-3

Se você quiser separar apenas os membros privados dos públicos, apenas envolva-os em $ variable

$:
        requirements:
              {}
        body: null
        definitions: null

E use @$.requirements

Borovsky
fonte