TIL21, 객체 지향 프로그래밍
객체 지향 프로그래밍은 코드를 추상화하여 직관적으로
생각할 수 있기 때문에 오래전부터 프로그래밍 언어에
빠르게 적용되어 왔으며, 사람이 세상을 보는 시각과 흡사하다.
Java, C# 같은 언어들은 처음부터 철저하게 객체 지향을 적용했으나,
Javscript같은 경우에는 이와 같은 언어들과는 다른 독특한 방식이 사용된다.
바로, class를 이용하는 방법이다.
가장 기본적으로 class를 이용해 객체를 만드는 방법을 살펴보자.
class Car {
constructor (brand, name, color) {
this.brand = brand;
this.name = name;
this.color = color;
}
}
let mini = new Car ('bmw', 'mini', 'red');
let rubicon = new Car ('jeep', 'rubicon', 'white');
let the4 = new Car ('bmw', 'the4', 'black');
console.log(mini); // Car {brand: "bmw", name: "mini", color: "red"}
console.log(mini.brand); // 'bmw'
console.log(rubicon); // Car {brand: "jeep", name: "rubicon", color: "white"}
console.log(rubicon.name); // 'rubicon'
console.log(the4); // Car {brand: "bmw", name: "the4", color: "black"}
console.log(the4.color); // 'black'
위와 같이 코드를 짤 수 있는데, class는 암묵적인 규칙으로 대문자로 시작하며, 보통명사를 사용한다.
constructor, 즉 생성자 함수는 속성을 지정해주며, 리턴을 하지 않는다.
새로이 선언된 mini, rubicon, the4는 인스턴스(instance)라고 부르며, 함수를 실행할 때는 new를 붙인다.
this는 인스턴스를 가르킨다.
각각의 인스턴스들을 호출했을때 볼 수 있듯이,
각자의 객체들을 잘 만든것을 확인할 수 있다.
또한 dot notation으로 각각의 속성값들도 불러오는 것을 확인할 수 있다.
객체 지향의 기본적인 컨셉 4가지를 알아보자.
객체 지향의 4가지 컨셉
- 캡슐화 (Encapsulation)
- 코드를 단순하고 만들고, 재사용성을 높인다.
- 데이터와 기능을 하나의 단위로 묶는다.
- 은닉 (hiding) : 구현은 숨기고, 동작은 노출시킨다.
- 느슨한 결합 (Loose Coupling)에 유리 : 언제든 구현을 수정할 수 있다.
- 추상화 (Abstraction)
- 코드를 단순하게 만들고, 단순화된 사용으로 인해 변화에 대한 영향을 최소화 한다.
- 내부 구현은 아주 복잡하지만, 실제로 노출되는 부분은 단순하게 만든다.
- 예를 들어, 스마트폰의 내부는 아주 복잡하게 설계되어 있지만, 실생활에서는 화면을 터치하고, 음량버튼과 전원 버튼 등을 누르는 것만으로 동작할 수 있게 단순화할 수 있다.
- 너무 많은 기능들이 노출되지 않은 덕분에 예기치 못한 사용상 변화가 일어나지 않도록 한다.
- 클래스 정의시 메소드와 속성만 정의한 것을 인터페이스라고 부른다.
- 상속 (Inheritance)
- 불필요한 코드를 줄여 재사용성을 높인다.
- 부모 클래스의 특징을 자식 클래스가 물려받는다. (기본 클래스의 특징을 파생 클래스가 상속받는다고도 표현)
- 자동차라는 class가 있고, 이 class는 ‘브랜드’, ‘차량명’, ‘색상’의 속성을 갖고 있다. 이때 오토바이라는 새로운 class를 작성할때, 모든 속성을 새로 작성하는 것이 아니라 자동차 class에서 필요한 속성을 가져오고, 필요한 부분은 추가로 속성과 메소드를 작성해 불필요한 코드를 작성하는 것을 방지할 수 있다. (편하다)
class Car {
constructor (brand, name) {
this.brand = brand;
this.name = name;
}
}
class Sportcar extends Car {
constructor (brand, name, hp) {
super (brand, name);
this.hp = hp;
}
}
let gv70 = new Car('genesis', 'gv70');
let the4 = new Sprotcar('bmw', 'the4', '184');
console.log(gv70); // {brand: 'genesis', name: 'gv70'}
console.log(the4); // {brand: 'bmw', name: 'the4', hp: 184}
위 코드와 같이, extends와 super 키워드를 이용해서 상속을 구현한다.
Sprotcar class에 부모 class인 Car의 속성(brand, name)을 받아와서 사용할 수 있다.
거기에 새로운 속성 hp를 지정해주고, 사용할 수 있다.
이렇게 불필요한 코드 몇줄 적는것을 생략할 수 있고, 기존에 작성한 코드를 활용하기 쉽다.
- 다형성 (Polymorphism)
- 같은 이름은 가진 method라도, 조금씩 다르게 작동해 완전히 다른 결과물을 만들어낼 수 있다.
class Animal {
constructor (name) {
this.name = name;
}
makeSound() {
console.log('Animal Sound');
}
}
class Dog extends Animal {
constructor (name) {
super (name);
}
makeSound() {
console.log('Woof! Woof! Woof!');
}
}
const a = new Animal();
const b = new Dog();
a.makeSound(); // 'Animal Sound'
b.makeSound(); // 'Woof! Woof! Woof!'
위 코드와 같이, 같은 이름을 가진 method이지만 완전히 다른 결과물을 얻을 수 있는것이다.
Prototype 기반의 언어, JavaScript
위에서 언급했듯이, 처음부터 철저하게 객체 지향을 적용한 Java, C#같은 언어와는 다르게 JavaScript는 다른 방식을 사용하고 있다.
그렇다면 JavaScript의 기반은 무엇일까? 바로 *Prototype이다.
Prototype은 원형 객체, 쉽게 말해 **‘유전자’와 같다.
우리가 지금껏 배열의 method, 또는 문자열의 method를 사용할 때 많이 접해보았다.
배열 arr이 있다고 가정하자!
arr에 무언가를 넣고 싶을땐 arr.push(); method로 아주 편리하게 사용해왔다.
뭘 어떻게 했길래 push()를 적으면 배열 뒤에 문자가 들어가는걸까?
Array.prototype을 콘솔에 적어보면, 여러 함수들이 나온다.
그 중에 push() 함수 외에도 익숙한 join(), slice(), sort() 등등의 함수들이 있다.
이 함수들을 미리 정의해주어, 바로 사용할 수 있게 만든것이다.
Array.prototype.add = function () {
}
위와 같이 Array.prototype에 내가 만든 add() 함수도 넣을 수 있다. (빈 함수임)
아래와 같은 객체가 하나 있다.
const human = {name: 'choco', age: 7}
이때, human.name을 찍으면 ‘choco’가 찍힌다.
하지만 human이라는 객체가 가지고 있지 않은 녀석을 찍으면, 부모 Prototype을 찾아가서 확인하고,
없으면 그 부모의 부모 Prototype을 찾아가서 확인하고,
또 없으면 등등등.. 결국 최상위 부모 Prototype을 찾아가서 확인을 하고 값을 반환하게 된다.
아래 예시에서 살펴보도록 하자.
class Human {
constructor (name, sex) {
this.name = name;
this.sex = 'male';
}
}
class Student extends Human {
constructor (name, height) {
super(name);
this.height = height;
}
}
let student = new Student('zoro', 181);
student.name; // 'zoro' -> student가 name을 갖고있으니 바로 갖고있는 것 리턴.
student.height; // 181 -> student가 height를 갖고있으니 바로 갖고있는 것 리턴.
student.sex; // 'male' -> student가 sex를 갖고있지 않으니 부모 class Human에 가서 sex를 찾고, 'male' 리턴.