ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JS/OOP] 자바스크립트와 객체지향 프로그래밍(Object Oriented Programming) / 캡슐화(Encapsulation), 추상화(Abstraction), 상속(Inheritance) 개념 정리
    Frontend 2019. 7. 21. 16:23

    객체지향 프로그래밍(Object Oriented Programming)

    객체지향 프로그래밍이란 어떤 동작이나 로직보다는 객체를 중심으로 프로그래밍을 하는 일종의 새로운 패러다임이다. 여기서 객체란 우리가 모델링하고자 하는 대상과 관련된 모든 정보와 데이터, 코드, 동작, 기능들을 담을 수 있다. 그러니까 객체지향 프로그래밍이란 간단하게 말해서 우리가 어떤 프로그램을 새로 만들려고 할 때 어떤 방식으로 프로그램을 구성하고 조립할 것인지에 대한 이론적인 개념이다. 어떤 식으로 프로그램을 만드느냐는 순전히 개발자의 창의력과 아이디어에 달려있다. 그러니까 우리가 객체지향 프로그래밍에서 주로 사용되는 프로토타입이나 클래스 등이 어떤 식으로 기능하는지는 공부할 수 있지만 객체지향 프로그래밍 자체를 어떻게 하는지는 순전히 개인의 상상력에 달린 문제이다.

     

     

    만약에 우리가 와이파이로 연결된 블루투스 스피커의 각종 버튼을 조절하는 앱을 구현한다고 해보자.

     

    var speaker = {
      volume: 0,
      volumeUp: function () {
        this.volume += 10;
      },
      volumeDown: function () {
        this.volume -= 10;
      }
    };

    먼저 스피커 객체를 만들고, volumeUp과 volumeDown 함수를 만들어 볼륨을 조절하도록 하였다.

     

    speaker.volumeUp();
    speaker.volumeDown();
    speaker.volume = 10000;

     

    이제 사용자는 speaker.volumeUp()와 speaker.volumeDown() 함수를 호출하여 원하는만큼 볼륨을 조절할 수 있다.

    그런데 문제는 외부에서 speaker.volume에 직접 접근하여 값을 변경할 수 있다는 점이다.

     

    이렇게 되면 볼륨의 최대 허용 크기가 100인데, 의도치 않게 10000이란 값을 부여하여 오류를 발생시킬 수 있을 것이다.

    이 때 volume 변수를 외부에서 변경할 수 없도록 보호하는 것을 캡슐화라고 한다.

     

     

    캡슐화(Encapsulation)

    var speaker = (function () {
      var volume = 0;
      
      return {
        volumeUp: function () {
          volume += 10;
        },
        volumeDown: function () {
          volume -= 10;
        }
      }
    })();
    
    speaker.volumeUp();
    speaker.volumeDown();

     

    이제 volumeUp과 volumeDown 함수를 이용하여 볼륨을 조절할 수는 있지만

    volume이란 변수에 직접 접근하여 값은 바꿀 수 없게된다.

     

    이렇게 외부에서 함부로 변수의 값을 마음대로 바꿀 수 없도록 

    내부의 정보를 외부로부터 은폐하는 것을 캡슐화(Encapsulation)라고 한다.

     

     

    추상화(Abstraction)

    만약에 사용자가 특정 버튼을 눌렀을때 일정 시간이 지나면 자동으로 볼륨이 0으로 변하는 기능을 넣고 싶다고 해보자.

     

    var speaker = (function () {
      var volume = 0;
      return {
        volumeUp: function () {
          volume += 10;
        },
        volumeDown: function () {
          volume -= 10;
        },
        autoVolumeOff: function () {
          setTimeout(function () {
            volume = 0;
          }, 5000);
        }
      }
    })();
    
    speaker.volumeUp();
    speaker.volumeUp();
    speaker.volumeUp();
    speaker.autoVolumeOff();

     

    이 때 스피커 앱을 사용하는 사용자는 어떤 원리에 의해 자동으로 스피커의 볼륨이 0이 되는지 알 수가 없다. 우리가 객체를 만들 때에는 복잡한 원리나 구동 방식을 사용자로부터 추상화시켜주는 작업이 중요한데 이를 추상화(Abstraction)라고 한다. 즉, 우리가 만든 객체를 사용자들이 쉽게 쓰도록 해주는 것이다.

     

     

    Using Constructor Function

    function Speaker () {
      this.volume = 0;
    }
    Speaker.prototype.volumeUp = function () {
      this.volume += 10;
    };
    Speaker.prototype.volumeDown = function () {
      this.volume -= 10;
    };
    Speaker.prototype.autoVolumeDown = function () {
      var that = this;
      setTimeout(function () {
        that.volume = 0;
      }, 5000);
    };
    var speaker1 = new Speaker();
    var speaker2 = new Speaker();
    var speaker3 = new Speaker();
    
    speaker1.volumeUp();
    speaker1.volumeDown();

     

    그런데 만약 우리에게 여러 개의 스피커가 필요하고 앞으로도 계속 여러 개의 스피커를 생성하여 위에서 만든 볼륨 조절 기능을 그대로 쓰고 싶다면 우리는 이 때 Constructor와 Prototype을 이용할 수 있다.

     

    위의 코드를 보면 Speaker라는 이름의 Constructor(생성자 함수)를 만들었고,

    Constructor의 Prototype(프로토타입)에 volumeUp, volumeDown, autoVolumeDown과 같은 메소드를 추가해주었다.

     

    그리고 speaker1, speaker2, speaker3와 같은 여러 개의 스피커 인스턴스를 만들었다.

    이제 우리는 speaker1, speaker2, speaker3 이 세 개의 스피커에서 모두 볼륨 조절 함수를 사용할 수 있게 된다.

    왜냐하면 이 3개의 인스턴스는 모두 Speaker라는 같은 Constructor에서 생성된 인스턴스이기 때문에 프로토타입을 공유하기 때문이다.

     

     

    Using Factory Function

    var speakerPrototype = {
      volume: 0,
      volumeUp: function () {
        this.volume += 10;
      },
      volumeDown: function () {
        this.volume -= 10;
      },
      autoVolumeDown: function () {
        setTimeout(function () {
          this.volume = 0;
        }, 5000);
      }
    }
    
    function createSpeaker () {
      return Object.create(speakerPrototype);
    }
    
    var speaker1 = createSpeaker();
    var speaker2 = createSpeaker();
    var speaker3 = createSpeaker();
    
    speaker1.volumeUp();
    speaker1.volumeDown();

     

    만약 constructor 대신 다른 방법을 사용하고 싶다면 Factory Function을 사용할 수도 있다.

    이 방법은 객체를 선언하고 그 객체를 포함하여 새로운 객체를 생성하는 방법으로 만들 수 있다.

     

    위 코드에서는 createSpeaker() 메소드가 바로 Factory Function이다.

    자바스크립트에서는 생성자 함수나 클래스를 사용하지 않고 new object를 리턴하는 함수를 Factory Function이라고 한다.

     

     

    참고자료:

    https://medium.com/javascript-scene/javascript-factory-functions-vs-constructor-functions-vs-classes-2f22ceddf33e

     

    JavaScript Factory Functions vs Constructor Functions vs Classes

    Prior to ES6, there was a lot of confusion about the differences between a factory function and a constructor function in JavaScript. Since…

    medium.com

     


     

    이제 상속(Inheritance)의 개념에 대해서 알아보자.

     

    function Person (name, job) {
      this.name = name;
      this.job = job;
    }
    Person.prototype.changeJob = function (newJob) {
      this.job = newJob;
    }
    var person1 = new Person('Julia', 'programmer');
    console.log(person1);

     

    Person이란 constructor를 만들었다. 이 생성자 함수는 name과 job 정보를 담고 있으며,

    프로토타입에 있는 changeJob라는 메소드로 job을 변경할 수 있다.

     

     

    function Daughter (name, job) {
      this.name = name;
      this.job = job;
      this.age = 15;
    }
    Daughter.prototype.changeJob = function (newJob) {
      this.job = newJob;
    }
    Daughter.prototype.grow = function (time) {
      var her = this;
      setTimeout(function () {
        her.age += 1;
        console.log(her.age);
      }, time);
    }

     

    Person 하위에는 Daughter라는 자식이 존재한다.

    Daughter contructor에는 아까와 같은 changeJob 메소드가 존재하고 grow라는 새로운 메소드가 추가되었다.

     

    Daughter constructor는 Person constructor와 동일한 부분이 많다.

    이렇게 비슷한 부분을 매번 새로 만드는 것이 아니라

    기존에 만들어둔 것을 이어받아 코드의 재사용성을 향상시켜주는 것이 바로 Inheritance이다.

     

     

    상속(Inheritance)

    function Person (name, job) {
      this.name = name;
      this.job = job;
    }
    Person.prototype.changeJob = function (newJob) {
      this.job = newJob;
    }
    var person1 = new Person('Julia', 'programmer');
    console.log(person1);
    
    function Daughter (name, job) {
      Person.call(this, name, job);
      this.age = 15;
    }
    
    Daughter.prototype = Object.create(Person.prototype);
    Daughter.prototype.constructor = Daughter;
    
    Daughter.prototype.grow = function (time) {
      var her = this;
      setTimeout(function () {
        her.age += 1;
      }, time);
    }
    
    var daughter1 = new Daughter('sally', 'student');
    daughter1.changeJob('idol');
    console.log(daughter1);

     

    이제 우리는 Person constructor 내에서 call()을 이용하여 Person을 호출함으로써

    Daughter constructor내에 this.name과 this.job을 써줄 필요가 없어졌다.

     

    또한 Person.prototype을 포함하는 새로운 객체를 Daughter의 프로토타입에 할당함으로서

    Daughter의 하위 인스턴스들 역시 Person의 프로토타입에 있는 메소드를 이용할 수 있게 되었다.

     

     

     

     

    function Person (name, job) {
      this.name = name;
      this.job = job;
    }
    Person.prototype.changeJob = function (newJob) {
      this.job = newJob;
    }
    var person1 = new Person('Julia', 'programmer');
    
    function Daughter (name, job) {
      Person.call(this, name, job);
      this.age = 15;
    }
    
    Daughter.prototype = Object.create(Person.prototype);
    Daughter.prototype.constructor = Daughter;
    
    Daughter.prototype.grow = function (time) {
      var her = this;
      setTimeout(function () {
        her.age += 1;
      }, time);
    }
    
    var daughter1 = new Daughter('sally', 'student');
    daughter1.changeJob('idol');
    
    function Son (name, job) {
      Person.call(this, name, job);
      this.feeling = 'sad';
    }
    Son.prototype = Object.create(Person.prototype);
    Son.prototype.constructor = Son;
    
    Son.prototype.beHappy = function () {
      this.feeling = 'happy';
    }
    
    var son1 = new Son('mike', null);
    son1.beHappy();
    console.log(son1);

     

    이제 같은 방법으로 얼마든지 Person constructor 하위에 새로운 constructor를 추가할 수 있다.

     

     

     

    Class - ES6

    class Person {
      constructor(name, job) {
        this.name = name;
        this.job = job;
      }
      changeJob(newJob) {
        this.job = newJob;
      }
    }
    
    class Daughter extends Person {
      constructor(name, job) {
        super(name, job);
        this.age = 15;
      }
      grow(time) {
        var her = this;
        setTimeout(function () {
          her.age += 1;
        }, time);
      }
    }
    
    class Son extends Person {
      constructor(name, job) {
        super(name, job);
        this.feeling = 'sad';
      }
      beHappy() {
        this.feeling = 'happy';
      }
    }
    
    var daughter1 = new Daughter('sally', 'student');
    daughter1.changeJob('Idol');
    var son1 = new Son('Mike', null);
    son1.beHappy();
    
    console.log(daughter1, son1);

     

    ES6에서는 이러한 constructor와 prototype 구조를 좀 더 간단하게 표현할 수 있는 Class라는 것이 새로 생겼다.

    아까의 길고 복잡한 코드를 위와 같이 간단하게 표현할 수 있다.

     

     

     

    반응형

    COMMENT