¿Miembros privados en CoffeeScript?


84

¿Alguien sabe cómo hacer miembros privados y no estáticos en CoffeeScript? Actualmente estoy haciendo esto, que solo usa una variable pública que comienza con un guión bajo para aclarar que no debe usarse fuera de la clase:

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

  getName: -> @_name

Poner la variable en la clase la convierte en un miembro estático, pero ¿cómo puedo hacer que no sea estática? ¿Es incluso posible sin volverse "elegante"?

Respuestas:


20

¿Es incluso posible sin volverse "elegante"?

Es triste decirlo, tendrías que ser elegante .

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

Recuerde, "Es solo JavaScript".


1
... y entonces tienes que hacerlo como lo harías en JS. Es fácil olvidarlo cuando está escondido detrás de todo ese azúcar, ¡gracias!
thejh

4
¿Es eso realmente privado? Aún puede acceder a él fuera de la clase. a = Thing ('a') luego a.getName () devuelve el valor y a.getName = -> 'b' lo establece.
Amir

4
@Amir: namesolo es visible desde el interior del cierre del constructor. Mire esta esencia: gist.github.com/803810
thejh

13
También cabe destacar que @getName = -> nameparece romper cualquier posible herencia de la getNamefunción.
Kendall Hopkins

12
Esta respuesta es incorrecta: aquí, getNamees pública y namesolo se puede acceder a ella desde la función constructora, por lo que no es realmente "privada" para el objeto.
tothemario

203

las clases son solo funciones, por lo que crean ámbitos. todo lo definido dentro de este alcance no será visible desde el exterior.

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 esto en lo siguiente:

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

9
Cabe señalar que estas variables privadas no están disponibles para las subclases.
Ceasar Bautista

45
También debe tenerse en cuenta que los métodos 'privados' deberán llamarse como foo.call(this)para thisque sean la instancia de la función. Esta es la razón por la que intentar emular la herencia clásica en JavaScript se vuelve complicado.
Jon Wingfield

3
Otro inconveniente es que no tendrá acceso a métodos "privados" para las pruebas unitarias ..
nuc

16
Los métodos privados de @nuc son detalles de implementación que se prueban a través de los métodos públicos que los llaman, lo que quiere decir que los métodos privados no deben probarse por unidad. Si un método privado parece que debería ser testeable por unidades, entonces quizás debería ser un método público. Consulte esta publicación para obtener una buena explicación también stackoverflow.com/questions/5750279/…
mkelley33

2
También debe tenerse en cuenta que necesitará definir sus variables "privadas" arriba donde se usan en funciones "públicas". De lo contrario, CoffeeScript se confundirá y creará nuevas vardeclaraciones internas que las sombrearán.
Andrew Miner

11

Me gustaría mostrar algo aún más elegante

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

Ahora puedes hacer

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

2

Hay un problema con la respuesta de Vitaly y es que no puede definir las variables que desea que sean exclusivas del alcance, si crea un nombre privado de esa manera y luego lo cambia, el valor del nombre cambiaría para cada instancia de la clase, así que hay una forma en que podemos resolver ese 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

No es imposible acceder a la matriz de nombres desde afuera a menos que use getNames

Prueba esto

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

Amo esta implementación. ¿Algún inconveniente?
Erik5388

2

Aquí hay una solución que se basa en varias de las otras respuestas aquí más https://stackoverflow.com/a/7579956/1484513 . Almacena las variables de instancia privada (no estáticas) en una matriz de clase privada (estática) y utiliza un ID de objeto para saber qué elemento de esa matriz contiene los datos que pertenecen a cada instancia.

# 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

2

Aquí está el mejor artículo que encontré sobre el ajuste public static members, private static members, public and private members, y algunas otras cosas relacionadas. Cubre tanto los detalles y jsfrente a coffeela comparación. Y por razones históricas , aquí está el mejor ejemplo de código:

# 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

1

A continuación se explica cómo puede declarar miembros privados y no estáticos en Coffeescript.
Para obtener una referencia completa, puede consultar 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

1

la "clase" en los guiones de café conduce a un resultado basado en prototipos. Entonces, incluso si usa una variable privada, se comparte entre instancias. Puedes hacerlo:

EventEmitter = ->
  privateName = ""

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

.. lleva a

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

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

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

Pero tenga cuidado de poner los miembros privados antes que las funciones públicas, porque el script de café devuelve las funciones públicas como objeto. Mira el Javascript compilado:

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

  return {
    setName: function(name) {
      return privateName = name;
    },
    getName: function() {
      return privateName;
    }
  };
};

0

Dado que el script de café se compila en JavaScript, la única forma en que puede tener variables privadas es a través de cierres.

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

Esto se compilará a través del siguiente 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

Por supuesto, esto tiene las mismas limitaciones que todas las demás variables privadas que puede tener mediante el uso de cierres, por ejemplo, los métodos recién agregados no tienen acceso a ellos ya que no se definieron en el mismo ámbito.


9
Eso es una especie de miembro estático. e = new Animal(5);f = new Animal(1);e.test()alerta uno, quiero cinco.
thejh

@thejh Oh, lo siento, veo el error ahora, creo que fue demasiado tarde para pensar en esto ayer.
Ivo Wetzel

@thejh Eso me sucedió, intenté resolver ese problema en mi respuesta.
iConnor

0

No puede hacerlo fácilmente con las clases CoffeeScript, porque utilizan el patrón de constructor de Javascript para crear clases.

Sin embargo, podrías decir algo como esto:

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}"

Pero pierde la grandeza de las clases de CoffeeScript, porque no puede heredar de una clase creada de esa manera de otra manera que usando extend () nuevamente. instanceof dejará de funcionar y los objetos creados de esta manera consumirán un poco más de memoria. Además, ya no debe usar las nuevas y súper palabras clave.

El punto es que los cierres deben crearse cada vez que se crea una instancia de una clase. Los cierres de miembros en las clases CoffeeScript puras se crean solo una vez, es decir, cuando se construye el "tipo" de tiempo de ejecución de la clase.


-3

Si solo desea separar los miembros privados de los públicos, simplemente envuélvalo en $ variable

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

y use @$.requirements

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.