저는 요즘 클린 아키텍처(Clean Architecture) 책을 읽고 있어요. 이 책에서는 ‘좋은 아키텍처를 만드는 일은 객체 지향 원칙을 이해하고, 응용하는 것에서 출발한다’ 라고 말합니다. 왜 그런 것일까요?
책에서는 객체 지향을 통해 ‘아키텍트는 플러그인 아키텍처를 구성할 수 있고, 이를 통해 고수준의 정책을 포함하는 모듈을 저수준의 세부사항을 포함하는 모듈에 대해 독립성을 보장할 수 있다. (중략) 독립적으로 개발하고 배포할 수 있다’ 라고 말해요. 음 도통 무슨말인지 모르겠네요🤣
객체 지향은 쉽게 말하면 '실제 세계를 모델링 함으로써 소프트웨어를 조금 더 쉽게 이해할 수 있는 패러다임' 이라 말할 수 있겠네요. 그러면 객체 지향의 핵심을 파악하며, 지금까지의 말이 어떤 의미인지 알아가 보도록 합시다!
아! 그리고 JS가 왜 다중 패러다임 언어인지도 같이 이해해 보도록 합시다!
❓캡슐화
캡슐화 라는 것은 쉽게 말한다면 정보를 은닉하는 것입니다. 우선 예를 통해 빠르게 알아보도록 합시다!
우선 객체 지향의 개념이니 만큼 Class를 사용한 구현 예시를 먼저 보겠습니다.
class Account {
#balance = 0; // private 속성
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
console.log(`${amount} deposited. New balance: ${this.#balance}`);
}
}
withdraw(amount) {
if (amount > 0 && amount <= this.#balance) {
this.#balance -= amount;
console.log(`${amount} withdrawn. New balance: ${this.#balance}`);
} else {
console.log("Insufficient funds.");
}
}
getBalance() {
return this.#balance;
}
}
우선 정보 은닉을 위해서 private 변수를 사용할 수 있습니다. JS 클래스에서는 #을 이용하여 선언할 수 있구요. 이렇게 되면 #balance 변수는 deposit, withdraw, getBalance 메서드로 간접적인 접근을 할 순 있겠으나, 직접적으로 접근할 순 없어요.
그리고 클래스가 아닌 함수를 이용해서 이 캡슐화를 만들 수도 있어요.
function createPerson(name) {
let _name = name;
return {
getName: function() {
return _name;
},
setName: function(newName) {
_name = newName;
}
};
}
const person = createPerson("Lurgi");
console.log(person.getName()); // "Lurgi"
person.setName("jeongwoo");
console.log(person.getName()); // "jeongwoo"
함수 내부의 _name 은 캡슐화를 통한 정보 은닉에 성공한 상태입니다. 접근은 getName과 setName 을 통해서 간접적으로만 가능합니다! createPerson의 실행이 끝났음에도 _name 변수가 가비지 컬렉션의 대상이 되지 않은 이유는 getName setName이 _name 변수를 참조하고 있기 때문입니다! 이를 클로저(Closure)라고 하구요!
클로저를 이용하여 이렇게 함수를 이용한 캡슐화를 할 수 있습니다!
❓상속화
상속화는 말 그대로 다른 객체의 속성을 상속받는 행위를 뜻합니다. 이는 예시를 통해 빠르게 알아보도록 합시다!
// 수퍼 클래스
class Animal {
constructor(name) {
this.name = name;
}
getAnimalname() {
return this.name;
}
makeSound() {
console.log("Some sound");
}
}
// 자식 클래스
class Dog extends Animal {
constructor(name) {
super(name);
}
makeSound() {
console.log("Mung!");
}
}
const dog = new Dog("Mott");
console.log(dog.getAnimalname()); // Mott
dog.makeSound(); // Mung!
이는 상속화를 class를 이용하여 구현한 모습입니다. Animal 클래스 객체의 메서드를 그대로 가져와 dog 클래스에서 사용할 수도 있고, 심지어 메서드를 덮어쓰는 것 역시 가능합니다. 덮어쓰는 것을 오버 라이딩(재정의) 라고 하구요! 이에 관해서는 이전에 포스팅한 글을 참고해주세요!
그리고 이 상속화 역시 함수로 표현할 수 있습니다! 함수에서의 상속은 prototype 으로 구현할 수 있습니다.
// 수퍼 클래스
function Animal(name) {
this.name = name;
}
Animal.prototype.getAnimalname = function () {
return this.name;
};
Animal.prototype.makeSound = function () {
console.log("Some sound");
};
// 자식 클래스
function Dog(name) {
Animal.call(this, name);
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.makeSound = function () {
console.log("Mung!");
};
const dog = new Dog("Mott");
console.log(dog.getAnimalname()); // Mott
dog.makeSound(); // Mung!
클래스 보다는 확실히 복잡해진 것 같아요.
Object.crate()를 이용하여 Dog 클래스의 프로토타입을 Animal 클래스의 프로토타입으로 설정하여 상속을 구현하였습니다! 그리고 함수의 call 메서드를 통해 함수 호출시 인자를 더해주는 역할을 해주었구요. 이처럼 함수를 이용해서도 상속화를 구현할 수 있다는 점!
❓다형성 (Polymorphism)
클린 아키텍처 책에서 O.O(객체 지향. Object Oriented)에서의 핵심은 다형성이라고 말합니다. 다형성이 무엇일까요?
다형성은 같은 이름의 메서드나 함수가 다른 상황에서 다르게 동작할 수 있는 능력을 의미합니다.
우선 예제를 통해 알아보도록 합시다!
function makeSound(animal) {
return animal.sound();
}
const dog = {
sound: function() {
return "Mung!";
}
};
const cat = {
sound: function() {
return "Meow!";
}
};
console.log(makeSound(dog)); // "Mung!"
console.log(makeSound(cat)); // "Meow!
class Animal {
sound() {
return "sound";
}
}
class Dog extends Animal {
sound() {
return "Mung!";
}
}
class Cat extends Animal {
sound() {
return "Meow!";
}
}
function makeSound(animal) {
return animal.sound();
}
const dog = new Dog();
const cat = new Cat();
console.log(makeSound(dog)); // "Mung!"
console.log(makeSound(cat)); // "Meow!"
함수를 이용한 예제와, 클래스를 이용한 예제를 만들어 보았습니다. 함수에서는 makeSound 메서드 하나를 통해 두 개의 결과값을, 클래스 에서는 Animal 하나의 클래스에서 상속받은 메서드를 통해 두 개의 결과값을 가져올 수 있었습니다. 이렇듯 각기 다른 상황에서 다른 동작을 할 수 있는 것을 다형성이라고 합니다!
조금 더 설명드리기 위해 JS에서의 고차함수를 예시로 들 수 있겠네요!
const arr = [1,2,3,4,5]
console.log(arr.map((number)=> number+1)) // [2,3,4,5,6]
console.log(arr.map((number)=> number*2)) // [2,4,6,8,10]
이렇게 map 메서드는 인자로 받은 콜백 함수에 의존하는 고차함수 입니다. 이를 의존성 주입 (Dependency Injection) 이라고 표현합니다!
다형성과 의존성에 대해서는 다음번에 더 자세하게 다뤄보도록 하겠습니다!!
😊결론..!
JS는 ES6에서 본격적으로 클래스가 도입됨에 따라 클래스 기반의 객체 지향 프로그래밍을 지원하게 되었는데요. 이 때문에 요즘 많은 분들이 Class = 객체 지향 이란 생각을 가지고 계시는 것 같습니다. 그런 의미에서 이번 시간에는 클래스 예시와 함수 예시를 모두 보여드렸구요!
JS는 함수형과 객체지향 모두를 지원하는 다중 패러다임 프로그래밍입니다! 저는 개인적으로 좋은 프로그래밍을 하기 위해선 함수형과 객체지향의 장단점을 알고 적시에 사용할 줄 알아야 한다는 생각이 드네요. 이 둘의 균형을 잘 잡는 것도 중요하다 생각하구요. React가 아무리 함수 지향 라이브러리에 가깝다고 하더라도, 엄밀히 함수형만을 따르지는 않는다는 것! 그러니 우리 객체 지향의 개념을 공부하도록 합시다!
'Front End > Javascript' 카테고리의 다른 글
[Javascript] 전역에서 객체를 선언하면 안되는 이유 (0) | 2023.11.02 |
---|---|
[Javascript] 객체 지향의 의존성 (0) | 2023.10.30 |
[Javascript] while문을 이용한 반복과 재귀를 이용한 반복문의 차이 (0) | 2023.10.27 |
[Javascript] else if 대신 if return 사용하는 이유? (0) | 2023.10.27 |
[JS] WeakMap이 무엇인가..? (0) | 2023.10.22 |