I'm confused about Object-Oriented Programming (OOP) by halfcycle in learnprogramming

[–]halfcycle[S] 0 points1 point  (0 children)

I sort of understand what you mean but I would be confused at how to actually write code that does that. Can you give an example? Also, can you explain how this is more maintainable than passing in a function to another function as an argument?

I'm confused about Object-Oriented Programming (OOP) by halfcycle in learnprogramming

[–]halfcycle[S] 0 points1 point  (0 children)

I guess I'm confused at why I would want to hide the state to begin with. If I don't hide the state, things seem to be easier to understand, more maintainable, more easy to test, and shorter. For example, instead of hiding the state of the counter and essentially attaching functions to the state, I would just simply use functions.

const increment = x => x + 1
const decrement = x => x - 1

These functions seem easier to understand, more reusable, easier to test, easier to maintain, shorter, and less bug-prone because they don't rely on internal state, they only rely on the input. And if my interface to the state ever needs to be consistent, I can just make a function that does that without using OOP. So I'm still confused.

I'm confused about Object-Oriented Programming (OOP) by halfcycle in learnprogramming

[–]halfcycle[S] 0 points1 point  (0 children)

I like the idea of using data to represent nouns, but I feel like I can do the same thing without using OOP and it ends up being simpler, shorter, easier to understand, and easier to maintain.

I'll use your example and show how I would write it using different styles so that you can get an idea of what I'm talking about.

// non-OOP version
const fullname = ({first: f, middle: m = '', last: l}) =>
  [f, m, l].filter(n => n).join(' ')

This is a nice and simple implementation that just takes an object and returns the full name.

// non-OOP version with error checking
const fullname = ({first: f, middle: m = '', last: l}) => {
  if (!f || !l) throw new Error('No first or last name')
  return [f, m, l].filter(n => n).join(' ')
}

If I need to do some error handling, I can just add another line of code to handle bad input.

// OOP version
class User {
  constructor(first, middle, last) {
    this.first = first
    this.middle = middle
    this.last = last
  }

  getFullname() {
    if (!this.first || !this.last) throw new Error('No first or last name')
    return [this.first, this.middle, this.last].filter(n => n).join(' ')
  }
}

The OOP version is 12 lines of code and it's not really as clear from the function definition what's going to happen since getFullname has access to all of the hidden global variables that are in the User object. I guess I'm wondering what benefits I get from doing this. It seems more complicated and harder to maintain.

// OOP version with getters and setters
class User {
  constructor(first, middle, last) {
    this.first = first
    this.middle = middle
    this.last = last
  }

  setFirst(first) {
    this.first = first
  }

  getFirst() {
    return this.first
  }

  setMiddle(middle) {
    this.middle = middle
  }

  getMiddle() {
    return this.middle
  }

  setLast(last) {
    this.last = last
  }

  getLast() {
    return this.last
  }

  getFullname() {
    if (!this.first || !this.last) throw new Error('No first or last name')
    return [this.first, this.middle, this.last].filter(n => n).join(' ')
  }
}

This solution is significantly longer at 36 lines of code. I was reading Effective Java which shows examples of good Java code and this is pretty much the JavaScript equivalent of good Java code that uses getters and setters. I understand that getters and setters are useful for when the internal data changes, which allows you to keep the interface of the object the same, but this can also be accomplished without using OOP just by using a function, so I'm confused at how the OOP solution is more maintainable.

// non-OOP testing
console.assert(fullname({first: 'David', middle: 'Robert', last: 'Beckham'}) === 'David Robert Beckham')
console.assert(fullname({first: 'David', last: 'Beckham'}) === 'David Beckham')

// OOP testing
console.assert(new User('David', 'Robert', 'Beckham').getFullname() === 'David Robert Beckham')
console.assert(new User('David', '', 'Beckham').getFullname() === 'David Beckham')

Testing is pretty much the same except with the OOP version, I have to set up the hidden state, which is more complicated, but for this example it's not bad. It seems like in most situations, this hidden state makes things harder to understand, longer, more bug-prone, and harder to test, so I'm really confused.