객체(Object)란?
- 실재(實在) 하는 대상을 변수(상태, 속성)와 함수(행동)으로 추상화 시킨 개념을 말한다.
- 눈에 보이지 않는 논리, 사상, 철학, 개념, 공식 등 무형의 대상도 포함한다.
- 우리가 보고 느끼고 인지하는 모든 대상 = 객체
클래스(Class)와 인스턴스(instance)
1. 클래스 (Class)
- 객체를 정의하는 설계도로 객체가 가질 속성(변수), 행동(메서드)를 정의한 틀을 말한다.
- 객체를 만들기 위한 메타정보
2. 인스턴스 (Instance)
- 특정 클래스를 바탕으로 실체화되어 메모리에 할당된 것을 말한다.
- 객체를 클래스와의 관계에서 설명할 때 사용한다.
- ex) A는 B클래스의 인스턴스
* 객체와 인스턴스의 관계 *
- 객체는 일반적인 용어로, 클래스에 상관없이 프로그램에서 사용될 수 있는 모든 데이터나 기능을 가진 실체를 말한다.
- 인스턴스는 특정 클래스와 관련해서, 'A는 B의 인스턴스이다.'라는 맥락에서 설명할 때 주로 사용한다. (어떤 클래스를 따르는지 강조)
Car myCar = new Car();
Bike myBike = new Bike();
myCar와 myBike 모두 객체이고, myCar는 Car 클래스의 인스턴스, myBike는 Bike 클래스의 인스턴스!
객체 지향 프로그래밍이란?
실제 세계를 모방해 기본적인 단위인 객체를 만들고, 그들 간의 유기적인 상호작용을 규정해 프로그램을 발전시키는 프로그래밍 방법론이다.
객체 지향 프로그래밍에는 4가지 특징이 있다.
다음과 같은 4가지 특성을 살려서 개발을 하면 프로그램의 효율성과 생산성을 극대화할 수 있다.
1. 추상화
- 공통의 속성과 기능을 묶어 내는 과정
다음과 같이 회사에는 다양한 산업군이 속할 수 있고, 이를 공통으로 묶어 Company클래스로 추상화할 수 있다.
객체 지향 프로그래밍에서는 유연하고, 변경에 열려 있는 프로그램 설계를 위해 역할과 구현을 분리하는데, '역할'에 해당하는 부분을 추상 클래스와 인터페이스를 통해 추상화 할 수 있다.
a. 추상 클래스 (abstract class)
- 공통된 기능과 상태를 공유하고 싶을 때
- 기본 구현을 제공하면서 일부 메서드를 서브 클래스에서 구현하도록 강제하고 싶을 때
- 상태를 관리해야 할 때 (인터페이스는 상태를 가질 수 X)
b. 인터페이스 (interface)
- 다양한 클래스에서 공통적으로 구현해야 하는 행동을 정의할 때
- 구현 보다는 계약(Contract)을 정의하고 싶을 때
- 다중 상속이 필요할 때
* 인터페이스 *
- 추상 메서드나 상수로 객체가 수행해야 하는 핵심적인 역할만 규정하고, 실제 구현은 해당 인터페이스를 구현하는 각 객체에서 하도록 설계하는 것
public interface Company {
public abstract void makeMoney();
void goToWork();
void getOffWork();
}
- 역할과 구현을 분리해 유연하고 변경이 용이한 프로그램을 설계하는 데 핵심적인 역할을 수행한다.
2. 상속(Inheritance)
- 기존 클래스를 재활용하여 새로운 클래스를 작성하는 문법 요소
- 클래스 간 공유되는 속성, 기능을 상위 기능으로 추상화해 하위 클래스가 상위 클래스의 모든 속성, 기능을 사용할 수 있도록 한다.
- 공유 속성, 기능을 반복적으로 정의할 필요가 없기 때문에 반복적인 코드를 최소화할 수 있고, 간편한 관리가 가능하다.
- 메서드 오버라이딩을 통해 재정의도 가능하다.
* 상속과 인터페이스 *
- 공통점 : 상위-하위 클래스 관계를 전제하여 공통적인 기능을 공유
- 상속 : 상위 클래스의 속성, 기능을 그대로 받아 사용 or 오버라이딩을 통한 선택적 재정의 가능
- 인터페이스 : 인터페이스에 정의된 추상 메서드 내용이 하위 클래스에서 구현
결론 : 상속이 인터페이스를 통한 구현보다 추상화 정도 低
3. 다형성(Polymorphism)
- 다형성(多形性)이란, 어떤 객체의 속성이나 기능이 상황에 따라 여러 가지 형태를 가질 수 있는 성질
- 어떤 객체의 속성, 기능이 맥락에 따라 다른 역할을 수행할 수 있는 특징
- 다형성은 크게 정적 다형성과 동적 다형성으로 나눌 수 있고, 구체적인 구현 방법에 따라 몇 가지로 구분된다.
a. 정적 다형성
- 컴파일 시점에 결정되는 다형성으로, 컴파일러가 어느 메서드를 호출할지 이미 알고 있는 경우
① 메서드 오버로딩 (Method Overloading)
- 동일한 메서드 이름을 사용하지만, 매개변수의 종류, 개수, 순서를 다르게해 다른 방식으로 동작하게 한다.
class Calculator {
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
}
Calculator calculator = new Calculator();
System.out.println(calculator.add(5, 10)); // 15
System.out.println(calculator.add(5.5, 10.2)); // 15.7
b. 동적 다형성
- 런타임 시점에 결정되는 다형성으로, 객체의 실제 타입에 따라 호출할 메서드가 결정된다.
① 메서드 오버라이딩 (Method Overriding)
- 상위 클래스의 메서드를 하위 클래스에서 재정의하는 것이다.
- 상위 클래스 타입의 참조 변수로 하위 클래스의 객체를 참조할 때, 런타임 시점에 하위 클래스에 재정의된 메서드가 호출된다.
class Animal {
void sound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("Dog barks");
}
}
Animal myDog = new Dog();
myDog.sound(); // 출력: "Dog barks"
② 인터페이스 기반 다형성
- 인터페이스 타입의 참조 변수로 여러 구현 클래스의 객체를 다룰 수 있는 다형성이다.
- 인터페이스를 구현한 클래스가 여러 개 있을 때, 인터페이스 타입으로 해당 객체를 처리할 수 있음.
interface Company {
void makeMoney();
}
class IT implements Company {
public void makeMoney() {
System.out.println("IT 회사에서 돈을 법니다.");
}
}
class Manufacturing implements Company {
public void makeMoney() {
System.out.println("제조 회사에서 돈을 법니다.");
}
}
Company it = new IT();
Company manufacturing = new Manufacturing();
it.makeMoney(); // "IT 회사에서 돈을 법니다."
manufacturing.makeMoney(); // "제조 회사에서 돈을 법니다."
- 이런식으로 참조하면 Company 타입 객체들을 배열로 묶어서 작업할 수도 있다.
Company companies[] = new Company[2];
companies[0] = new IT();
companies[1] = new Manufacturing();
for(Company company : companies) {
company.makeMoney();
}
또 다른 형태를 살펴보자. 다음과 같이 직장인 클래스가 있다고 가정해보자.
Employe는 IT 회사랑 제조 회사에 매우 의존적이다. 다르게 말해서 객체 간의 결합도가 매우 높다.
이러한 상황에서는 각 클래스의 내부 변경이나 객체 교체 발생 시 하나하나 다 바꿔줘야 하는 문제가 발생한다.
하지만 다음과 같이 매개 변수로 인터페이스 타입의 참조 변수를 전달하면, 다양한 객체를 주입받을 수 있어 더이상 특정 클래스에 의존하지 않아도 된다.
특정 구현체에 의존하는 것이 아닌, 인터페이스에 의존해 유연한 객체 주입과 결합도 감소를 가능하게 할 수 있다.
C. 캐스팅에 의한 다형성
- 캐스팅을 통해 상위 클래스와 하위 클래스 간의 형 변환을 사용해 다형성을 구현할 수도 있다.
① 업캐스팅 (Upcasting)
- 하위 클래스 객체를 상위 클래스 타입으로 변환하는 것
Animal myAnimal = new Dog(); // Dog 객체를 Animal 타입으로 참조
② 다운캐스팅 (Downcasting)
- 상위 클래스 타입을 하위 클래스 타입으로 변환하는 것
- 하위 클래스의 구체적인 메서드나 속성을 사용하고자 할 때 필요할 수 있다.
Animal myAnimal = new Dog();
Dog myDog = (Dog) myAnimal; // 명시적으로 다운캐스팅
d. 추상 클래스에 의한 다형성
- 추상 클래스는 추상 메서드를 포함한 클래스를 말하고, 이를 상속받은 하위 클래스에서 추상 메서드를 구현한다.
- 추상 클래스 타입의 참조 변수를 통해 다양한 하위 클래스 참조 가능
abstract class Shape {
abstract void draw();
}
class Circle extends Shape {
@Override
void draw() {
System.out.println("Drawing a circle");
}
}
Shape shape = new Circle();
shape.draw(); // "Drawing a circle"
4. 캡슐화(Encapsulation)
- 클래스 안에 서로 연관있는 속성과 기능을 하나의 캡슐로 만들어 데이터를 외부로부터 보호하는 것을 말한다.
→ 객체 고유의 독립성과 책임 영역을 안전하게 지킬 수 있음!
캡슐화를 통해 4가지 이점을 가져갈 수 있다.
- 데이터 보호 : 내부 데이터를 외부로부터 보호해 잘못된 접근이나 수정으로 인한 오류를 방지한다.
- 코드 유지 보수성 향상 : 외부에서 객체 내부 구현에 의존하지 않도록 하고, 내부 구현을 변경해도 외부에 영향을 최소화한다.
- 데이터 무결성 : 외부 객체에서는 객체가 제공하는 메서드를 통해서만 데이터를 변경해 객체 상태를 안전하게 유지 ex) getter, setter
- 복잡성 숨기기 : 복잡한 내부 로직을 감추고, 필요한 기능만 외부에 공개해 코드를 단순화 함
캡슐화를 구현하는 방법은 접근 제어자(access modifiers)를 사용하면 된다.
캡슐화를 구현한 예시를 살펴보도록 하겠다.
다음과 같은 IT 클래스와 Employee 클래스가 있다고 가정하자.
여기서 IT 클래스의 working() 메서드와 leaveWork() 메서드가 변경되면 Employee 클래스의 life() 메서드도 수정을 해야하는 상황이 발생할 수 있다.
Employee 클래스가 IT 클래스의 세부 로직을 속속히 알고 있기 때문에 변경에 취약하다는 문제를 가지고 있다.
다음과 같이 working() 메서드와 leaveWork() 메서드의 접근 제어자를 private으로 바꿔주고, 이를 호출하는 dayAtWork() public 메서드를 생성한다.
그리고 Employee 클래스의 life() 메서드에서 dayAtWork()을 호출하게 되면 IT 클래스의 내부 로직이 바뀌더라도 Employee 클래스는 더이상 영향을 받지 않게 된다.
IT 클래스와 관련된 기능은 온전히 IT 클래스에서만 관리되도록 하고, working, leaveWork에서 불필요한 내부 동작의 노출은 최소화하여 클래스 간 결합도를 낮출 수 있다.
정리
추상화
- 공통의 속성과 기능을 묶어내는 과정을 말한다.
- 추상 클래스와 인터페이스를 통해 추상화할 수 있다.
상속
- 상위 클래스의 속성과 기능을 상속받아 사용할 수 있고, 특정 메서드를 오버라이딩하여 재정의 할 수 있도록 한다.
다형성
- 어떤 객체의 속성, 기능이 맥락(상황)에 따라 다른 역할을 수행할 수 있는 것을 말한다.
- 객체 간 관계를 보다 유연하고, 확장에 용이하도록 설계하는 핵심 역할이다.
- 다형성을 제대로 활용하기 위해서는 추상화, 상속의 개념이 필수
캡슐화
- 속성과 메서드를 하나의 단위로 묶고, 데이터를 외부로부터 보호하는 것.
- 이를 통해 데이터의 무결성을 보장하고, 객체의 상태를 안정적으로 관리한다.
- 객체의 자율성을 높이고, 객체 간 결합도를 낮추는 역할도 수행
'프로그래밍 > ect' 카테고리의 다른 글
SOLID, 객체 지향 설계 5원칙 : 의존 역전 원칙 (Dependency Inversion Principle) (2) | 2024.06.14 |
---|---|
SOLID, 객체 지향 설계 5원칙 : 인터페이스 분리 원칙 (Interface Segregation Principle) (0) | 2024.06.13 |
SOLID, 객체 지향 설계 5원칙 : 리스코프 치환 원칙(Liskov Substitution Principle) (0) | 2024.06.09 |
SOLID, 객체 지향 설계 5원칙 : 개방 폐쇄 원칙(Open Closed Principle) (0) | 2024.06.08 |
SOLID, 객체 지향 설계 5원칙 : 단일 책임 원칙(Single Responsibility Principle) (0) | 2024.06.07 |