프로그래밍에서 필요한 데이터를 추상화시켜 상태와 행위를 가진 객체로 만들고, 객체들 간의 상호작용을 통해 로직을 구성하는 프로그래밍 방법이다.
대표적으로 java, python, c++ 등이 있다.
프로그램에서 사용되는 데이터 또는 식별자에 의해 참조되는 공간을 의미
값을 저장할 변수와 수행할 메서드를 서로 연관된 것들끼리 묶어서 만들어진다.
핵심은, 객체 내부에 자료형 필드와 함수가 함께 존재한다는 것이다.
객체들이 공통적으로 필요로 하는 속성이나 동작을 하나로 추출해 내는 작업
즉, 알고는 있지만 정확하게 표현하기 힘든 것들을 중요한 부분이나 특징점으로 잡아 설명하는 것을 추상화 한다고 한다.
프로그래밍에서 추상화는, 클래스를 정의할 때 불필요한 부분들을 생략하고 객체의 속성 중 중요한 것에만 중점을 두어 개략화 하는 것을 말한다.
이처럼 공통된 기능을 휴대폰에서 미리 구현을 하고, 상속을 통해 공통된 기능을 삼성폰과 아이폰에 내려주면 각자의 고유 기능을 집중적으로 개발할 수 있다.
즉, 추상화로 구현을 하면 새로운 객체를 만들 때 클래스의 고유한 기능만 새로 만들고 공통된 기능은 상속으로 받아쓰면 된다.
객체 지향 프로그래밍의 추상화는 객체의 관련 속성만 '표시'하는 데이터 추상화와 불필요한 세부 정보는 '숨긴다'는 제어 추상화가 있다.
어떤 클래스의 메서드를 사용하는 사용자에게 해당 메서드의 작동방식과 같은 내부 로직을 숨기는 것
핵심은 프로그래밍을 빠르게 설계하고 구현하기 위해 추상화를 사용한다고 생각하면 된다.
예를 들면 아래 달력과 관련된 코드를 보자.
import CountryCalendar; // 라이브러리 클래스를 사용하기 위해 불러옴
CountryCalendar cal = CountryCalendar.getInstance("countryName");
getInstance()
메서드에 나라이름을 넣어서 호출만 해주면 자동으로 달력값을 받을 수 있다.
public static CountryCalendar getInstance(Locale aLocale) {
return createCalendar(TimeZone.getDefault(), aLocale);
}
private static CountryCalendar createCalander(TimeZone zone, Locale aLocale) {
// ...
if(caltype != null) {
switch (caltype) {
case "buddhist" :
cal = new BuddhistCalendar(zone, aLocale);
break;
case "japanese" :
cal = new JapaneseImperialCalendar(zone, aLocale);
break;
case "gregory" :
cal = new GregoryianCalendar(zone, aLocale);
break;
}
}
}
안의 소스코드를 더 보면, 알 수 없는 메서드를 호출해 리턴하거나 클래스를 생성해 할당하고 있다.
위의 정보들은 라이브러리가 동작하는데 있어 필수적으로 필요한 것들이겠지만, 사용자는 몰라도 된다는거다.
대상을 간단한 개념으로 일반화 하는 과정
예를 들어 아이폰 객체를 추상화를 해보면, 아래와 같다.
abstract class 전자제품 {
}
abstract class 통신기기 {
}
abstract class 휴대폰 {
}
class 아이폰 {
}
그리고 이렇게 추상화 한 상위요소부터 각 요소에 맞는 기능들을 정의한다.
그리고 상위 요소가 가진 내용들을 가질 수 있도록 상속 관계를 설정해 이어준다.
abstract class 전자제품 {
전원기능();
}
abstract class 통신기기 extends 전자제품 {
통화기능();
}
abstract class 휴대폰 extends 통신기기 {
카메라기능();
게임기능();
}
class 아이폰 extends 휴대폰 {
전원기능() { ... }
통화기능() { ... }
카메라기능() { ... }
게임기능() { ... }
애플 제품 연동기능() { ... }
}
// → 최종적으로 아이폰 class는 전원, 통화, 카메라, 게임, 애플 연동 5가지 기능을 정의하여 설계된다
아이폰 제품 하나만 만들 때는 비효율적으로 보일 수도 있겠으나, 제품 종류가 늘어날 수록 장점이 된다.
데이터와 메서드를 하나의 단위로 묶어, 외부에서 접근하지 못하도록 보호하는 개념
외부에서 알 필요가 없는 부분은 감춤으로써 대상을 단순화하는 추상화의 한 종류
class Time {
private int hour; // hour는 외부에서 접근하지 못하게private으로 선언한다.
// Setter
public void setHour(int hour) {
if (hour < 0 || hour > 24) { // hour에 대한 유효성 검사
return;
} else {
this.hour = hour;
}
}
// Getter
public int getHour() {
return hour;
}
}
public class Main {
public static void main(String[] args) {
MyTime time = new MyTime();
// 유효하지 않은 parameter 입력
time.setHour(25);
System.out.println("Hour: " + time.getHour()); // 0
// 유효한 parameter 입력
time.setHour(13);
System.out.println("Hour: " + time.getHour()); // 13
}
}
위 코드는 시간을 메서드로 입력받고, 출력하는 간단한 캡슐화 예제이다.
클래스의 hour
멤버변수는 private으로 선언해 다른 클래스에서 접근을 못하도록 했다.
그리고 이 private hour
변수를 다루기 위해서는 setter method인 setHour(int hour)
와 getter method인 getHour
의 호출을 통해서만 가능하다.
🔶 캡슐화를 해야 하는 이유?
✅ 데이터 보호 - 외부로부터 클래스에 정의된 속성과 기능들을 보호
✅ 데이터 은닉 - 내부의 동작을 감추고 외부에는 필요한 부분만 노출
캡슐화가 중요한 이유는 불안정한 부분과 안정적인 부분을 분리해서 변경의 영향을 통제할 수 있기 때문이다.
불안정한 부분은 객체 내부로 추상화해서 변경을 최소화하고, 안전한 부분은 외부에 공개한다.
이렇게 객체의 외부와 내부를 구분해 구현을 변경할 수 있는 폭은 넓어지고, 변경의 영향은 통제할 수 있다.
또한, 코드 작성 이후 추가적인 요구사항에 유연하게 대처할 수 있다.
여러 개체들이 지닌 공통된 특성을 부각시켜 하나의 개념이나 법칙으로 성립하는 과정
예를 들어 삼성폰, 휴대폰, 통신기기, 전자제품은 모두 중복된 속성을 가지고 있다.
이런 속성을 클래스마다 만들어주기보다는 공통된 속성들을 하나로 모아놓은 클래스를 만들어, 그 클래스를 상속하면 효율적으로 프로그래밍이 가능하다.
상속을 통해 하위 클래스는 상위 클래스의 변수와 기능을 물려받기 때문에, 코드의 중복을 제거할 수 있다.
// super 클래스
class Parent {
String name;
String age;
public void say() {
System.out.println(name + age);
}
}
// sub 클래스 (상속 받음)
class Child extend Parent{
String hair;
public void myHair() {
System.out.println(hair);
}
}
// 상속받은 sub 클래스는 super 클래스의 속성들을 이용이 가능하다.
Child c = new Child();
c.name = "풍성한";
c.age = 17;
c.say();
c.hair = "M자형.."
c.myHair();
상속을 사용하기 위해서는 extends
키워드를 상속 받을 클래스에 명시해 사용한다.
같은 자료형에 여러 가지 타입으 데이터를 대이해 다양한 결과를 얻어낼 수 있는 성질
예를 들면 나라는 사람은 누군가에게는 자식, 누군가에게는 동생, 누군가에게는 애인 등이 될 수 있다.
이처럼 객체도 상황에 따라 여러가지 형태를 가질 수 있다는 것이 다형성의 핵심
대표적인 예로는 메서드 오버라이딩과 메서드 오버로딩이 있다.
🔶 메서드 오버라이딩
부모 클래스의 메서드를 자식 클래스에서 재정의해서 사용하는 것
자식 클래스는 부모 클래스의 메서드를 재활용하면서, 독자적인 기능을 추가할 수 있다.
🔶 메서드 오버로딩
같은 이름의 메서드를 다양한 매개변수 타입과 개수로 오버로딩해 사용하는 것
메서드 이름을 동일하게 유지하면서도, 다양한 상황에서 유연하게 대응할 수 있는 방법을 제공한다.
하지만 무분별한 오버로딩은 코드의 동작을 이해하는데 어려움을 줄 수 있기 때문에, 적절하게 사용해야 한다.
마지막으로, 코드로 예시를 들어보자면
일반 TV와 스마트 TV로 예시를 들어보겠다.
TV 클래스와 SmartTV 클래스가 상속 관계를 맺었다고 가정하자.
class TV {
void powerOn_Off() {} // 전원 켜기/끄기
void volumeOn_Off() {} // 볼륨 켜기/끄기
void channelOn_Off() {} // 채널 돌리기
}
class SmartTV extends TV {
void AI_Assistance() {} // 인공지능 기능
void shopping() {} // 쇼핑하기
}
TV 클래스에는 전원, 볼륨, 채널 켜기/끄기 기능밖에 없고, 스마트 TV 클래스에는 기본 TV 동작 메서드를 상속하고 추가적으로 AI 기능과 쇼핑 기능이 들어 있다.
Tv tv_remotecontrol = new Tv();
SmartTv smart_remotecontrol = new SmartTv();
보통은 일반적으로 동일한 클래스 타입의 참조 변수를 생성해 초기화 하여 사용해왔다.
TV tv_remoteControl = new SmartTV();
// Tv 클래스 타입의 참조변수 remoteControl를 선언한다.
// SmartTv의 인스턴스를 생성하고, 이 인스턴스의 주소값을 참조변수 remoteControl에 대입한다.
하지만 만일 부모 자식 상속 관계에 있으면 위와 같이 부모 타입으로 자식 클래스 타입을 받아 초기화 할 수 있다.
위의 상황을 그림으로 보면 이렇게 생각하면 된다.
마지 일반 텔레비전의 리모콘으로 스마트 티비를 조종하는 것 같은 느낌...
하지만 사용할 수 있는 기능이 줄어든 것 뿐이고, 스마트 TV 자체가 동작하는 것에는 문제가 없다.
이번에는 반대 상황이다.
SmartTv smart_remotecontrol = new Tv();
스마트 티비 리모콘으로 일반 티비를 다뤄보자.
스마트 TV에서만 작동 가능한 기능 버튼을 누르면 오류가 발생하게 된다.
일반 TV에는 해당 기능 메서드가 없으니까!
이를 막기 위해서 일반 TV 리모콘으로 스마트 TV를 다루지 못하게 차단하면 되긴 하지만, 이거 말고 다른 방법이 없을까??
이 때에는 다운캐스팅을 이용하면 가능하긴 하다.
Tv tv = new Tv();
SmartTv smart_remotecontrol = (SmartTv) tv;
위처럼 다운캐스팅을 사용해 코드를 작성하면, 빨간줄은 안쳐지지만 실제로 동작하지 않는다. (런타임 에러)
다운 캐스팅
부모 클래스가 자식 클래스 타입으로 캐스팅 되는 것
다운 캐스팅은 캐스팅 연산자 괄호를 생략할 수 없다.
또한 다운 캐스팅의 목적은 업캐스팅 한 객체를 다시 자식 클래스 타입의 객체로 되돌리는데 목적을 둔다. (복구의 목적)class Unit { public void attack() { System.out.println("유닛 공격"); } } class Zealot extends Unit { public void attack() { System.out.println("찌르기"); } public void teleportation() { System.out.println("프로토스 워프"); } } public class Main { public static void main(String[] args) { Unit unit_up; Zealot zealot = new Zealot(); unit_up = zealot; // 업캐스팅 // * 다운캐스팅(downcasting) - 자식 전용 멤버를 이용하기위해, 이미 업캐스팅한 객체를 되돌릴때 사용 Zealot unit_down = (Zealot) unit_up; // 캐스팅 연산자는 생략 불가능. 반드시 기재 unit_down.attack(); // "찌르기" unit_down.teleportation(); // "프로토스 워프" } }
업캐스팅 된 객체
unit_up
에서 자식 클래스에만 있는teleportation()
메서드를 실행해야 하는 상황이 왔을 때, 다운 캐스팅을 통해 자식 클래스 타입으로 회귀시킨 뒤 메서드를 실행하면 된다.
객체 지향 프로그래밍(OOP)의 개념과 4가지 특징
객체 지향 개념과 추상화 완벽 이해하기
자바의 다형성(Polymorphism) 완벽 이해하기
OOP 캡슐화 & 정보 은닉 개념 완벽 이해하기