객체 지향 프로그램이란 무엇이고 어떻게 활용할 수 있는지에 대한 것은 개발자 면접에서 자주 나오는 질문 중의 하나!
모든 데이터를 현실에 빗대어 객체로 다루는 프로그래밍 기법
프로그래밍에서 필요한 데이터를 추상화시켜, 상태와 행위를 가진 객체로 만들고
객체들 간의 상호작용을 통해, 로직을 구성하는 프로그래밍 방법
객체지향 프로그래밍을 지원하는 언어 : C++, C#, Java, Python, JavaScript, Ruby, Swift ...
객체 지향 vs 비객체 지향적 코드
// 비객체 지향적 코드 1
int hour1, hour2, hour3
int minute1, minute2, minute3
float second1, second2, second3
// 객체 지향적 코드 1
Time t1 = new Time();
Time t2 = new Time();
Time t3 = new Time();
// 비객체 지향적 코드 2
int[] hour = new int[3];
int[] minute = new int[3];
int[] second = new int[3];
// 객체 지향적 코드 2
int[] hour = new int[3];
t[0] = new Time();
t[1] = new Time();
t[2] = new Time();
정의한 기능의 흐름에 따라, 순서대로 동작을 추가하며 프로그램을 완성하는 방식
간단한 프로그램일 일 경우 : 코드 흐름이 직관적인 장점
규모 큰 프로그램 일 경우 : 더 이상 직관적인 코드 X
→ A → B → C 라는 동작을 구현 도중, C 에서 A 로 돌아가야할 상황에서 goto 를 활용하고 이를 무분별하게 사용함으로 인해 코드가 지저분해진다.
그래서 등장한 것이 절차적(구조적) 프로그래밍이다.
데이터와 함수가 논리적으로 묶여있을 수 없는 구조이므로, 동작이 추상적이다.
따라서, 이를 묶기 위한 패러다임으로 '객체 지향 프로그래밍' 이 등장
참고: POP (Procedural-Oriented Programming, 절차 지향 프로그래밍)
private으로 선언한 데이터는 자기 자신을 통해서만 접근 가능
객체와 함수의 재사용이 쉽다.
여러 종류의 자동차들이 공통적으로 가지고 있는 가속, 브레이크, 속도와 같은 것들을 모아
자동차라는 객체를 모델링한다.
객체에서 공통된 속성과 행위를 추출 하는 것
공통의 속성과 행위를 찾아서, 타입을 정의하는 과정
불필요한/복잡한 정보는 숨기고 중요한 정보만을 표현함으로써, 프로그램을 간단하게 만드는 것
부모 클래스에 정의된 모든 것(속성(필드)과 행위(메서드))을 자식 클래스가 물려받는다.
부모 객체에 있는 것만 수정하면, 자식 객체 전부 반영이 된다. (예시 : MachineSystem 에서 bluePrint: BluePrint)
자식클래스에서 새로운 함수 추가 or 부모 클래스의 함수 재정의(Overriding)해 사용할 수 있다.
→ 새로운 클래스(자식)가 기존의 클래스(부모)의 데이터와 연산을 이용할 수 있다.
코드 작성이 간결
최소한의 상속 규칙만 지키면 되므로, 프로젝트의 확장 시 오류 최소화
클래스의 재사용 쉬움
상위 클래스의 변경이 어려워짐
불필요한 클래스 증가 할 수 있다.
상속 자체가 잘못 된 경우도 발생할 수 있다.
Car 클래스를 토대로 A자동차 객체와 B자동차 객체를 만들었을 때,
이 두 객체의 경적 소리가 다르다면 ‘경적을 울리다’라는 행위(horn(); 메서드의 구현)를 다르게 재정의 하여 사용 가능
여러 가지 형태를 가질 수 있는 능력
서로 다른 클래스의 객체가 같은 동작 수행 명령을 받았을 때, 각자의 특성에 맞는 방식으로 동작하는 것
하나의 변수명, 함수명이 상황에 따라 다른 의미로 해석 될 수 있는 것
어떠한 요소에 여러 개념을 넣어놓는 것
예시
'비싼 자동차'라는 개념을 일반화(상속)하여 람보르기니, 페라리 등의 객체를 만들었다.
'비싼 자동차'라는 클래스의 '배기음 재생' 이라는 메소드를 실행했을 때,
자식 클래스들이 각기 다른 배기음을 내뿜는다.(다형성)
다형성 + 상속은 시너지가 엄청나다.
다형성 구현을 통해 코드를 간결하게 해주고, 유연함을 갖추게 해준다.
구체적으로 현재 어떤 클래스 객체가 참조되는지는 무관하게 헐렁하게 프로그래밍하는 것이 가능하다
부모 클래스 타입의 참조변수로, 자식 클래스의 인스턴스를 참조할 수 있도록 한다.
// 기존의 방식
Tv t = new Tv(); // Tv 인스턴스를 다루기 위해서, Tv타입의 참조변수를 사용
SmartTv s = new SmartTv(); // SmartTv 인스턴스를 다루기 위해서, SmartTv타입의 참조변수를 사용
// 상속에서는 이렇게도 가능하다
Tv t = new SmartTv();
// 기존의 참조
SmartTv s = new SmartTv();
// 상속에서의 참조
Tv t = new SmartTv();
SmartTv s = new Tv(); // 에러
Product 부모 클래스
class Product {
int price;
int point;
}
TV 자식 클래스
class Tv extends Product {
}
Computer 자식 클래스
class Computer extends Product {
}
Buyer 클래스
class Buyer {
int money = 1000;
int point = 0;
void buy (Tv t) { // 구입할 제품을 매개변수로 받는다 -> Tv를 구매하기 위해 매개변수의 타입을 Tv로
money = money - t.price;
...
}
void buy (Computer c) { // Computer를 구매하기 위해
money = money - c.price;
...
}
}
Buyer 클래스
class Buyer {
int money = 1000;
int point = 0;
void buy (Product p) { // 구입할 제품을 매개변수로 받는다 -> Tv를 구매하기 위해 매개변수의 타입을 Tv로
money = money - p.price;
...
}
}
인터페이스 타입의 참조변수로, 이를 구현한 클래스의 인스턴스를 참조할 수 있음
인터페이스 타입으로 형변환 가능
메서드의 매개변수의 타입으로도 인터페이스가 사용될 수 있음
// Fightable 인터페이스를 구현한 Fighter 클래스
class Fighter extends Unit implements Fightable {
public void mov(int x, int y) {
}
public void attack(Fightable f) { // attack의 매개변수로 Fighter 인스턴스를 넘겨줄 것이다. attck(new Fighter()) 과 같은 의미
}
}
리턴 타입이 인터페이스
Fighter method() {
Fighter f = new Fighter();
return f; // method()의 리턴 타입은 Fighter 인터페이스이므로, Fighter 인터페이스를 구현한 Fighter클래스의 인스턴스 주소를 반환
// return new Fighter(); 로 묶을 수 있다
}
제네릭 : <타입>
or List<T>
(모든 타입 다 받음) --> 우선순위! (실제로 더 多 사용)
다형성 : 상속과 연결지어서
List<TV> tvList = new ArrayList<TV>(); // OK : ArrayList가 List구현
List<TV> tvList = new LinkedList<TV>(); // OK : LinkedList가 List구현
제네릭 클래스간의 다형성은 성립됨
클래스 단위로 모듈화시켜서 개발하므로, 업무 분담이 편리하고 대규모 소프트웨어 개발에 적합
클래스 단위로 수정 가능하므로, 유지 보수가 편리
클래스 재사용 or 상속을 통한 확장으로, 코드 재사용이 용이
(상대적으로) 처리속도 느리다.
객체의 수 多 면, 용량 大
설계 시, 시간 多 노력 多 필요
참고: OOP (객체지향), AOP(관점지향)
참고: 객체지향 프로그래밍이란?
참고: 객체지향 프로그래밍 제대로 이해하기
참고: Generics 02 제네릭스 용어, 제네릭 타입과 다형성