[S2U2] 객체 지향 프로그래밍

👽·2024년 3월 6일
0
post-thumbnail

Ch1. 객체 지향 (OOP, Object-oriented programming)

🔸 객체 지향 프로그래밍이란 프로그램을 객체 라는 독립적인 단위로 나누고, 각 객체는 데이터와 그 데이터를 조작하는 메서드를 포함함.

📌 클로저 모듈 패턴

🔸 객체.메서드() : 객체 내에 메서드 호출.

let counter1 = {
  value: 0,
  increase: function() {
    this.value++ // 메서드 호출을 할 경우, this는 counter1을 가리킵니다
  },
  decrease: function() {
    this.value--
  },
  getValue: function() {
    return this.value
  }
}

counter1.increase()
counter1.increase()
counter1.increase()
counter1.decrease()
counter1.getValue() // 2

💡 메서드 호출 방식을 이용할 때는 화살표 함수를 사용할 수 없음.

  • 화살표 함수 표현식은 this가 없기 때문에 메서드가 아닌 함수에만 사용해야 함.

클로저를 이용해 매번 새로운 객체 생성하기.

🔸 똑같은 기능을 하는 함수를 클로저 모듈 패턴을 이용해 여러 개 만들 수 있음. 복사, 붙여넣기 하지 않고 같은 코드를 재사용할 수 있음.

function makeCounter() {
  let value = 0; //private; 외부 함수 스코프에 있는 변수
  return {
    increase: function() {
      value++;
    },
    decrease: function() {
      value--;
    },
    getValue: function() {
      return value;
    }
  }
}

let counter1 = makeCounter() // counter1 객체는 value와 메서드를 가짐.
counter1.increase()
counter1.getValue() // 1

let counter2 = makeCounter()
counter2.decrease()
counter2.decrease()
counter2.getValue() // -2

다음 코드는 단순 객체를 사용한 경우로, 똑같은 기능을 하는 코드가 여러개 필요할 경우 복사, 붙여넣기를 통한 번거러움이 존재 (재사용성이 떨어짐)

let ageCounter1 = {
  age: 20,
  increase: function() {
    this.age++;
  },
  decrease: function() {
    this.age--;  
  },
  getAge: function(){
    return this.age;  
  }
}
ageCounter1.increase();
ageCounter1.getAge(); // 21

📌 클래스와 인스턴스

🔸 객체 지향 프로그래밍 : 하나의 모델이 되는 청사진(class)을 만들고, 그 청사진을 바탕으로 한 객체(instance object)를 만드는 프로그래밍 패턴.

클래스

🔸 일반적인 다른 함수와 구분하기 위해 클래스는 보통 대문자로 시작하며 일반명사로 만듬 (일반적인 함수는 동사를 사용하고 소문자로 시작)
🔸 생성자(constructor) 함수 : 인스턴스가 만들어질 때 실행되는 코드로 return 값을 만들지 않음.

  • ES5. 클래스는 함수로 정의 가능
    function Car(brand, name, color) {
    	// 인스턴스가 만들어질 때 실행되는 코드
    }
  • ES6. class라는 키워드를 이용해서 정의 가능 (최근 주로 사용)
class Car{
	contructor(brand, name, color) {
	// 인스턴스가 만들어질 때 실행되는 코드
	}
}

인스턴스

🔸 new 키워드 사용하여 만듦.

ES5에서 new 키워드를 붙이지 않을 경우 그냥 함수가 실행되며, 생성자 함수는 아무것도 반환하지 않기 때문에 undefined를 보임.
ES6에서 new 키워드를 붙이지 않을 경우 TypeError가 뜸

🔸 즉시, 생성자 함수가 실행되며, 변수에 클래스의 설계를 가진 새로운 객체(인스턴스)가 할당됨.

new 함수명() 동작 원리

function User(name, age) {
	// 📍 1. 빈 객체를 만들고, this에 할당 this={}
	this.name=name;
	this.age=age;
	// 📍 2. 함수 본문을 실행하면서 this에 property추가
	// 📍 3. return this
}
let user = new User() // 📍 0. 실행

🔸 각각의 인스턴스는 클래스의 고유한 속성과 메소드를 갖게 됨.

let avante = new Car('hyundai', 'avante', 'black');
let mini = new Car('bmw', 'mini', 'white');
let beetles = new Car('volkswagen', 'beetles', 'red');
  • 각각의 인스턴스(avante, mini, beetls)는 Car라는 클래스의 고유한 속성과 메소드를 가짐.

