객체지향 프로그래밍-1
객체지향 프로그래밍(OOP)의 개념과 원칙
객체지향 프로그래밍(Object-Oriented Programming, OOP) 은 프로그램을 객체 들의 상호 작용으로 구성하는 프로그래밍 패러다임임. 절차지향 프로그래밍이 함수나 절차의 순서에 집중한다면, 객체 지향은 현실 세계의 사물에 해당하는 객체들을 만들고 이들 간의 협력 을 통해 프로그램을 구성함.
이렇게 함으로써 코드의 재사용성 과 유지보수성 을 높이고, 현실 세계 모델링에 보다 가깝게 프로그램을 설계할 수 있음.
클래스(Class) 와 객체(Object) 의 차이
클래스 와 객체 는 객체지향의 핵심 개념으로, 밀접하게 연관되어 잇지만 구분되는 개념임. 클래스를 흔히 설계도(blue print) 에 비유하고, 객체를 그 설계도로부터 생성된 실체(instance) 에 비유함.
클래스 : 어떤 객체들이 가져야 할 속성(필드) 과 동작(메서드) 을 정의한 설계도임. 예를 들어
동물
클래스는 모든 동물이 공통적으로 가지는 속성(예: 이름, 나이) 과 동작(예: 소리내기)을 정의할 수 있음. 클래스 그 자체로는 추상적인 개념이며, 실제 메모리 상에 존재하지 않음.(인스턴스화되지 않으면 메모리를 차지하지 않음)객체 : 클래스라는 설계도를 기반으로 메모리에 할당된 실제 사례(instance) 임. 객체는 클래스가 정의한 속성과 동작을 실제로 갖춘 구체적인 존재 임. 하나의 클래스 설계도로부터 여러개의 객체를 생성할 수 있음. 예를 들어
동물
클래스에서Dog
객체,Cat
객체 등을 만들어 낼 수 있음. 각 객체는 클래스에서 정의한 대로 속성 값을 가지고, 메서드를 정의할 수 있음.
요약하면, 클래스는 객체를 생성하기 위한 틀이고, 객체는 그 틀을 기반으로 만들어진 실제 독립적인 개체 임.
객체지향 프로그래밍의 네 가지 주요 개념
객체지향 프로그래밍에는 프로그램을 효과적으로 구조화하기 위한 네 가지 핵심 원칙이 있음. 캡슐화 , 상속, 다형성, 추상화 . 각 개념을 하나씩 설명하면 다음과 같음.
1. 캡슐화(Encapsulation)
캡슐화 는 객체의 속성과 동작을 하나로 묶고, 구현 세부 내용을 외부로부터 숨기는 특징임. 마치 약의 캡슐처럼 객체 내부를 감싸서 보호한다 하여 붙여진 이름임. 캡슐화의 주요 목적은 데이터 보호 와 정보 은닉 임.
객체는 내부적으로 여러 필드(데이터) 와 메서드(기능) 를 갖는데, 캡슐화를 통해 이들을 외부에서 직접 접근하지 못하게 막고, 필요한 인터페이스(메서드)만 제공함으로써 객체 내부의 무결성을 유지함.
일반적으로 클래스의 필드는
private
와 같이 접근 제어자 를 사용하여 외부에서 직접 보지 못하도록 숨기고, 대신public
메서드를 통해서만 접근하거나 변경할 수 있게 함. 이렇게 하면 객체 내부의 데이터가 의도치 않게 변경 되거나 잘못된 방식으로 사용 되는 것을 방지할 수 있음.예를 들어, 은행 계좌 객체라면 잔액 필드는
private
으로 숨기고, 입금/출금 메서드만public
으로 제공하여 잔액 변경 로직을 객체가 스스로 관리하도록 함. 이것이 캡슐화를 통한 정보 은닉과 데이터 보호의 한 예임.
2. 상속(Inheritance)
상속 은 기존 클래스를 바탕으로 새로운 클래스를 정의하는 기능임. 상속을 통해 코드 재사용 이 가능하며, 계층적인 클래스 구조를 만들 수 있음.
상속을 받는 기존 클래스를 부모 클래스(또는 슈퍼 클래스, 기반 클래스) , 상속하여 새롭게 만드는 클래스를 자식 클래스(서브 클래스, 파생 클래스) 라고 함. 자식 클래스는 부모 클래스의 속성과 메서드를 돌려받아, 부모 클래스에서 정의된 기능을 그대로 사용할 수 있음.
상속은 "~는 ~이다(is-a)" 관계로 설명되며, 자식 클래스는 부모 클래스의 일종임. 예를 들어,
Bird
클래스가Animal
클래스를 상속받았다면, "새는 동물이다" 라는 관계가 성립함. 자식 클래스는 부모의 트깆ㅇ을 상속받고, 필요에 따라 기능을 확장하거나 변경(메서드 오버라이딩) 할 수 있음.상속의 이점은 공통 코드를 부모 클래스에 작성하고 여러 자식 클래스에서 이를 공유 할 수 있다는 것임. 중복 코드를 줄이고, 시스템을 계층 구조로 논리적으로 조직화 할 수 있음. 다만 너무 깊은 상속 구조는 오히려 복잡성을 증가시킬 수 있으므로 적절한 수준에서 활용함.
3. 다형성(Polymorphism)
다형성 은 말 그대로 "여러 가지 형태를 가짐" 을 뜻함. 객체지향에서 다형성은 같은 타입이지만 다양한 실제 구현을 가진 객체들 을 일관된 방식으로 다룰 수 있는 능력을 말함.
가장 대표적인 다형성의 예는 메소드 오버라이딩 을 통해 나타남. 부모 클래스 타입으로 여러 자식 클래스의 객체를 참조하더라도, 호출된 메서드는 실제 객체의 클래스에서 구현된 버전 이 실행되는 특성이 있음. 이를 동적 바인딩(dynamic binding) 이라고도 하며, 실행 시간에 실제 객체 타입에 맞는 메서드가 선택됨. 예를 들어,
Animal
타입의 변수에Dog
객체와Cat
객체를 넣고 각각..sound()
메서드를 호출하면, 참조 타입은 같아도 실제 객체에 따라 개는 개 짖는 소리, 고양이는 고양이 울음소리가 나오는 식임.다형성은 인터페이스 나 추상 클래스 를 통해서도 구현됨. 여러 클래스가 동일한 인터페이스나 추상 메서드를 구현하면, 그 상위 타입 하나로 묶어서 다룰 수 있음. 클라이언트 코드는 구체적인 클래스 구분 없이 상위 타입만 알고 있어도 동작할 수 있기 때문에, 객체 교체가 용이하고 유연한 코드 구조를 만들 수 있음.
정리하면, 다형성을 활용하면 하나의 추상적인 타입으로 여러 실제 타입의 객체들을 처리할 수 있어 코드의 유연성 과 확장성 이 높아짐.
4. 추상화(Abstraction)
추상화 는 불필요한 세부 사항을 숨기고, 중요한 본질에 집중하여 모델링 하는 것을 말함. 객체지향 프로그래밍에서 추상화는 객체들의 공통적인 속성이나 동작을 뽑아내어 정의하거나 인터페이스화 하는 것을 의미함.
추상화를 통해 복잡한 시스템을 단순화 할 수 있음. 예를 들어, 자동차를 모델링할 때, 우리는 엔진 내부의 연소 과정 같은 세부 사항까지 신경쓰지 않고도, 운전자가 조작하는 요소(핸들, 페달 등) 와 자동차의 주요 기능(가속, 감속, 방향전환) 에 집중하여 클래스를 설계함. 이렇게 중요한 특징만 반영하고 세부 구현은 나중에 처리하거나 감추는 것이 추상화임.
프로그래밍에서 추상화를 실현하는 방법으로 추상 클래스 와 인터페이스 가 있음. 추상 클래스는 공통되는 속성이나 메서드를 기본 구현하거나 추상 메서드 형태로 선언해놓고, 구체적인 내용은 상속받은 클래스에서 구현하도록 함. 인터페ㅣ스도 일종의 추상화 도구로, 클래스들이 반드시 구현해야 할 메서드의 목록(규격)을 정의함.
추상화를 잘 사용하면 코드의 유연성 이 높아짐. 상위 수준의 개념만 알면 하위 구현 상세를 몰라도 상호작용 할 수 있으므로, 모듈 간 결합도가 낮아지고 코드 수정이 용이해짐.
메서드 오버로딩(Method Overloading)
메서드 오버로딩 은 동일한 이름 을 가진 메서드를 한 클래스 안에 여러 개 정의하는 기능임. 단, 각 메서드는 매개변수의 유형이나 개수 가 달라야함. 즉. 메서드 시그니처(signature) (메서드 이름 + 매개변수 목록)가 서로 달라야 같은 이름을 가진 메서드를 여러 개 둘 수 있는데, 이것을 오버로딩이라고 함.
오버로딩의 정의와 필요성
오버로딩을 사용하는 이유는 유사한 기능을 하나의 이름으로 묶어서 표현 함으로써 코드의 가독성과 사용 편의성을 높이기 위함. 예를 들어, 어떤 객체를 출력하는 print()
메서드를 생각해보면, 정수, 문자열, 실수 등 다양한 타입을 출력할 수 있어야 함. 언어 차원에서 메서드 오버로딩을 지원하면 print(int x)
, print(String s)
, print(double d)
와 같이 메서드 이름은 같지만 입력 매개변수가 다른 메서드를 정의해 둘 수 있음. 그러면 사용자는 print()
라는 동일한 이름의 메서드를 호출하되 전달하는 인자에 따라 적절한 메서드가 자동으로 선택 되므로, 일관된 이름으로 다양한 상황을 처리할 수 있음.
요약하면, 오버로딩은 하나의 개념 을 표현하는 메서드 이름 아래에 상황에 따른 여러 동작을 구현할 수 있게 해주며, 개발자가 메서드 이름을 매번 다르게 기억하지 않아도 되는 이점이 있음.
오버로딩의 규칙
오버로딩이 성립하기 위한 구체적인 규칙은 다음과 같음.
매개변수 목록이 달라야함 : 동일 클래스 내에서 메서드 이름이 같다면 매개변수의 개수 혹은 타입이 달라야함 (예를 들어, 하나는
func(int a)
, 다른 하나는func(String s)
또는func(int a, int b)
형태로 차이가 있어야 함.)반환 타입이나 접근 제한자는 관련 없음 : 오버로딩을 판단할 때 오직 매개변수 시그니처만 고려되므로, 반환 타입만 다르거나 접근 제어자만 다른 경우는 오버로딩이 아님. 예를 들어,
int func()
와double func()
는 매개변수가 동일(없음) 하므로 오버로딩이 성립하지 않으며 컴파일 오류임.호출 시점에 구분 : 어떤 메서드를 호출할지 결정하는 것은 컴파일 시점(정적 바인딩) 임. 전달되는 인자의 타입과 개수를 보고 컴파일러가 적합한 시그니처를 가진 오버로딩 메서드를 선택함. (주의: 자바 등에서는 자동형변환 규칙까지 고려하여 선택되는데, 애매한 경우 컴파일 에러가 날 수 있음)
메서드 오버라이딩(Method Overriding)
메서드 오버라이딩 은 상속 관계 에서 등장하는 개념으로, 부모 클래스(상위 클래스)가 가지고 있는 메서드를 자식 클래스(하위 클래스)에서 재정의 하는 것을 말함. 자식 클래스는 부모로부터 물려받은 메서드를 그대로 사용할 수 있지만, 필요한 경우 메서드의 구현을 변경하여 자기 클래스에 맞게 새롭게 정의할 수 있는데, 이것이 오버라이딩 임.
오버라이딩의 정의와 필요성
오버라이딩의 목적은 상속받은 기능을 상황에 맞게 확장하거나 변경 하는 데 있음. 부모 클래스에 일반적으로 동작하도록 정의된 메서드가 있을 때, 이를 상속받은 자식 클래스에서 그대로 쓰면 맞지 않는 경우가 종종 있음. 이때 자식 클래스에서 해당 메서드를 재정의 함으로써 동일한 메서드 호출에 대해 클래스별로 다른 동작 을 수행할 수 있음.
예를 들어, 게임 프로그램에서 Character
라는 부모 클래스가 attack()
이라는 메서드를 가지고 있고, 이를 상속받은 Archer
와 Warrior
같은 자식 클래스가 있다고 하면, 기본 Character
의 attack()
은 "기본 공격" 정도로 구현될 수 있지만, Archer
클래스는 활을 쏘는 방식으로, Warrior
클래스는 검으로 베는 방식으로 각각 attack()
메서드를 오버라이딩 하여 캐릭터 종류에 따른 공격 방식을 구현할 수 있음. 이처럼 오버라이딩을 통해 다형성 이 구현되고, 프로그램은 상위 타입(Character
) 만 다루면서도 실제로는 다양한 하위 타입별 동작을 얻을 수 있음.
오버로딩의 규칙(부모-자식 메서드 관계)
오버라이딩이 성립하려면 다음과 같은 조건이 만족해야함.
메서드 이름과 시그니처 동일 : 자식 클래스의 메서드는 부모 클래스의 메서드와 이름, 매개변수 타입 및 개수 가 완전히 같아야함. (리턴 타입도 동일해야 하지만, 자바의 경우 공변 반환(covariant return) 을 활용하여 리턴 타입을 부모 메서드의 리턴 타입을 상속 받은 타입으로 변경 가능하게 한 경우도 있음. 일반적인 경우 동일한 타입으로 둠)
접근 범위는 같거나 더 넓게 : 자식 클래스에서 재정의된 메서드는 부모의 메서드보다 접근 제한 범위를 더 좁게 설정할 수 없음. 예를 들어 부모 메서드가
public
이면 자식에서도public
이어야 하고, 부모가protected
이면 자식은protected
나public
으로만 재정의가 가능함.(더 좁은 범위인default
나private
로 바꿀 수 없음.)예외 제한 : 언어에 따라 규칙이 있는데, 자바의 경우 부모 메서드가 던지는 검사 예외(checked exception) 가 있다면, 자식 메서드는 부모보다 상위 타입의 예외를 선언할 수 없음.
특별한 메서드는 오버라이딩 불가 :
static
메서드는 클래스별로 존재하므로 오버라이딩 대상이 아니며(동일 이름으로 정의하면 숨김(hiding) ),private
메서드는 애초에 자식이 접근할 수 없으므로 오버라이딩 할 수 없음. 또한final
로 선언된 메서드는 오버라이딩을 금지함.
Last updated
Was this helpful?