0

Let's say I have a function Farm (animal1, animal2, ...) that takes objects as parameters. These objects are either sheep or cows, which are made by one of two factory methods:

function Sheep(id)
{
  function noise() {console.log('baa');}
  return {
    my_id : id, 
    noise
  }
}

function Cow(id)
{
  function noise() {console.log('moo');}
  function swish_tail () {}
  return {
    my_id : id, 
    noise,
    swish_tail
  }
}

Now, imagine Farm needs to do something different depending on the type of object of each animal (e.g. just list the numbers of each, or perhaps make all the sheep make a noise first then the cows). What is the best thing to do?

  • Use prototypes for each animal, and check the prototype of each in Farm?
  • Work based on the knowledge that only a Cow has a swish_tail () function as a member?
  • Have Sheep and Cow functionally inherit from an Animal factory method, and add something like a type member to Animal?

Ideally, Farm should be ignorant of the implementation of each animal as far as possible,

Edit: I understand there are similarities with this question however this does not quite address the question of how to solve the specific problem.

stuwilmur
  • 112
  • 1
  • 1
  • 7
  • 2
    Both animals should be derived from a common instance "animal" which shares the maximum possible between them, to work DRY, so in this case, `noise` would be a method on the `animal` prototype – vsync Oct 25 '22 at 12:14
  • Thanks that was my thinking. Part of my question remains: say `Farm` needs to process animals in some order (e.g. sheep before cows), how best to get the type of each? – stuwilmur Oct 25 '22 at 12:21
  • 1
    @vsync I don't think the duplicate applies. It shows an OO solution through the lens of prototype inheritance. It does not address FP approach to solving the problem. Well it doesn't even provide an OO solution. more of a "this is how OO is implemented in JS through prototypes" but still. – VLAZ Oct 25 '22 at 12:22
  • OK, will vote to reopen – vsync Oct 25 '22 at 12:44

2 Answers2

1

The functional approach would be to have a type property:

const AnimalType = {
  SHEEP: 0,
  COW: 1
};

function Sheep(id)
{
  function noise() {console.log('baa');}
  return {
    type : AnimalType.SHEEP,
    my_id : id, 
    noise
  };
}

function Cow(id)
{
  function noise() {console.log('moo');}
  function swish_tail () {}
  return {
    type : AnimalType.COW,
    my_id : id, 
    noise,
    swish_tail
  };
}

Then you can check the type of an animal like this:

function doSomethingWithAnimal(animal)
{
  switch (animal.type) {
    case AnimalType.SHEEP:
      console.log(`Animal with id ${animal.my_id} is a sheep!`);
      break;
    case AnimalType.COW:
      console.log(`Animal with id ${animal.my_id} is a cow!`);
      animal.swish_tail();
  }
}
Lauren Yim
  • 12,700
  • 2
  • 32
  • 59
1

Adding a "type" to Animal instance would be suffice. Here's an example I've made for you, which I hope would be helpful in understanding inheritance:

/////////////////////////////////////////
// Create Farm Constructor
function Farm(params) {
    this.animals = {} // to be filled with arrays by animal's type
}

Farm.prototype = {
    addAnimal( newAnimal ) {
        const exists = this.getAnimalById(newAnimal?.id)

        if( exists ) {
          console.warn('already exists')
          return 'already exists'
        }
             
        if( !(newAnimal instanceof Animal) ) {
          console.warn('not an animal')
          return                              
        }

        // add new animal to the object containing all animals by type, if not already exists          
        this.animals[newAnimal.type] = [...(this.animals[newAnimal.type] || []), newAnimal]
  
    },
  
    getAnimalById(id) {
      return id && Object.values(this.animals)?.flat()?.includes(id)
    },

    addAnimals( animals ){    
        animals.forEach(animal => animal && myFarm.addAnimal(animal))
    },

    getAnimalsByType( type ) {
        return type && this.animals.filter(animal => animal.type == type)
    },

    listAnimalsSounds( ...types ) {
        types = types || Object.keys(this.animals);  
        for( let type of types ) {
            console.log(  this.animals[type]?.[0]?.getSound(type)  )
        }
    }
}

/////////////////////////////////////////
// Create Generic-Animal Constructor
function Animal({id, type, name, gender}) {
    this.id = id
    this.type = type
    this.name = name
    this.gender = gender
                                          
    return this
}
                                          
// all commonanilities between animals
Animal.prototype = {
    getSound(type){                    
        return this.sound 
            ? `${this.sound} is the sound of a ${type}` 
            : `${type} does not make any sound`
    }
}

/////////////////////////////////////////
// Create Cow Animal Constructor

function Cow({name, id, gender, givesMilk}){
    Animal.call(this, {id, type: 'cow', name, gender})
    this.givesMilk = givesMilk // property specific to cows
    return this
}

Cow.prototype = Object.create(Animal.prototype);
Object.assign(Cow.prototype, {
    sound: 'moooo',
    tail: true,
    furr: true,
})

/////////////////////////////////////////
// Create Dog Animal Constructor

function Dog({name, id, gender, knowsTricks}){
    Animal.call(this, {id, type: 'dog', name, gender})
    this.knowsTricks = knowsTricks // property specific to dogs
    return this
}

Dog.prototype = Object.create(Animal.prototype);
Object.assign(Dog.prototype, {
    sound: 'howwww',
    swim: true,
    tail: true,
    furr: true,
})


/////////////////////////////////////////
// create a new farm instance and fill with animals
const myFarm = new Farm();

// create initial animals
const cows = [
    new Cow({name:'bill', id:11, gender:'male'}),
    new Cow({name:'mandy', id:12, gender:'female', givesMilk: true}),
]

const dogs = [
    new Dog({name:'ralf', id:21, gender:'male', knowsTricks: false}),
]


// add all initial animals
myFarm.addAnimals([...cows, ...dogs])

// list below animals' sounds, in this order:
myFarm.listAnimalsSounds('dog', 'cow')
vsync
  • 118,978
  • 58
  • 307
  • 400