OOP (Object Oriented Programming) & Inheritance Patterns
프로그래밍 방식은 이분법적으로 나눌 수 있는게 아니다.
"지향"이라는 단어를 사용한 만큼에 있어 어떤 방식에서 무엇을 좀더 중요시하며 프로그래밍을 할 뿐,
두 방식은 서로를 배제하지 않는다. 두 방식들이 공존하는 부분도 있다는 점 유의하자.
절차 지향 언어
프로그램을 하나의 절차로 묶어 기능들(함수)을 순서대로 실행함으로써 개발자가 의도한 대로 작업을 처리하는 방식.
ex) C, COBOL, Fortran, Perl HTML 등
객체 지향 언어
"클래스"라고 불리는 데이터 모델들의 청사진(블루 프린트)를 사용해서 프로그래밍 하는 방식.
프로그램(프로젝트)의 모든 요소(속성, 메서드)는 객체를 이용해서 형성된다.
ex) Java, C++, C#, Python, PHP, JavaScript, Ruby, Object Pascal, Object-C, Swift, Scala, Common Lisp
OOP (Object Oriented Programming)
프로그램 디자인 철학.
OOP의 모든것은 self-sustainable(자급자족?)할 수 있는 object(객체)들로 이루어져 있다.
각 오브젝트가 다른 오브젝트에 의존적인 관계가 아니라 독립적인 위치를 갖는다고 할 수 있다.
그리고 각 객체들은 재사용성을 갖는다.
"클래스" 로 선언된 프로토타입은 "인스턴스" 라는 오브젝트를 찍어낼 수 있다.
클래스는 몇 번이든 재사용 되어 인스턴스를 생성할 수 있다.
클래스는 내부에 속성과 메서드를 갖는다.
예시)
- 클래스 : 붕어빵 틀의 설계도
- 속성 : 붕어빵 틀의 크기, 색깔 등 (붕어빵 틀의 정보)
- 메서드 : 뒤집기, 뚜껑열기, 뚜껑닫기 (붕어빵 틀의 기능)
- 인스턴스(객체) : 붕어빵 틀
클래스를 이용해서 찍어낸(생성한) 인스턴스엔 클래스에 명시되어있는 속성과 메서드가 그대로 들어있다.
각 인스턴스 마다 속성값은 다르게 할당할 수 있다.
속성값을 할당하는 방법은
인스턴스를 생성할 때 클래스의 "생성자" 를 이용해서 인스턴스가 생성될 때 할당한다.
OOP Basic Concepts
Encapsulation 캡슐화
코드 복잡도를 줄이고, 재사용성을 높인다
클래스 안에 데이터(속성)와 기능(메서드)를 작성해서 각 클래스가 따로따로 속성과 메서드를 갖도록 작성한다.
쉽게 말해 한 곳에 데이터(속성)와 기능(메서드)을 작성함으로써 인스턴스가 온전히 자기자신의 데이터와 기능을 갖도록 한다.
인스턴스에서 메서드를 사용할 때 클래스에 작성된 메서드 코드와 작동방식을 알 수 없도록 클래스 내부에 작성.
언제든 구현 코드를 수정할 수 있다.
Java같은 다른 언어에서는 외부에서 접근할 수 없도록 보호(private)해주는 장점도 있다.
//Encapsulation 되지 않은 예
let color = 'gray';
let age = 2;
let hair = 'dirty';
function grooming(){
hair = 'clean';
}
//Encapsulation 적용한 예
let cat = {
color: 'gray',
age: 2,
hair: 'dirty',
grooming: function(){
this.hair = 'clean';
}
};
cat; //{color: 'gray', age: 2, hair: 'dirty', grooming: f}
cat.grooming();
cat; //{color: 'gray', age: 2, hair: 'clean', grooming: f}
Inheritance 상속
같은 코드 중복을 방지
부모를 상속받은 객체는 부모 클래스의 속성, 메서드를 그대로 물려 받는다.
예를 들어 Car 클래스를 상속받은 Small_Car클래스는 Car 클래스의 속성과 메서드를 사용할 수 있고,
Small_Car 클래스로 생성한 인스턴스 또한 Car 클래스의 속성과 메서드를 사용할 수 있다.
// ES5 Pseudoclassical Subclassing 상속
var Human = function(name){
this.name = name;
}
Human.prototype.sleep = function(){
console.log('zzz');
}
let steve = new Human('steve');
var Student = function(name){
Human.call(this, name); //Human의 생성자에 this(Student)를 전달.
//윗줄을 this.name = name; 으로써도 되지만 그러면 부모생성자를 사용하지 않는다.
}
/*
Student에 Human을 상속하는 법.
*john.__proto__ = Human.prototype *DEPRECATED
(.__proto__는 참조용으로만사용)
*Student.prototype = Human.prototype 하면 Human.prototype의 참조값을
가져오기 때문에 Student.prototype이 변경될 때 Human.prototype도 변경된다.
*/
Student.prototype = Object.create(Human.prototype);
//Human.prototype을 복사해서 Student에 상속.
Student.prototype.constructor = Student; //Student의 생성자를 따로 생성.
//윗줄이 없으면 Student의 생성자는 없고 Human의 생성자를 사용한다.
Student.prototype.learn = function(){
console.log('learning...');
}
let john = new Student('john');
steve.sleep();
john.sleep();
john.learn();
john instanceof Student; //true
john instanceof Human; //true
// ES6 Class..extends 키워드를 사용한 상속
class Car {
constructor(name, color){
this.name = name;
this.color = color;
this.top_speed = 100
}
drive(){
console.log(this.color+' '+this.name+' is '+this.top_speed+"km/h now");
}
}
class BMW extends Car {
constructor(name, color){ //부모생성자와 arguments(파라미터)가 동일하다면 자식생성자는 생략할 수 있다
super(name, color); //this를 사용하기 전에 반드시 super()로 부모 클래스의 생성자를 호출해야 한다
this.top_speed = 200; //자식클래스에서 속성을 새로 설정.
}
//상속받은 메서드를 또 작성하거나 수정하지 않았음. (오버라이딩 하지 않음)
}
let car = new Car('car', 'black');
car.drive();
let bmw = new BMW('BMW', 'red');
bmw.drive(); //상속받은 메서드는 자식 클래스에 구현해놓지 않아도 사용할 수 있다.
Abstraction 추상화
코드 복잡도를 줄이고, 코드 변경시 영향을 독립적으로 한다
속은 복잡하지만 사용자가 쓰기엔 간단한 것.
함수에 적용하면 평균을 구하는 함수는 내부가 복잡하지만 사용자는 숫자만 넣으면 함수를 작동시킬 수 있다.
클래스가 청사진(설계도)라면 추상 클래스는 미완성 청사진이다.
미완성 상태로 작성하기 때문에 인터페이스가 간단하고,
구현 코드 변경시에 영향이 적다.(직접 구현되지 않았기 때문에)
미완성이기 때문에 추상 클래스로는 인스턴스를 생성할 수 없고,
추상 클래스를 상속받은 클래스에서 직접 추상 클래스를 완성시켜서 사용해야 한다.
같은 방법으로 추상 메서드 또한 상속을 통해 자식 클래스에서 구현(완성)되어야 사용할 수 있다.
추상화는 반드시 자식 클래스에서 코드를 구현해야 하는 경우에 사용한다.
Polymorphism 다형성
비효율적인 switch/case 를 재조정
같은 클래스를 상속한 자식들이
상속받은 메서드를 사용할 때 각기 다른 코드로 메서드를 구현할 수 있는 것을 의미한다.
Car 클래스를 상속받은 BMW 클래스와 Audi 클래스가 있다.
자식 클래스는 모두 Drive()라는 메서드를 상속받았다고 하자.
이 때, BMW 클래스의 Drive()와 Audi 클래스의 Drive()는
메서드명은 같지만 각각 다른 기능을 수행할 수 있다는 말이다.
부모 클래스의 메서드를 상속받은 자식 클래스 안에서 상속받은 메서드를 수정하는 것을
"오버라이딩" 이라고 한다.
오버라이딩을 통해 부모 객체를 상속받은 자식 객체들이 단순히 부모 객체의 메서드를 그대로 사용해야만 하는 것이 아닌,
자식 객체에게 적합한 방법으로 메서드를 적절히 수정해서 사용할 수 있다.