Membri privati ​​in CoffeeScript?


84

Qualcuno sa come creare membri privati ​​e non statici in CoffeeScript? Attualmente lo sto facendo, che utilizza solo una variabile pubblica che inizia con un carattere di sottolineatura per chiarire che non dovrebbe essere utilizzata al di fuori della classe:

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

  getName: -> @_name

Mettere la variabile nella classe la rende un membro statico, ma come posso renderla non statica? È anche possibile senza "fantasia"?

Risposte:


20

È anche possibile senza "fantasia"?

Triste a dirsi, dovresti essere fantasioso .

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

Ricorda: "È solo JavaScript".


1
... e quindi devi farlo come faresti in JS. Facile dimenticarlo quando è nascosto dietro tutto quello zucchero, grazie!
thejh

4
È davvero privato? Puoi comunque accedervi fuori dal corso. a = Thing ('a') quindi a.getName () restituisce il valore e a.getName = -> 'b' lo imposta.
Amir

4
@Amir: nameè visibile solo dall'interno della chiusura del costruttore. Guarda questa sintesi
thejh

13
Vale anche la pena notare che @getName = -> namesembra rompere ogni possibile eredità della getNamefunzione.
Kendall Hopkins

12
Questa risposta è sbagliata: qui, getNameè pubblica ed nameè accessibile solo dalla funzione di costruzione, quindi non è realmente "privata" per l'oggetto.
tothemario

203

le classi sono solo funzioni, quindi creano ambiti. tutto ciò che è definito all'interno di questo ambito non sarà visibile dall'esterno.

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 lo compila come segue:

(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
Va notato che queste variabili private non sono disponibili per le sottoclassi.
Ceasar Bautista

45
Va anche notato che i metodi "privati" dovranno essere chiamati come foo.call(this)per thisessere l'istanza della funzione. Questo è il motivo per cui provare a emulare l'ereditarietà classica in JavaScript diventa complicato.
Jon Wingfield,

3
Un altro svantaggio è che non avrai accesso a metodi "privati" per i test unitari ..
nuc

16
I metodi privati ​​@nuc sono dettagli di implementazione che vengono testati tramite i metodi pubblici che li chiamano, vale a dire che i metodi privati ​​non dovrebbero essere testati in unità. Se un metodo privato sembra che dovrebbe essere testabile in unità, forse dovrebbe essere un metodo pubblico. Vedi questo post per una buona spiegazione così stackoverflow.com/questions/5750279/...
mkelley33

2
Va anche notato che dovrai definire le tue variabili "private" sopra dove vengono utilizzate nelle funzioni "pubbliche". Altrimenti, CoffeeScript si confonderà e creerà nuove vardichiarazioni interne che li ombreggeranno.
Andrew Miner

11

Vorrei mostrare qualcosa di ancora più elaborato

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

Adesso puoi farlo

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

C'è un problema con la risposta di Vitaly ed è che non puoi definire variabili che vuoi essere univoche per l'ambito, se hai creato un nome privato in quel modo e poi lo hai cambiato, il valore del nome cambierebbe per ogni singola istanza della classe, quindi c'è un modo in cui possiamo risolvere il 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

Non è impossibile accedere all'array dei nomi dall'esterno a meno che non si utilizzi getNames

Provalo

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 compilato

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 questa implementazione. Qualche inconveniente?
Erik5388

2

Ecco una soluzione che si basa su molte delle altre risposte qui oltre a https://stackoverflow.com/a/7579956/1484513 . Memorizza le variabili di istanza privata (non statica) in un array di classe privata (statico) e utilizza un ID oggetto per sapere quale elemento di quell'array contiene i dati appartenenti a ciascuna istanza.

# 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

Ecco l'articolo migliore che ho trovato su impostazione public static members, private static members, public and private members, e alcune altre cose correlate. Copre molto particolari e jscontro coffeeconfronto. E per i motivi storici ecco il miglior esempio di codice da esso:

# 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

Ecco come puoi dichiarare membri privati ​​e non statici in Coffeescript
Per riferimento completo, puoi dare un'occhiata 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

1

"classe" negli script del caffè porta a un risultato basato su prototipi. Quindi, anche se utilizzi una variabile privata, questa viene condivisa tra le istanze. Puoi farlo:

EventEmitter = ->
  privateName = ""

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

.. porta a

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

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

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

Ma attenzione a mettere i membri privati ​​prima delle funzioni pubbliche, perché coffee script restituisce le funzioni pubbliche come oggetto. Guarda il Javascript compilato:

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

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

0

Poiché lo script coffee si compila fino a JavaScript, l'unico modo per avere variabili private è attraverso le chiusure.

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

Questo verrà compilato tramite il seguente 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

Ovviamente questo ha tutte le stesse limitazioni di tutte le altre variabili private che puoi avere attraverso l'uso di chiusure, ad esempio, i metodi appena aggiunti non hanno accesso ad esse poiché non sono state definite nello stesso ambito.


9
È una specie di membro statico. e = new Animal(5);f = new Animal(1);e.test()avvisa uno, ne voglio cinque.
thejh

@thejh Oh, scusa allora, vedo l'errore ora, immagino che ieri fosse troppo tardi per pensare a queste cose.
Ivo Wetzel

@thejh È successo a me, ho tentato di risolvere il problema nella mia risposta.
iConnor

0

Non puoi farlo facilmente con le classi CoffeeScript, perché usano il modello di costruzione Javascript per creare classi.

Tuttavia, potresti dire qualcosa del genere:

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

Ma perdi la grandezza delle classi CoffeeScript, perché non puoi ereditare da una classe creata in quel modo in nessun altro modo se non usando di nuovo extent (). instanceof smetterà di funzionare e gli oggetti creati in questo modo consumano un po 'più di memoria. Inoltre, non devi più utilizzare le parole chiave nuove e super .

Il punto è che le chiusure devono essere create ogni volta che viene creata un'istanza di una classe. Le chiusure dei membri nelle classi CoffeeScript pure vengono create una sola volta, ovvero quando viene costruito il "tipo" di runtime della classe.


-3

Se vuoi separare solo i memebers privati ​​da quelli pubblici, racchiudilo in $ variable

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

e utilizzare @$.requirements

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.