속성과 메서드

🔸 클래스에 속성과 메소드를 정의하고 인스턴스에서 이용.

🔸 클래스: 속성의 정의

  • this : 인스턴스 객체를 의미.
  • ES5
function Car(brand, name, color) {
	this.brand = brand;
    this.name = name;
    this.color = color;
}
  • ES6
class Car {
	constructor(brand, name, color) {
    	this.brand = brand;
   		this.name = name;
   		this.color = color;
    }
}
  • parameter로 넘어온 브랜드, 이름, 색상 등은 인스턴스 생성시 지정하는 값, this에 할당한다는 것은 만들어진 인스턴스에 해당 브랜드, 이름, 색상을 부여하겠다는 의미

🔸 클래스: 메소드의 정의

  • ES5. prototype을 이용해 정의.
function Car(brand, name, color) { /*생략*/ }
Car.prototype.refuel = function() {
    // 연료 공급을 구현하는 코드
}
  • ES6. 생성자 함수와 함께 class 키워드 안쪽에 묶어서 정의.
class Car {
	constructor(brand, name, color) { /* 생략 */ }
    refuel() {
    }
    drive() {
    }
}

🔸 인스턴스에서 사용

let avante = new Car('hyundai', 'avante', 'black'); // instance
avante.color; // 'black'
avante.drive(); // 'avante가 운전을 시작합니다'

📍 생성자 함수 정도만 객체 지향 프로그래밍에서 보편적인 개념이며, prototype이나 this는 오직 JavaScript에서만 유효한 용어.

  • ES5 방식
  • ES6 방식
class Car { // class
	constructor(brand, name, color) { // 생성자 함수
    	this.brand = brand; // this 객체: 이 예제에서는 avante === this
   		this.name = name;
   		this.color = color;
    }
    refuel() { // prototype 객체: 속성이나 메서드를 정의할 수 있음
    }
    drive() {
        console.log(this.name + '가 운전을 시작합니다');
    }
}
let avante = new Car('hyundai', 'avante', 'black'); // instance
avante.color; // 'black'
avante.drive(); // 'avante가 운전을 시작합니다'

🔸 배열은 전부 Array의 인스턴스로, 배열을 정의하는 것은 Array의 인스턴스를 만들어내는 것과 동일.

let arr = ['code', 'states', 'pre'] 
let arr = new Array('code', 'states', 'pre'); // 위 코드와 동일

arr.length; // 3
arr.push('course'); // 새 element를 추가

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

