객체 지향 프로그래밍 (Object-Oriented Programming, OOP)
컴퓨터 프로그래밍 패러다임 중 하나로, 현실 세계의 사물이나 개념을 객체(Object)로 보고 그 객체들 간의 상호작용으로 프로그래밍하는 방법이다.
객체 지향 프로그래밍은 프로그램을 유연하고 변경이 쉽게 만들기 때문에 대규모 소프트웨어 개발에 많이 사용된다.
구성 요소
- 클래스(Class) : 객체를 정의해놓은 것으로 객체를 생성하는데 사용된다. (객체를 생성하기 위한 설계도 또는 틀)
- 객체(Object) : 현실 세계의 사물이나 개념 (클래스에서 정의한 내용을 기반으로 생성된 실체)
- 메시지(Message) : 객체들 간의 상호작용에 사용되는 수단으로, 객체에 어떠한 행위를 하도록 지시하는 명령 또는 요구사항
- 인스턴스(Instance) : 클래스를 사용하여 실제로 메모리상에 할당된 객체
- 메서드(Method) : 다른 객체로부터 메시지를 수신할 때 메시지를 처리하는 로직 (객체가 수행하는 행위)
클래스는 객체를 생성하기 위한 설계도 역할을 하며, 클래스를 사용하여 객체를 생성한다.
이 때 생성된 객체가 실제 메모리에 할당되면 인스턴스라고 한다.
운전자가 브레이크를 밟아 차를 멈출 때 "차를 멈추다"라는 메시지를 자동차(객체)에게 전달한다.
"차를 멈추다"라는 메시지를 수신한 자동차(객체)는 차를 멈추는 것을 수행하는 로직을 메서드라고 한다.
특징
- 추상화 (Abstraction)
- 캡슐화 (Encapsulation)
- 상속 (Inheritance)
- 다형성 (Polymorphism)
추상화 (Abstraction)
예를 들어 포르쉐, 테슬라, 페라리가 있다고 하자. (모두 자동차의 종류)
이들은 모두 전진하다, 멈추다(기능), 핸들(속성)을 공통으로 가지고 있다.
이를 자동차라는 클래스(인터페이스)로 객체의 공통적인 속성과 기능을 추출하여 정의하는 것을 추상화라고 한다.
이처럼 공통 기능을 자동차에서 미리 정의를 하고 포르쉐, 테슬라, 페라리는 자동차라는 클래스(인터페이스)를 상속(구현)을 받아 미리 정의된 공통 기능을 각각 구현할 수 있다. 이렇게되면 공통 기능을 한 곳에서 정의를 했기 때문에 코드의 중복이 줄어든다.
캡슐화 (Encapsulation)
캡슐은 내용물을 외부에서 보이지 않게 포장한 형태를 말한다.
객체의 속성과 기능을 캡슐처럼 외부에서 직접 접근하지 못하도록 제한하는 정보은닉의 개념 중 하나이다.
자바로 예를 들면, Car 라는 클래스에 price 라는 변수가 있다고 하자.
Car는 price가 10,000 값으로 입력되어있는데 외부에서 임의로 다른 값으로 변경할 가능성이 있다.
그렇기 때문에 price는 외부에서 직접 접근하지 못하게 접근제어자로 제한하고 접근 가능한 메서드를 제공한다. getter/setter
또한 유효성 체크 로직을 통해 유효한 값만 넣을 수 있도록 제한할 수 있다.
public class Car {
// private으로 접근 제한 (private: Car 클래스 내에서만 접근 가능)
private int price = 10000;
// price에 접근해서 조회할 수 있는 getter 메서드 (public: 모든 클래스에서 접근 가능)
public int getPrice() {
return price;
}
// price에 접근해서 값을 변경할 수 있는 setter 메서드 (public: 모든 클래스에서 접근 가능)
public void setPrice(int price) {
// price에 대한 유효성 검사
if(price < 0) {
return;
}
this.price = price;
}
}
정보은닉
객체 지향에서 정보 은닉(data hiding)이란 사용자가 굳이 알 필요가 없는 정보는 사용자로부터 숨겨야 한다는 개념이다.
객체에 대한 구체적인 정보를 노출시키지 않도록 하는 기법
접근제어자
자바(JAVA)는 4가지의 접근제어자를 통해 객체의 캡슐화, 정보은닉을 구현한다
- public : 모든 클래스에서 접근 가능
- default : 같은 패키지에서만 접근 가능
- protected : 같은 패키지이거나 해당 클래스를 상속받은 클래스에서만 접근 가능
- private : 같은 클래스 내에서만 접근 가능
상속 (Inheritance)
객체들 간의 관계를 구축하는 개념이다.
상위 클래스(부모 클래스)로부터 속성(변수)과 기능(메서드)을 하위 클래스(자식 클래스)가 물려받는 것을 상속이라 한다.
상위 클래스 = 부모 클래스
하위 클래스 = 자식 클래스
위 그림으로 예시를 들어보자.
교통수단은 자동차의 상위 클래스이고,
자동차는 교통수단의 하위 클래스이자 포르쉐, 테슬라, 페라리의 상위 클래스이다.
포르쉐, 테슬라, 페라리 또한 자동차의 하위 클래스이다.
자동차는 상위 클래스인 교통수단으로부터 수송하다를 물려받았고,
포르쉐, 테슬라, 페라리는 상위 클래스인 자동차로부터 수송하다, 전진하다, 멈추다, 핸들을 물려받았다.
또한 오버라이딩을 통해 상위 클래스로부터 물려받은 기능(메서드)를 하위 클래스의 고유 기능으로 재정의할 수 있다.
이렇게 상속을 통해서 상위 클래스의 속성(변수)과 기능(메서드)을 하위 클래스에서 상속받아 코드 중복 제거, 코드 재사용의 장점을 얻을 수 있다.
다형성 (Polymorphism)
하나의 객체가 여러 가지 타입을 가질 수 있는 것
다형성은 객체 지향 프로그래밍의 상속과 인터페이스 개념을 통해 구현되며 코드 재사용성과 유연성을 향상시킨다.
위 그림에서 운전자는 자동차를 운전을 할 수 있다.
만약 운전자가 자동차의 한 종류인 포르쉐를 운전하다 사고가 났다. 다른 차로 변경해서 운전을 해야하는 상황에서 운전자는 자동차를 운전할 수 있기 때문에 포르쉐가 아닌 자동차의 다른 종류인 테슬라나 페라리로 변경해도 운전할 수 있다.
이렇게 자동차(객체)가 포르쉐, 테슬라, 페라리 등 여러 가지 자동차 종류(타입)를 가질 수 있는 것을 다형성이라고 한다.
다형성의 필수 조건
1. 상속관계
부모(상위) 클래스와 자식(하위) 클래스는 상속 관계여야 한다.
2. 오버라이딩 (Overriding)
다형성이 보장되기 위해 오버라이딩(자식 클래스 메서드의 재정의)이 반드시 필요하다.
3. 업캐스팅 (Upcasting)
자식 클래스의 객체가 부모 클래스의 타입으로 형변환(업캐스팅)해야 한다.
다형성이 하나의 객체가 여러 가지 타입을 가질 수 있는 것이라고 했다.
하지만 모든 타입을 가질 수 있는 것은 아니다.
아래 자바코드로 예시를 들어보겠다.
class Porsche extends Car {
void heatedSeat() {}
}
public class Car {
void drive() {}
}
1. 부모 타입의 참조변수로 자식 타입의 인스턴스를 참조할 수 있다.
Car car = new Porsche(); // O
자바에서는 다형성을 통해 부모 타입(Car)의 참조 변수로 자식 타입(Porsche)의 인스턴스를 참조할 수 있다.
2. 자식 타입의 참조변수로 부모 타입의 인스턴스를 참조할 수 없다.
Porsche porsche = new Car(); // X (컴파일 에러)
실제 인스턴스인 Car의 멤버 개수보다 Porsche 타입의 참조 변수 porsche가 사용할 수 있는 멤버 개수가 더 많기 때문이다.
Porsche 클래스가 Car 클래스를 상속받으면 Car 클래스의 모든 멤버(필드, 메서드)를 가지고 있기 때문에 Porsche 타입인 참조변수 porsche는 인스턴스 Car보다 사용할 수 있는 멤버 개수가 더 많다.
위 코드에서 참조변수 porsche는 Porsche 타입이므로 drive(), heatedSeat()을 사용할 수 있다.
(drive는 Car 클래스를 상속받았기 때문에 사용가능)
하지만 참조변수 porsche가 참조하고 있는 인스턴스는 Car 타입이고, Car 타입의 인스턴스에는 heatedSeat()가 존재하지 않기 때문에 문제가 발생한다.
이렇게 존재하지 않는 멤버를 사용할 가능성이 있으므로 자식 타입의 참조변수로 부모 타입의 인스턴스를 참조할 수 없다.
3. 참조변수가 사용할 수 있는 멤버의 개수는 인스턴스의 멤버 개수보다 같거나 적어야 한다.
Car car = new Porsche();
2번의 내용에 나왔던 것처럼
인스턴스 Porsche의 멤버 개수보다 참조 변수 car의 멤버 개수가 많으면, 존재하지 않는 멤버를 사용하는 문제가 발생할 수 있기 때문이다.
따라서 자식 타입의 참조변수로 부모 타입의 인스턴스를 참조할 수 없고,
참조변수가 사용할 수 있는 멤버의 개수는 인스턴스의 멤버 개수보다 같거나 적어야 한다.
Car 클래스와 Porsche 클래스의 상속관계를 도식화한 그림이다.
참조변수의 타입은 참조변수가 참조하는 인스턴스의 사용범위라고 이해하면 될 것 같다.
다형성을 활용한 자바코드
class Car extends Transportation {
void accelerate() {}
void stop() {}
}
class Porsche extends Car {
void heatedSeat() {}
}
class Tesla extends Car {
void selfDrive() {}
}
class Ferrari extends Car {
void coolSeat() {}
}
public class Transportation {
public void transport() {
System.out.println("수송하다");
}
public static void main(String[] args) {
// 1. 참조변수의 타입과 인스턴스의 타입이 같음
Transportation transportation = new Transportation();
Car car = new Car();
Porsche porsche = new Porsche();
Tesla tesla = new Tesla();
Ferrari ferrari = new Ferrari();
// 2. 부모 타입의 참조변수로 자식 타입의 인스턴스 생성
Transportation transportation1 = new Car();
Transportation transportation2 = new Porsche();
// 3. 부모 타입의 참조변수로 자식 타입의 인스턴스 생성
Car car1 = new Porsche();
Car car2 = new Tesla();
// 4. 자식 타입의 참조변수로 부모 타입의 인스턴스 생성
Car car3 = new Transportation(); // 컴파일 에러
Porsche porsche1 = new Car(); // 컴파일 에러
}
}
- 코드 중복 제거, 코드 재사용 : 상속을 기반으로 하기 때문에 상속이 가지고 있는 코드 중복 제거, 코드 재사용의 장점이 있다.
- 유지보수 용이 : 여러 객체를 하나의 타입으로 관리가 가능하기 때문에 유지보수가 용이하다.
- 유연성 : 다형성을 사용하면 객체를 추상화하고, 해당 객체를 사용하는 다른 객체에서 동일한 클래스 또는 인터페이스를 사용할 수 있다. 이를 통해 객체 간의 결합도를 낮추고 코드의 유연성과 확장성을 높일 수 있다.
💡 정리
객체지향 프로그래밍
컴퓨터 프로그래밍 패러다임 중 하나로 현실 세계의 사물이나 개념을 객체로 보고 그 객체들 간의 상호작용으로 프로그래밍하는 방법
구성 요소
- 클래스 : 객체를 정의해놓은 것으로, 객체를 생성하는데 사용된다.
- 메시지 : 객체들 간의 상호작용에 사용되는 수단으로, 객체에 어떠한 행위를 하도록 지시하는 명령 또는 요구사항
- 객체 : 현실 세계의 사물이나 개념
특징
- 추상화 : 객체의 공통적인 속성과 기능을 추출하여 정의
- 캡슐화 : 객체의 속성과 기능을 캡슐처럼 외부에서 직접 접근하지 못하도록 제한하는 정보은닉의 개념 중 하나
- 상속 : 상위 클래스(부모 클래스)로부터 속성(변수)과 기능(메서드)을 하위 클래스(자식 클래스)가 물려받는 것
- 다형성 : 하나의 객체가 여러 가지 타입을 가질 수 있는 것
📖 참고