Membres privés dans CoffeeScript?

84

Quelqu'un sait-il comment créer des membres privés et non statiques dans CoffeeScript? Actuellement, je fais cela, qui utilise simplement une variable publique commençant par un trait de soulignement pour clarifier qu'elle ne doit pas être utilisée en dehors de la classe:

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

  getName: -> @_name

Mettre la variable dans la classe en fait un membre statique, mais comment puis-je la rendre non statique? Est-ce même possible sans «fantaisie»?

thejh
la source

Réponses:

20

Est-ce même possible sans «fantaisie»?

C'est triste à dire, il faut être chic .

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

N'oubliez pas: "C'est juste du JavaScript"

matyr
la source
1
... et vous devez donc le faire comme vous le feriez dans JS. Facile de l'oublier quand il est caché derrière tout ce sucre, merci!
thejh
4
Est-ce vraiment privé? Vous pouvez toujours y accéder en dehors de la classe. a = Thing ('a') puis a.getName () renvoie la valeur et a.getName = -> 'b' la définit.
Amir le
4
@Amir: namen'est visible que de l'intérieur de la fermeture du constructeur. Regardez ceci: gist.github.com/803810
thejh
13
A noter également qui @getName = -> namesemble rompre tout héritage possible de la getNamefonction.
Kendall Hopkins le
12
Cette réponse est fausse: ici, getNameest public, et namen'est accessible qu'à partir de la fonction constructeur, donc ce n'est pas vraiment "privé" pour l'objet.
tothemario
203

les classes ne sont que des fonctions, elles créent donc des portées. tout ce qui est défini à l'intérieur de cette portée ne sera pas visible de l'extérieur.

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 compile ceci comme suit:

(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
la source
9
Il convient de noter que ces variables privées ne sont pas disponibles pour les sous-classes.
Ceasar Bautista
45
Il convient également de noter que les méthodes «privées» devront être appelées comme foo.call(this)pour thisêtre l'instance de la fonction. C'est pourquoi essayer d'émuler l'héritage classique en JavaScript devient poilu.
Jon Wingfield
3
Un autre inconvénient est que vous n'aurez pas accès aux méthodes "privées" pour les tests unitaires.
nuc
16
Les méthodes privées @nuc sont des détails d'implémentation qui sont testés via les méthodes publiques qui les appellent, c'est-à-dire que les méthodes privées ne doivent pas être testées à l'unité. Si une méthode privée semble devoir être testable unitaire, alors peut-être que ce devrait être une méthode publique. Voir cet article pour une bonne explication ainsi que stackoverflow.com/questions/5750279/…
mkelley33
2
Il convient également de noter que vous devrez définir vos variables "privées" ci-dessus là où elles sont utilisées dans les fonctions "publiques". Sinon, CoffeeScript sera confus et créera de nouvelles vardéclarations internes qui les masqueront.
Andrew Miner
11

J'aimerais montrer quelque chose d'encore plus chic

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

Maintenant tu peux faire

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
la source
2

Il y a un problème avec la réponse de Vitaly et c'est que vous ne pouvez pas définir les variables que vous voulez être uniques à la portée, si vous avez créé un nom privé de cette façon et que vous l'avez modifié, la valeur du nom changerait pour chaque instance de la classe, il y a donc une façon de résoudre ce problème

# 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

Il n'est pas impossible d'accéder au tableau des noms de l'extérieur sauf si vous utilisez getNames

Testez ceci

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 compilé

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
la source
J'adore cette implémentation. Des inconvénients?
Erik5388
2

Voici une solution qui s'appuie sur plusieurs des autres réponses ici plus https://stackoverflow.com/a/7579956/1484513 . Il stocke les variables d'instance privée (non statiques) dans un tableau de classe privée (statique) et utilise un ID d'objet pour savoir quel élément de ce tableau contient les données appartenant à chaque instance.

# 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
la source
2

Voici le meilleur article que je trouve sur l' établissement public static members, private static members, public and private memberset d'autres choses connexes. Il couvre beaucoup de détails et par jsrapport à la coffeecomparaison. Et pour les raisons historiques , voici le meilleur exemple de code:

# 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
plunntic iam
la source
1

Voici comment déclarer des membres privés et non statiques dans Coffeescript
Pour une référence complète, vous pouvez consulter 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
la source
1

«classe» dans les scripts café conduit à un résultat basé sur un prototype. Ainsi, même si vous utilisez une variable privée, elle est partagée entre les instances. Tu peux le faire:

EventEmitter = ->
  privateName = ""

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

.. mène à

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

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

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

Mais veillez à mettre les membres privés avant les fonctions publiques, car coffee script renvoie les fonctions publiques en tant qu'objet. Regardez le Javascript compilé:

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

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

Étant donné que le script coffee se compile en JavaScript, la seule façon d'avoir des variables privées est de fermer.

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

Cela compilera via le JavaScript suivant:

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

Bien sûr, cela a les mêmes limitations que toutes les autres variables privées que vous pouvez avoir grâce à l'utilisation de fermetures, par exemple, les méthodes nouvellement ajoutées n'y ont pas accès car elles n'ont pas été définies dans la même portée.

Ivo Wetzel
la source
9
C'est une sorte de membre statique. e = new Animal(5);f = new Animal(1);e.test()alerte un, je veux cinq.
thejh
@thejh Oh, désolé alors, je vois l'erreur maintenant, je suppose qu'il était trop tard pour penser à ce truc hier.
Ivo Wetzel le
@thejh Cela m'est arrivé, j'ai tenté de résoudre ce problème dans ma réponse.
iConnor
0

Vous ne pouvez pas le faire facilement avec les classes CoffeeScript, car elles utilisent le modèle de constructeur Javascript pour créer des classes.

Cependant, vous pouvez dire quelque chose comme ceci:

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

Mais vous perdez la grandeur des classes CoffeeScript, car vous ne pouvez pas hériter d'une classe créée de cette manière autrement qu'en utilisant à nouveau extend (). instanceof cessera de fonctionner et les objets créés de cette manière consomment un peu plus de mémoire. De plus, vous ne devez plus utiliser les nouveaux et super mots clés.

Le fait est que les fermetures doivent être créées chaque fois qu'une classe est instanciée. Les fermetures de membres dans les classes CoffeeScript pures ne sont créées qu'une seule fois, c'est-à-dire lorsque le «type» d'exécution de la classe est construit.

Jaakko Salomaa
la source
-3

Si vous voulez seulement séparer les membres privés du public, enveloppez-le simplement dans $ variable

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

et utilise @$.requirements

Borovsky
la source