oop - ¿Miembros privados en CoffeeScript?
instance-variables private-members (11)
¿Alguien sabe cómo hacer miembros privados 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 lo convierte en un miembro estático, pero ¿cómo puedo hacerlo no estático? ¿Es posible incluso sin ser "elegante"?
¿Es posible incluso sin ser "elegante"?
Es triste decirlo, tendrías que ser elegante .
class Thing extends EventEmitter
constructor: (name) ->
@getName = -> name
Recuerde: "Es solo JavaScript".
"clase" en los scripts 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 a los miembros privados antes de las funciones públicas, porque Coffee Script 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;
}
};
};
Aquí hay una solución que se basa en varias de las otras respuestas aquí más https://.com/a/7579956/1484513 . Almacena las variables de instancia privada (no estáticas) en una matriz de clase privada (estática) y utiliza una 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
Aquí se muestra cómo puede declarar miembros privados no estáticos en Coffeescript
Para una referencia completa, puede echar un vistazo a 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
Como Coffee Script 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 todas las mismas limitaciones que todas las otras 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.
Hay un problema con la respuesta de Vitaly y es que no puede definir las variables que desea que sean únicas para el alcance, si creó un nombre privado de esa manera y luego lo cambió, el valor del nombre cambiaría para cada instancia de la clase, entonces hay una manera 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
Pruebe 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;
};
Me gustaría mostrar algo incluso 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
No puedes hacerlo fácilmente con las clases de CoffeeScript, porque usan 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 se pierde la grandeza de las clases de CoffeeScript, porque no se puede heredar de una clase creada de esa forma de otra manera que no sea usando extend () nuevamente. instanceof dejará de funcionar, y los objecs creados de esta manera consumen un poco más de memoria. Además, ya no debe usar las palabras clave nuevas y súper .
El punto es que los cierres deben crearse cada vez que se crea una instancia de una clase. Los cierres de miembro en clases de CoffeeScript puro se crean solo una vez, es decir, cuando se construye el "tipo" de tiempo de ejecución de clase.
Si solo quieres miembros privados separados del público, simplemente envuélvelo en $ variable
$:
requirements:
{}
body: null
definitions: null
y use @$.requirements
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);
Here''s el mejor artículo que encontré sobre la configuración de public static members
private static members
public and private members
, private static members
, private static members
public and private members
, y algunas otras cosas relacionadas. Cubre muchos detalles y coffee
comparación js
vs. coffee
. Y por las 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