객체지향이란 ?
- 프로그래밍에서 필요한 데이터를 추상화시켜 상태와 행위를 가진 객체로 만들고,
객체들 간의 상호작용을 통해 로직을 구성하는 프로그래밍 방법이다.- 대표적으로 많이 알려진 java 언어를 포함해서, Python, C++, C#, Kotiln 등이 있다.
여기서 그럼 객체란 무엇일까 ?
- 객체는 프로그램에서 사용되는 데이터 또는 식별자에 의해 참조되는 공간을 의미하며,
값을 저장할 변수(계좌)와 수행할 메서드(계좌에 대한 함수)를 서로 연관된 것들끼리 묶어서 만들어진다. (예를 들면, ‘자동차’나 ‘사람’처럼 생각)- 객체들은 서로 메서드 호출을 통해 메시지를 주고 받아 협력한다.
절차 지향은 객체 지향의 반대말로, 물이 위에서 아래로 흐르는 것처럼 순차적인 처리가 중요시 되며 프로그램 전체가 유기적으로 연결되도록 만드는 프로그래밍 기법을 의미한다.
- 장점 : 절차 지향은 컴퓨터의 처리 구조와 유사해 실행속도가 빠르고, 각 프로그램의 흐름을 쉽게 추적할 수 있다는 장점이 있지만,
- 단점 : 각 코드가 매우 유기성이 높아 유지보수가 어렵고, 실행순서가 정해져 있으므로 코드의 순서가 바뀌면 동일한 결과를 보장하기 어렵다는 단점이 있다.
객체 지향 프로그래밍은 가장 대표적인 세 가지의 프로그래밍 패러다임 중 하나로, 상태(데이터)와 그 데이터를 조작하는 프로세스(메서드)가 같은 모듈 내부에 배치되는 프로그래밍 방식을 의미한다.
객체 지향 프로그래밍(OOP)은 프로그램을 객체들의 집합으로 볼 수 있는 설계 원칙을 제공한다.
따라서 객체 지향 프로그래밍의 방식은 데이터와 기능이 밀접하게 연결되어 있기 때문에, 코드의 구조와 동작을 직관적으로 파악할 수 있다.
예를 들면, ‘자동차’라는 객체가 있다고 가정할 때, 이 객체는 ‘색상’, ‘속도’와 같은 데이터와 ‘출발’, ‘정지’와 같은 기능(메서드)을 가지게 됩니다. 따라서, 만약 문제가 발생한다면 ‘자동차’라는 객체의 내부만 살펴보면 되는것
또한, 객체 지향의 특성으로 인해 하나의 객체에 정의된 기능이나 데이터 구조는 다른 객체에서도 쉽게 재사용할 수 있습니다. 이로 인해 코드의 재사용성과 확장성이 향상되고, 결과적으로 개발 시간을 효율적으로 관리할 수 있게 된다.
추상화란 객체들이 공통적으로 필요로 하는 속성이나 동작을 하나로 추출해 내는 작업을 의미한다. 즉, 불필요한 세부 사항을 생략하고, 중요한 특징만을 강조함으로써 코드를 더욱 간결하고 관리하기 쉽게 만드는 원칙.
알고는 있지만, 정확하게 표현하기 힘든 것들을 중요한 부분이나 특징점으로 잡아 설명하는 것을 추상화 한다고 할 수 있다.
프로그래밍에서 추상화는 클래스를 정의할 때 불필요한 부분들을 생략하고 객체의 속성 중 중요한 것에만 중점을 두어 개략화 하는 것을 의미한다.
쉽게 예를 들자면, 삼성폰, 아이폰이라는 객체가 있을 때, 이 객체들을 하나로 묶을 때 공통적인 특징을 휴대폰으로 묶어서 이름을 붙이는 것을 추상화라고 할 수 있다.
/** Abstraction **/
interface Human {
name: string;
setName(name);
getName();
}
// 인터페이스에서 상속받은 프로퍼티와 메소드는 구현하지 않을 경우 에러가 발생합니다.
class Employee implements Human {
constructor(public name: string) { }
// Human 인터페이스에서 상속받은 메소드
setName(name) { this.name = name; }
// Human 인터페이스에서 상속받은 메소드
getName() { return this.name; }
}
const employee = new Employee("");
employee.setName("이용우"); // Employee 클래스의 name을 변경하는 setter
console.log(employee.getName()); // Employee 클래스의 name을 조회하는 getter
위 코드에서는 Employee 클래스는 Human 인터페이스에서 정의한 name 프로퍼티와 setName, getName 메서드를 강제로 구현하게 되있다.
따라서, 동일한 인터페이스인 Human 인터페이스를 구현하는 모든 클래스는 해당 인터페이스에 선언된 프로퍼티와 메서드를 구현해야 함을 보장하게 되었습니다. 이로 인해 코드의 일관성을 유지할 수 있다.
상속(Inheritance)은 하나의 클래스가 가진 특징(함수, 변수 및 데이터)을 다른 클래스가 그대로 물려 받는 것을 의미한다.
/** Inheritance **/
class Mother { // Mother 부모 클래스
constructor(name, age, tech) { // 부모 클래스 생성자
this.name = name;
this.age = age;
this.tech = tech;
}
getTech(){ return this.tech; } // 부모 클래스 getTech 메서드
}
class Child extends Mother{ // Mother 클래스를 상속받은 Child 자식 클래스
constructor(name, age, tech) { // 자식 클래스 생성자
super(name, age, tech); // 부모 클래스의 생성자를 호출
}
}
const child = new Child("이용우", "28", "Node.js");
console.log(child.name); // 이용우
console.log(child.age); // 28
console.log(child.getTech()); // 부모 클래스의 getTech 메서드 호출: Node.js
Mother부모 클래스를 상속받은Child자식 클래스에서name,age멤버 변수를 직접 접근하여 호출하고,Mother부모 클래스에서 정의된getTech()메소드를 호출할 수 있게 되었다.
이처럼, 상속의 이러한 특성 덕분에 코드를 재사용하기 수월해지고, 중복을 줄일 수 있게되는 장점이 있는것.
다형성(Polymorphism)은 하나의 객체(클래스)가 다양한 형태로 동작하는것을 의미합니다. 이는 객체가 가진 특성에 따라 같은 기능이 다르게 재구성되는 것을 의미한다.
비유적으로 표현하면, 한명의 남자는 어떠한 상황에서 여러가지 역할이 있는데,
누군가에게는 친구가, 자식에게는 아버지, 동아리에는 동호회 리더, 아내에게는 남편, 부모님에게는 자식이 될 수도 있다.
이처럼 객체도 상황에 따라 여러가지 형태를 가질 수 있다는 것이 다형성의 핵심.
/** Polymorphism **/
class Person {
constructor(name) { this.name = name; }
buy() {}
}
class Employee extends Person {
buy() { console.log(`${this.constructor.name} 클래스의 ${this.name}님이 물건을 구매하였습니다.`); }
}
class User extends Person {
buy() { console.log(`${this.constructor.name} 클래스의 ${this.name}님이 물건을 구매하였습니다.`); }
}
const employee1 = new Employee("이용우");
const employee2 = new Employee("김창환");
const user1 = new User("이태강");
const user2 = new User("김민수");
const personsArray = [employee1, employee2, user1, user2];
// personsArray에 저장되어 있는 Employee, User 인스턴스들의 buy 메소드를 호출합니다.
personsArray.forEach((person) => person.buy());
// Employee 클래스의 이용우님이 물건을 구매하였습니다.
// Employee 클래스의 김창환님이 물건을 구매하였습니다.
// User 클래스의 이태강님이 물건을 구매하였습니다.
// User 클래스의 김민수님이 물건을 구매하였습니다.
위의
personsArray.forEach()예제에서person변수는Person클래스를 상속받은Employee또는User클래스의 인스턴스를 참조한다.
각 인스턴스의buy메서드를 호출하는 것은 동일하지만,Employee와User클래스의buy메서드는 서로 다른 행위를 수행하고 있는 것을 확인할 수 있습니다. 이것이 바로 다형성(Polymorphism)의 특징이다.
객체 내부의 세부적인 사항을 감추는 것, 즉 중요한 정보를 외부로 노출시키지 않도록 만드는 것을 캡슐화(Encapsulation)라고 한다.
집의 현관문을 생각해본다면, 저희는 집 안을 외부에게 노출시키지 않기 위해 현관문을 잠글 수 있다. 여기서 현관문은 우리가 데이터에 접근할 수 있는 방법을 제한하는 메서드와 같다.
Javascript 는 완벽한 캡슐화를 지원하지 않는다. 그러나, 개발자들은 변수 앞에 언더바(_)를 붙여 내부의 변수를 숨긴것 “처럼” 나타내는 규칙을 따르곤 한다.
/** Encapsulation **/
class User {
private name: string; // name 변수를 외부에서 접근을 할 수 없게 만듭니다.
private age: number; // age 변수를 외부에서 접근을 할 수 없게 만듭니다.
setName(name: string) { // Private 속성을 가진 name 변수의 값을 변경합니다.
this.name = name;
}
getName() { // Private 속성을 가진 name 변수의 값을 조회합니다.
return this.name;
}
setAge(age: number) { // Private 속성을 가진 age 변수의 값을 변경합니다.
this.age = age;
}
getAge() { // Private 속성을 가진 age 변수의 값을 조회합니다.
return this.age;
}
}
const user = new User(); // user 인스턴스 생성
user.setName('이용우');
user.setAge(30);
console.log(user.getName()); // 이용우
console.log(user.getAge()); // 30
console.log(user.name); // Error: User 클래스의 name 변수는 private로 설정되어 있어 바로 접근할 수 없습니다.
User클래스를 선언하고 내부에는name,age멤버 변수를 초기화 하였다.
여기서 특별하게private라는 접근 제한자(Access modifier)를 사용하고 있는데, 인스턴스 내부에서만 해당 변수에 접근이 가능하도록 제한하는 문법이다.- 따라서, User 클래스의 name, age 멤버 변수는 클래스 외부에서는 어떠한 방법으로도 직접 접근을 할 수 없다. 오로지 setter만 변수를 변경할 수 있고, getter만 변수를 조회할 수 있게 되었다.