🔸 초기의 프로그래밍 언어(C, 포트란 등)는 순차적인 명령의 조합인 절차적 언어라고 부름 (절차 지향 프로그래밍).
🔸 현대의 언어(Java, C++, C# 등)는 단순히 별개의 변수와 함수로 순차적으로 작동하는 것을 넘어, 데이터와 기능이 별개로 취급되지 않고, 한 번에 묶여서 처리할 수 있는 객체 지향의 특징을 가짐.
🔸 자바스크립트는 엄밀히 말해 객체 지향 언어는 아니지만, 객체 지향 패턴으로 작성할 수 있음.

OOP

🔸 프로그램 설계 철학임.
🔸 모든 것은 "객체"로 그룹화됨. 이 객체는 한번 만들고 나면, 메모리상에서 반환되기 전까지 객체 내의 모든 것이 유지됨.
🔸 4가지 주요 개념을 통해 재사용성을 얻을 수 있음.

클래스와 인스턴스

🔸 클래스는 일종의 원형(original form)으로, 세부 사항(속성)이 들어가지 않은 청사진임. 세부 사항만 넣는다면, 객체가 됨.
🔸 인스턴스는 클래스의 사례(instance object).
🔸 클래스는 객체를 만들기 위한 생성자(constructor) 함수를 포함함. 이 생성자를 통해 세부 사항(속성)을 넣어줌. 함수에 인자를 넣듯, 속성을 넣을 수 있습니다.

OOP 주요 개념

🔸 Encapsulation (캡슐화)
🔸 Inheritance (상속)
🔸 Abstraction (추상화)
🔸 Polymorphism (다형성)

캡슐화 Encapsulation

🔸 데이터(속성)와 기능(메서드)을 하나의 단위로 묶는 것으로, 데이터(속성)와 기능(메서드)들이 느슨하게 결합됨.

  • 느슨한 결합(Loose Coupling) : 코드 실행 순서에 따라 절차적으로 코드를 작성하는 것이 아니라, 코드가 상징하는 실제 모습과 닮게 코드를 모아 결합하는 것.

🔸 느슨한 결합(Loose Coupling)에 유리 : 언제든 구현을 수정할 수 있음
🔸 은닉(hiding) : 구현은 숨기고, 동작은 노출시킴

  • 은닉화 : 내부 데이터나 내부 구현이 외부로 노출되지 않도록 만드는 것.
  • 디테일한 구현이나 데이터는 숨기고, 객체 외부에서 필요한 동작(메서드)만 노출.
  • 객체 내 메서드의 구현만 수정하고, 노출된 메서드를 사용하는 코드 흐름은 바뀌지 않도록 만들 수 있음. 반면, 절차적 코드의 경우 데이터의 형태가 바뀔 때에 코드의 흐름에 큰 영향을 미치게 되어 유지 보수가 어려움.
  • 더 엄격한 클래스는 속성의 직접적인 접근을 막고, 설정하는 함수(setter), 불러오는 함수(getter)를 철저하게 나누기도 함.

추상화 Abstraction

🔸 내부 구현은 아주 복잡한데, 실제로 노출되는 부분은 단순하게 만든다는 개념으로, 인터페이스가 단순해짐.
🔸 캡슐화가 코드나 데이터의 은닉에 포커스가 맞춰져있다면, 추상화는 클래스를 사용하는 사람이 필요하지 않은 메서드 등을 노출시키지 않고, 단순한 이름으로 정의하는 것에 포커스가 맞춰짐.
🔸 클래스 정의 시, 메서드와 속성만 정의한 것을 인터페이스라고 부르며, 이것이 추상화의 본질.

상속 Inheritance

🔸 부모 클래스의 특징을 자식 클래스가 물려받는 것. 다시 말해, 기본 클래스(base class)의 특징을 파생 클래스(derived class)가 상속받는 것.

다형성 Polymorphism

🔸 다양한 형태를 가질 수 있음. 객체 역시 똑같은 메서드라 하더라도, 다른 방식으로 구현될 수 있음.
🔸 같은 이름을 가진 메서드라도 조금씩 다르게 작동.
🔸 만일 언어 자체에서 다형성을 제공하지 않는다면, 기본(부모) 클래스에 종류별로 분기를 시켜서 하나하나 다르게 만들어야함.

OOP의 주요 개념에 대한 장점

  1. 캡슐화는 코드가 복잡하지 않게 만들고, 재사용성을 높임.
  2. 추상화는 코드가 복잡하지 않게 만들고, 단순화된 사용으로 변화에 대한 영향을 최소화.
  3. 상속 불필요한 코드를 줄여 재사용성을 높임.
  4. 다형성으로 인해 동일한 메서드에 대해 if/else if와 같은 조건문 대신 객체의 특성에 맞게 달리 작성하는 것이 가능해짐.

OOP 참고 사이트

📌 객체지향 차이점

TypeScript는 JavaScript에 더 많은 기능을 붙인 새로운 언어로, 보다 객체 지향적으로 디자인된 언어.

은닉화(private 키워드)의 한계

🔸 Java나 TypeScript라는 프로그래밍 언어는 클래스 내부에서만 쓰이는 속성 및 메서드를 구분시키기 위해 private이라는 키워드를 제공.
🔸 이러한 은닉화를 도와주는 기능이 JavaScript에서는 지원하는 브라우저가 적어, 널리 쓰이지 않음.

TypeScript 문법

class Animal {
  private name: string; //  name이라는 속성이 존재. 그러나 private 키워드가 붙어 있어서, 클래스 내부에서만 사용 가능
  constructor(theName: string) {
    this.name = theName;
  }
}
new Animal("Cat").name; // 사용 불가
// Property 'name' is private and only accessible within class 'Animal'. 

🔸 JavaScript에서는 은닉화를 돕기 위해서 일반적으로 클로저 모듈 패턴을 사용. 클래스/인스턴스 형태로 만들 때에는 ES2019부터 #이라는 키워드가 도입됨.

추상화(interface 키워드) 기능의 부재

🔸 추상화는, 속성과 메서드의 이름만 노출시켜서 사용을 단순화한다는 의미를 가지며, 인터페이스(interface)의 단순화를 의미함.
🔸 그러나 Java나 TypeScript 언어는 언어의 주요 기능으로 interface를 구현해 놓았지만, 이러한 부분은 JavaScript에는 존재하지 않는 기능임.

TypeScript 문법

interface ClockInterface {
  currentTime: Date;
  setTime(d: Date): void;
}
class Clock implements ClockInterface {
  currentTime: Date = new Date();
  setTime(d: Date) {
    this.currentTime = d;
  }
  constructor(h: number, m: number) {}
}
  • 인터페이스와 클래스 구현이 따로 정의되어 있음.

🔸 인터페이스의 이점은, 인터페이스가 일종의 규약처럼 간주되어, 인터페이스를 클래스로 구현하는 사람들이 이에 맞게 작성할 수 있게 도움. (인터페이스는 다양한 구현이 있을 수 있음)
🔸 이는 클래스를 이용하는 입장에서 노출된 인터페이스를 통해 "이 클래스는 메서드 이름이 의도한 바대로 작동할 것이다"라는 것을 명백히 드러나게 해 줌.
🔸 또한 실질적인 구현 방법을 공개하지 않고, 사용법을 노출시키기에도 유리.

🔸 어떤 클래스가 외부 공개용으로 모듈처럼 작동할 때에 인터페이스는 빛을 발하며, 이러한 인터페이스 사용의 대표적인 예가 API(Application Programming Interface)임.

CH2. 프로토타입

📌 프로토타입과 클래스

🔸 JavaScript는 프로토타입(Prototype) 기반 언어로, 여기서 프로토 타입은 원형 객체를 의미함. (mdn 문서)

class Human {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  sleep() {
    console.log(`${this.name}은 잠에 들었습니다`);
  }
}

let kimcoding = new Human('김코딩', 30);

Human.prototype.constructor === Human; // true
Human.prototype === kimcoding.__proto__; // true
Human.prototype.sleep === kimcoding.sleep; // true

Human이라는 클래스와 인스턴스, 그리고 프로토타입의 관계

🔸 __proto__ : 부모 클래스의 프로토타입, 혹은 '부모의 부모 클래스'의 프로토타입을 탐색할 수 있음.

Array(배열) 클래스와 인스턴스, 그리고 프로토타입의 관계


🔸 배열(arr)은 Array 클래스의 인스턴스이며, 프로토타입에는 다양한 메서드가 존재

📌 프로토타입 체인

🔸 부모 클래스가 자식 클래스로 속성과 메서드를 물려주는 과정을 상속이라하며, JavaScript에서 구현할 때에는 프로토타입 체인을 사용함.
🔸 상위 프로토타입 객체로부터 메소드와 속성을 상속 받을 수 있고 그 상위 프로토타입 객체도 마찬가지. 이를 프로토타입 체인이라 부름.
🔸 JavaScript에서에서는 extendssuper 키워드를 이용해서 상속을 구현할 수 있음. (mdn 문서)

🔸 class ChildClass extends ParentClass { ... } : ParentClass를 ChildClass로 상속.
🔸 super([arguments]) : 상위 클래스의 생성자를 호출해 super()의 매개변수를 통해 상위 클래스의 멤버를 상속받을 수 있음.

DOM과 프로토타입

  • DOM을 이용해, document.createElement('div')로 새로운 div 엘리먼트를 생성하고, 이렇게 생성된 divHTMLDivElement라는 클래스의 인스턴스임.
  • DOM 엘리먼트는 innerHTML과 같은 속성, 또는 append()와 같은 메서드가 있음. 각각의 엘리먼트가 해당 메서드나 속성이 있다는 것을 통해, Element라는 공통의 부모가 있음을 알 수 있음.


🔸 (화살표 방향은 부모를 가리킴) EventTarget의 부모로는, 모든 클래스의 조상인 Object가 존재.

let div = document.createElement('div');

div.__proto__  // HTMLDivElement.prototype
div.__proto__.__proto__ // HTMLElement.prototype
div.__proto__.__proto__.__proto__ // Element.prototype
div.__proto__.__proto__.__proto__.__proto__ // Node.prototype
div.__proto__.__proto__.__proto__.__proto__.__proto__ // EentTarget.prototype
div.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__ // Object.prototype
div.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__ // null

추가 (정리중..)

construnctor

🔸 클래스의 인스턴스 객체를 생성하고 초기화하는 메서드

constructor() { ... }
constructor(argument0) { ... }
  • 다른 모든 메서드 호출보다 앞선 시점인, 인스턴스 객체를 초기화할 때 수행할 초기화 코드를 정의할 수 있음.
  • constructor라는 이름의 메서드는 하나의 클래스에 오직 하나만 존재할 수 있음. (SyntaxError)

🔸 클래스에 생성자를 정의하지 않으면 기본 생성자를 사용하며, 아무것도 상속하지 않는 기본 클래스일 때의 기본 생성자는 빈 메서드.

constructor() {}

🔸 다른 클래스를 상속하는 경우, 기본 생성자는 자신의 매개변수를 부모 클래스의 생성자로 전달.

constructor(...args) {
  super(...args);
}
class ValidationError extends Error { // 기본 내장 클래스인 Error 클래스를 확장
  printCustomerMessage() {
    return `Validation failed :-( (details: ${this.message})`;
  }
}

try {
  throw new ValidationError("Not a valid phone number");
} catch (error) { // error 객체를 확인하여 어떤 종류의 예외인지 판별
  if (error instanceof ValidationError) { // error 객체가 ValidationError 클래스의 인스턴스인지 확인
    console.log(error.name); // ValidationError가 아니라 Error!, Error 클래스의 인스턴스로 캡처되었기 때문에 출력 결과는 "Error"가 됩
    console.log(error.printCustomerMessage());
    // ValidationError 클래스의 인스턴스라면, 해당 객체의 속성과 메서드를 사용할 수 있습니다. 여기서는 name 속성과 printCustomerMessage() 메서드를 호출하여 예외 정보를 출력
  } else {
    console.log("Unknown error", error);
    throw error;
  }
}
  • ValidationError 클래스는 아무런 초기화 동작도 필요하지 않으므로 생성자를 별도로 명시하지 않았으며, 대신 기본 생성자가 매개변수로 부모 Error 클래스의 초기화를 처리하고 있습니다.

🔸 그러나, 파생 클래스에 직접 생성자를 정의할 경우, super()를 통해 부모 클래스의 생성자를 호출해야 함. (ReferenceError)

super()
🔸 객체 리터럴 또는 클래스의 [[Prototype]] 속성에 접근하거나 슈퍼클래스의 생성자를 호출하는 데 사용.
🔸 파생 클래스에서는 {} 빈 객체가 만들어지고, this에 할당하는 작업을 건너뛰기 때문에 Error가 발생함. 따라서 먼저 super()를 통해 부모 constructor를 실행해줌.

class ValidationError extends Error {
  constructor(message) {
    super(message); // 부모 클래스의 생성자 호출
    this.name = "ValidationError";
    this.code = "42";
  }

  printCustomerMessage() {
    return `Validation failed :-( (details: ${this.message}, code: ${this.code})`;
  }
}

try {
  throw new ValidationError("Not a valid phone number");
} catch (error) {
  if (error instanceof ValidationError) {
    console.log(error.name); // 이제 ValidationError!
    console.log(error.printCustomerMessage());
  } else {
    console.log("Unknown error", error);
    throw error;
  }
}

🔸 파생 클래스에서, this를 사용하기 전에 반드시 super()를 먼저 호출해야 함. (ReferenceError)

constructor의 매개변수

🔸 필요한 경우 : 상속을 받는 하위 클래스(subclass)가 상위 클래스(superclass)의 특성을 상속받을 때, 하위 클래스가 별도의 초기화 작업을 수행해야 하는 경우가 있음.
🔸 이 경우에는 생성자 함수에 매개변수가 필요. 하위에서 전달받은 변수를 상위클래스로 넘겨줘야 전달인자로 넘겨줄 수 있음

class Parent {
	constructor(color) {
    this.color = color;
    }
}
class Child extends Parent {
	constructor(color) {
    	super(color);
    }
}
const C = new Child("red")
// 📍 C를 출력할 시 color : "red" 가 들어감
// 📍 매개변수를 작성하지 않으면, 전달인자를 넘겨줘도 undefined가 나옴.

instanceof

🔸 object instanceof constructor : object의 프로토타입 체인에 constructor.prototype이 존재하는지 판별
🔸 클래스 내부에서 선언된 메서드는 __proto__에 들어가 있음

profile
코린이👽

0개의 댓글