3

What I am trying to achieve is create some functions that operate on properties of an object in JavaScript, which is a fairly typical thing to do.

The problem is that in the interests of flexibility and extensibility I don't want the functions to know about the object in advance. As far as I can tell this is exactly the kind of thing that Javascript should be pretty good for, but not having spent a lot of time around functional languages I haven't yet got a handle on how to do it.

So I might have a situation like this:

function Owner()
{
     var self=this;
     self.size = 100;
     self.writers = [];
}

function Writers()
{
    var Alice= function()
    {
        var that=this;
        that.name="alice";
        that.operate = function()
        {
            console.log( "Alice says: "+self.size );   
        }
    }
    this.alice=new Alice();
}

var owner = new Owner();
var writers = new Writers();
owner.writers.push( writers.alice );
owner.writers[0].operate();

Now this returns Alice Says: undefined which is all well and good, but not exactly what I am looking for- I would obviously like to see Alice Says: 100. In a classical language I would probably have to hand the Alice function a reference to the owner ( in pseudo-C# ) :

public class Owner()
{
    private List<Writer> writers;



    public Owner()
    {
        this.size=100;
        this.writers= List<Writer>();
    }

    public int Size{ get; set; }

    public void AddWriter( Writer writer )
    {
         writer.Owner = this;
         this.writers.Add(writer);     
    }

}

public class Writer
{
   private Owner owner;
   private string name;

   public void Writer( string name )
   {
      this.name=name;
   }

   public void operate()
   {
        Console.WriteLine( "{0} says: {1}", name, owner.Size );
    }
}

As far as I can tell, this is not very Javascripty- it seems like there should be a better- or at least more idiomatic - way of doing it.

In terms of the actual goal of the code I am working with ( which is basically like this, but more complicated ) the goal is to be able to add any number of "Writer" classes that can perform operations on the "Owner" instance to which they are attached. This is basically an implementation of the Decorator pattern and I can think of a lot of ways I could implement this but as I say, I'm looking for understand how the functiony/prototypey nature of Javascript can help me in this kind of situation so I can get a better handle on the language.

I'm also not entirely happy with how I am passing around the functions through instances- I feel like I should be able to throw functions around the place like confetti, so any tips on how doing that interacts with variable scope would be very helpful.

If it turns out what I'm trying to do is ass-backwards and there is a better way of achieving this type of goal, that is fine too. I'm trying to get to the good language in Javascript and it's proving tricky but I'm learning a whole lot.

Edit: My goal is to have a base type where I don't have to know when I design it the complete list of operations it might have to perform and where I don't have to create a new subclass for every different combination of available operations, ensuring my operations are decoupled from my core types.

So if I have an Animal type and then my operations were Eat, Run, Grow and so on, I would ideally have a myAnimal.can("Eat") type call ( I realise that this is a bit like hasOwnProperty but I'm assuming that these operations are of a specific base type ) that would inform me whether that operation was available and then something like myAnimal.actions.Eat("leaves") if it was accessible. When the Eat action was called it would affect the properties of the parent myAnimal object. During the lifetime of myAnimal it might have abilities added or removed at any point, but as long as one checks that it can do something that should be sufficient for my needs.

glenatron
  • 11,018
  • 13
  • 64
  • 112
  • 1
    What's `Owner`? I don't see how an owner has a size and writers... What is the relationship between `Owner` and `Writers`? So Alice is a writer? I'm not sure those names are obvious or tell us what the relationships are. Also, `self` is out of scope there, so obviously you get `undefined`. `that=this` and `self=this` aren't helping you, they seem unnecessary. – elclanrs Sep 29 '13 at 00:17
  • The `Alice` function does not know about the variable `self` – Joe Simmons Sep 29 '13 at 00:25
  • I'm glad you're willing to learn and adapt. If I knew more of your intentions, I could help you create what you wish, but I can't tell exactly what you want the script to do. How about you write some pseudo-script that shows what you'd like to accomplish? – Joe Simmons Sep 29 '13 at 00:28
  • 1
    Other than the `self` issue, the main problem I see is that the data is not modeled correctly, so we can't understand the objective... `Writers` should be `Writer` and the methods should be in the prototype. There should also be a clear relationship with `Owner` so you can inherit `size` and use it in `Writer`, but then that means a Writer is an Owner, mmm... I agree with Joe Simmons, some pseudo code would help, or maybe model the data in another language. – elclanrs Sep 29 '13 at 00:30
  • Sorry for the delay in getting back, wrote this last thing and slightly forgot I had posted it. Hopefully my explanation will make things a little clearer. – glenatron Sep 29 '13 at 20:43
  • is Owner class a singleton ? – GameAlchemist Sep 29 '13 at 21:11
  • @GameAlchemist definitely not. There will certainly be multiple Owners in the system, with their own unique collection of Writers. A Writer will always perform the same operations on the same attribute of the Owner - though different writers will perform different operations - which is why I feel as though passing functions around is a good solution to this problem. – glenatron Sep 29 '13 at 21:16
  • 1
    You're still describing what you're trying to do in computer-science terms (like achieve a Decorator pattern) rather than telling us what problem you're really trying to solve. For me, it's easier to know how to best accomplish something in javascript if I can get to the root problem you're trying to solve rather than being asked how to map a C# concept to JS. Mappings are often imperfect and non-optimized because the languages have different strengths/weaknesses. It's much easier to apply the strengths of a language to a problem if you understand the real objective of the problem. – jfriend00 Sep 29 '13 at 21:22
  • @jfriend00 If computer science doesn't apply to Javascript that _may_ be where I'm going wrong :p What I want to do is to give a different instances of the same base type the ability to perform different operations on their properties with another type/object/function/something representing the operations, so I can easily add or change the operations without having to change the base class. – glenatron Sep 29 '13 at 21:40
  • 1
    @glenatron - computer science works just fine with javascript. It's just that the "best" way to solve a problem (in any language) depends upon the problem, not the algorithm you're thinking about using. Too many questions here on SO ask about what's wrong with the solution path someone is pursuing without describing the actual problem. We can never give you the best answer without understanding the actual problem to be solved, and not just your prospective solution. – jfriend00 Sep 29 '13 at 21:43
  • @jfriend00 I've put in an edit that tries to cover what I'm doing- the system I'm writing is quite complicated in parts and I'm trying to distil down what _exactly_ is the part I don't understand. Obviously, this is a useful exercise in its own right and I'm getting closer to understanding what I need to be able to do as a result. – glenatron Sep 29 '13 at 21:52
  • @glenatron - see what I've added to my answer. – jfriend00 Sep 29 '13 at 22:12

3 Answers3

3

One way to achieve it is to use javascript closure:

function Owner() {
    var $self = {};
    $self.size = 150;
    $self.writers = [];

    function Writers() {

        var Alice = function () {

            var that = this;
            that.name = "alice";
            that.operate = function () {
                console.log("Alice says: " + $self.size);
                console.log($self);
            }
        }
        this.alice = new Alice();
    }

    var getO = function () {
        var writers = new Writers();
        $self.writers.push(writers.alice);
        $self.writers[0].operate();
        $self.writers[0].operate();
    }
    return getO
}

var o = Owner();
o();

Here is the fiddle.

Having a closure basically creates an environment where, the 'inner-outside' functions and variables are accessible from the 'inner-inside' of a function that has been returned:

These are the 'inner-outside' functions and variables:

    var $self = {};
    $self.size = 150;
    $self.writers = [];

    function Writers() {

        var Alice = function () {

            var that = this;
            that.name = "alice";
            that.operate = function () {
                console.log("Alice says: " + $self.size);
                console.log($self);
            }
        }
        this.alice = new Alice();
    }

Inside getO is the 'inner-inside':

    var getO = function () {
         //Here are the functions you want to call:

        var writers = new Writers();
        $self.writers.push(writers.alice);
        $self.writers[0].operate();
        $self.writers[0].operate();
     }

Returning getO and we've created a closure!

From now on when you call realowner(); you will be liking calling getO(); with everything in getO() being able to access to the 'inner-outside' variables & functions (e.g. $self).

Community
  • 1
  • 1
Archy Will He 何魏奇
  • 9,589
  • 4
  • 34
  • 50
  • This is a really good answer, thank you. Closures are one of those things I have heard described a whole lot but haven't really been able to grasp well enough to use them myself until now. I _think_ I'm starting to understand them a little better and that does sound like what I mean. – glenatron Sep 29 '13 at 20:45
2

In this line:

console.log( "Alice says: "+self.size ); 

self is not what you want it to be because your definition of self is not in scope, thus you are getting the global definition of self which is window and won't do what you want.

The issue in your design is that a Writers object doesn't know anything about an Owner object, thus is can't have methods that report the size of the Owner array.

You've made your code much more complex than it needs to be with the needless definitions of that and self and it is unclear why a Writers object should know about it's container. If you want that to be so, then you should pass it's container to the constructor of the Writers object and it can store a reference to it's container.

If you describe better what problem you're really trying to solve, we could probably come up with a massively simpler and more properly object oriented suggestion.


In Javascript, you still need to keep in mind the concept of data encapsulated into objects and those objects having methods that can operate on the data and properties that can be accessed. If you want to operate on the data in an object (as in your .size property), you must have a reference to the object that contains that property.

A reference to an object (like your Owner object) can come from several places:

  1. It can be stored as instance data in some other object and thus available from that other objects methods to be used as desired (e.g. store a reference to the Owner in each Writers object.
  2. It can be available through scope (e.g. defined in the current function or any parent function that you are nested in). Define the Writers object inside of the scope of the Owner's constructor. This would give you access to the Owner instance, but would also made the Writers objects only available inside Owners methods (not publicly available).
  3. It can be passed in as an argument to the method/function that wants to use it. You could pass the appropriate Owner to the .operate(curOwner) method as an argument.
  4. It can be stored globally and thus available anywhere. If there's only one Owner at a time (e.g. it's a singleton design pattern), you could just have the Owner be stored globally and accessed globally. This is less flexible (can't use more than one Owner sometime in the future for other purposes), but can be simpler.

What is lacking in your code is any way from the .operate() method of the Writers object to get to the corresponding Owner object. It simply isn't available from within the .operate() code. If you want it available there, you will have to change the structure of your code to make at least one of the above access methods or invent a new way to get to the Owner instance.


Based on your latest edit to describe the problem, here's some more info. One of the ways you can take advantage of javascript's loose typing is that you can have an array of objects and those objects can be of any type you want. As long as they all have a common set of methods such as .eat(), .grow(), .run(), you can treat the objects all the same (calling these methods on them) even though they are completely different types of objects. You don't have to make a common base class and then subclass from that. That's not very javascripty and not required. Just make three different objects, give each the required set of methods, put them in the array and then call the methods as needed. You can even test if an object has a particular method before calling it if they might not all have the same methods (though this is a less perfect OO design - though sometimes more practical than other designs).

For example, you can have this:

// define the cat object
function cat(name) {
   this.name = name;
}

cat.prototype = {
    eat: function(howMuch) {
        // implementation of eat here
    },
    run: function(howFar) {
        // implementation of run here
    },
    grow: function(years) {
        // implementation of grow here
    }
};

// define the dog object
function dog(name) {
    this.name = name;
}

dog.prototype = {
    eat: function(howMuch) {
        // implementation of eat here
    },
    run: function(howFar) {
        // implementation of run here
    },
    grow: function(years) {
        // implementation of grow here
    }
};

You can then have an array of cats and dogs:

var animals = [];
animals.push(new cat("fluffy"));
animals.push(new dog("bowser"));
animals.push(new cat("purr"));

You can then have code that operates on the contents of the animals array without regard for what kind of animal it is. So, to make each animal run 30 feet, you might do this:

for (var i = 0; i < animals.length; i++) {
    animals[i].run(30);
}

Now, you could make a base class that has a default constructor to save the name and defines do nothing abstract methods for eat, run and grow (in C++ or C# style), but that often isn't really necessary in JS because you don't have to know anything about the type of the object to use it as long as they all have some common set of methods you know how to call.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Thank you for the constructive comment just now! Greatly appreciate that! And +1! – Archy Will He 何魏奇 Sep 29 '13 at 01:10
  • Hopefully my edit describes what I am trying to do in a slightly clearer way. I've got a fairly clear picture of how I want the system to work - where one can add any selection of operations to an object of the parent type and then test the current instance for what it _can do_ rather than worrying about what it _is_ - which is one of the things I'm starting to understand Javascript should be good at. – glenatron Sep 29 '13 at 20:57
  • But as I work on this I would say that variable scope is my most consistent bugbear- I find the Javascript scope hierarchy endlessly flummoxing. – glenatron Sep 29 '13 at 20:58
  • @glenatron - see what I've added to my answer. – jfriend00 Sep 29 '13 at 21:34
  • @glenatron - based on your latest edits to your question, I've added some code examples to the end of my answer. – jfriend00 Sep 29 '13 at 22:11
  • So I could make `eat` an operation type that changed the `animal.isHungry` property and pass that operation type to the animal classes who I wanted to be able to eat ( so I only need one implementation of `eat` for everything that can eat ) but when I bound `eat` to a parent `animal` object, I would also have to pass the parent `animal` object to the `eat` operation type so it knew whose `isHungry` property it needed to change? – glenatron Sep 29 '13 at 23:00
  • @glenatron - sorry, I don't understand what you're asking in this latest comment. If `.eat()` should do the same thing for all animals, then you can indeed have only one implementation of that by just assigning the same method to each type of animal object. Keep in mind that in JS, a method is just a function that is a property of an object and if that function operates on `this`, it can be attached to any object and it will operate on that object. There is freedom and flexibility in javascript this way that other "strongly typed" languages do not have which can be useful at times. – jfriend00 Sep 29 '13 at 23:23
  • @jfriend00 This is exactly what I am trying to get to in understanding the more "Javascripty" way of doing this. If `eat` is purely a function, it's `this` will be the object it has been assigned to? – glenatron Sep 30 '13 at 09:15
  • @glenatron - `this` is set by the JS interpreter at the time the function/method is called according to how it is called. If it's called with `obj.method()`, then `this` in that invocation of `method()` will be set to `obj`. You can also cause `this` to be set to something different by using `obj.method.call(x)` or `obj.method.apply(x)`. – jfriend00 Sep 30 '13 at 21:36
1

I translated your pseudo-C# code into javascript.
While translating, i modified a bit naming conventions to common js standards. (leading upper case for class only, underscore for private members). I used properties and closure to get closer to your class description.

I also modified some signatures :
- name and size are readonly i guess.
- a writer must know about its owner.
- an owner might want to have all writers to operate. (just a guess).

hopefully this is will lead you to your goal.

fiddle is here : http://jsfiddle.net/gamealchemist/zPu8C/3/

function Owner() {
    var _writers = [];

    Object.defineProperty(this, 'size', {
        get: function () {
            return _writers.length
        },
        enumerable: true
    });

    this.addWriter = function (writer) {
        _writers.push(writer);
    }

    this.operateAll = function () {
        _writers.forEach(function (w) {
            w.operate();
        });
    };
}

function Writer(owner, name) {
    var _owner = owner;
    var _name = name;

    Object.defineProperty(this, 'name', {
        get: function () {
            return _name;
        },
        enumerable: true
    });

    this.operate = function () {
        console.log(this.name + " sees " + _owner.size + " people");
    }

    _owner.addWriter(this);
}

var wonderlandOwner = new Owner();

var alice = new Writer(wonderlandOwner, 'Alice');
var rabbit = new Writer(wonderlandOwner, 'Rabbit');
var madHatter = new Writer(wonderlandOwner, 'Mad Hatter');

wonderlandOwner.operateAll();

// outuput is :
// Alice sees 3 people 
// Rabbit sees 3 people 
// Mad Hatter sees 3 people 

madHatter.operate();

// output is :
// Mad Hatter sees 3 people
glenatron
  • 11,018
  • 13
  • 64
  • 112
GameAlchemist
  • 18,995
  • 7
  • 36
  • 59