객체지향은 4가지 특징을 가지고 있다. 이 4가지 특징에 대해서는 자주 들어 알고 있지만 올바르게 개념을 가지고 있는지는 확인할 필요가 있어 정리를 해보았다.
객체지향의 4가지 특징
- 캡슐화
- 상속
- 추상화
- 다형성
캡슐화
캡슐화란 변수나 메서드들을 캡슐로 감싸서 안 보이게 하는 정보 은닉 개념 중 하나이다. 객체의 개념에서 볼 때는 객체의 속성(Field)과 행위(Method)를 하나로 묶고, 실제 구현 내용의 일부를 외부에 감추어 은닉하여 외부에서 객체 내부에 어떤 속성이 있는지 완벽하게 알지 못해야 하는 것을 의미한다.
캡슐화는 객체의 속성과 행위를 하나로 묶음으로써 높은 응집도를 가지며, 객체의 내부 상태를 직접적으로 접근하지 못하게 막음으로써 결합도를 낮아지게 한다.
public class Person{
private String name;
private int age;
public Person(String name, int age){
this.name = name;
this.age = age;
}
public String introduce() {
return String.format("제 이름은 %s 이고 나이는 %d 입니다.", name, age);
}
}
class PersonTest {
@Test
void person() {
Person person = new Person("JH", 30);
String introduce = person.introduce();
assertThat(introduce).isEqualTo("제 이름은 JH 이고 나이는 30 입니다.");
}
}
위 코드로 높은 응집도와 낮은 결합도를 간단히 설명해 보면 Person클래스는 이름(name)과 나이(age)라는 두 가지 속성을 가지고 있다. 또한 자신을 설명하는 introduce() 메서드를 가지고 있다. 속성과 행위를 하나로 묶여있으므로 응집도가 높다고 볼 수 있다.
결합도 측면에서는 Person 클래스는 다른 클래스와 거의 어떤 결합도 가지고 있지 않다. 즉, 외부 클래스와의 의존성이 낮다. 쉽게 말하면 내부의 속성이나 행위가 변경되어도 외부의 영향이 적고 내부 안에서만 변경이 이루어진다는 것을 의미한다.
이런 캡슐화를 잘 지켜 코드를 작성하게 되면 코드의 유지보수와 확장성 향상에 도움이 된다.
상속
상속은 상위 클래스의 기능을 하위 클래스가 사용할 수 있는 개념을 의미한다. 상속을 함으로써 새로운 클래스를 정의할 때 이미 구현된 클래스를 속성과 기능을 받아 확장 또는 재사용할 수 있다.
일반적으로 상위 클래스는 하위 클래스보다 더 일반적인 개념과 기능을 가지며 하위 클래스는 상위 클래스보다 더 구체적인 개념과 기능을 가진다.
이러한 행위를 통해 하위 클래스는 상위 클래스의 속성과 기능을 확장한다.
기본 구현
class 부모클래스 {
// 속성과 메서드
}
class 자식클래스 extends 부모클래스 {
// 추가적인 속성과 메서드
}
class Animal {
void eat() {
System.out.println("동물은 음식을 먹는다.");
}
}
class Dog extends Animal {
void bark() {
System.out.println("개는 짖는다.");
}
}
public class TestInheritance {
public static void main(String[] args) {
Dog myDog = new Dog();
myDog.eat(); // Animal 클래스에서 상속받은 메서드
myDog.bark(); // Dog 클래스에서 정의된 메서드
}
}
Java에서의 상속의 주요 특징
1. 단일 상속 : Java는 다중 상속을 지원하지 않는다. 즉, 한 클래스에서는 하나의 클래스만 상속받을 수 있다.
2. super 키워드 : 자식 클래스에서 부모 클래스의 속성이나 메서드에 접근할 때는 super 키워드를 사용할 수 있다.
3. 오버라이딩 : 자식 클래스에서 부모 클래스의 메서드를 재정의할 수 있다. 이 때 @Override어노테이션을 사용하며 오버라이드임을 명시적으로 표시한다.
4. 생성자 상속 : 자식 클래스의 생성자는 부모클래스의 기본 생성자를 자동으로 호출한다. 만약 부모 클래스가 기본 생성자가 없다면, 자식 클래스의 생성자에서 super()를 사용하여 특정 생성자를 명시적으로 호출해야 한다.
상속에 대한 잘못된 생각
상속을 단순히 재사용의 개념으로 이해하면 안된다. 물론 상속의 특징 중 재사용이 있지만 그것을 중심으로 생각하여 서로 이질적인 관계의 클래스 간에는 사용하지 않는 것이 좋다. 이런 관계가 없는 클래스끼리 마구잡이로 상속을 하게 되면 결합도가 높아져 상위 클래스를 수정해야 할 때 하위 클래스에 미치는 영향이 매우 크게 나타난다.
추상화
추상화란 현실관점으로 보면 "사물이나 표상을 어떤 성질, 공통성, 본질에 착안하여 그것을 추출하여 파악하는 것"이라 정의한다. 여기서 핵심이 되는 개념은 "공통성과 본질을 모아 추출"한다는 것이다.
객체 지향의 관점에서 본다면 추상화는 "객체에서 공통된 속성과 행위를 추출"하는 것이다.
자바에서 추상화를 구현할 수 있는 문법에 요소로는 추상 클래스(abstract class)와 인터페이스(interface)가 있다.
기본 구현
추상클래스를 이용한 방법
추상클래스는 인터페이스와 다르게 구현을 가진 일반 메서드와 'abstract' 키워드를 붙인 추상 메서드 선언하여 사용한다.
추상클래스를 상속받으면 추상메서드는 오버라이딩하여 재정의를 하여야 한다.
public abstract class Mammal {
private String name;
public Mammal(String name) {
this.name = name;
}
public abstract void makeSound(); // 추상 메서드
public void breathe() {
System.out.println(name + " breathes.");
}
}
public class Dog extends Mammal {
public Dog(String name) {
super(name);
}
@Override
public void makeSound() {
System.out.println("Woof!");
}
}
인터페이스를 이용한 방법
'interface' 키워드를 사용하여 선언하는 인터페이스는 상수(static final)와 추상 메서드(abstract method)의 집합이다.
인터페이스는 추상 클래스보다 추상화 정도가 높으며 추상 클래스와는 다르게 구현부가 있는 일반 메서드, 일반 멤버 변수를 가질 수 없다는 특징이 있다.
public interface Animal {
void makeSound();
}
public class Dog implements Animal {
@Override
public void makeSound() {
System.out.println("Woof!");
}
}
public class Cat implements Animal {
@Override
public void makeSound() {
System.out.println("Meow!");
}
}
다형성
다형성은 어떤 객체의 속성이나 기능이 상황에 따라 여러 가지 형태를 가질 수 있는 성질을 의미한다. 대표적인 예로 메서드 오버라이딩과 메서드 오버로딩이 있다.
오버라이딩
오버라이딩은 부모클래스의 정의된 메서드를 지식 클래스에서 재정의하는 것을 의미하며 메서드의 시그니처(이름, 매개변수, 반환타입)는 같아야 하면, 구현 내용만 다르게 할 수 있다.
class Animal {
void speak() {
System.out.println("Animal speaks");
}
}
class Dog extends Animal {
@Override
void speak() {
System.out.println("Dog barks");
}
}
오버로딩
같은 이름의 메서를 여러 개 가지면서 매개변수의 유형과 개수가 다르도록 하는 기능 하다.
메서드의 이름은 같으나, 매개변수의 타입이나 개수는 달라야 한다.
class MathUtil {
int sum(int a, int b) {
return a + b;
}
double sum(double a, double b) {
return a + b;
}
int sum(int a, int b, int c) {
return a + b + c;
}
}
정리를 마치며
내가 생각하는 객체지향은 객체가 각자 책임을 가지고 협력을 하며 동작하는 것을 의미한다. 결국 올바른 객체지향적인 개발은 한다는 것은 객체로부터 시작하는 것임으로 객체의 책임을 불어넣는 캡슐화 단계가 중요한 것 같다. 이 단계를 잘 이해하면 다른 특징들을 적용하였을 때 더 객체지향 다운 코드 형태가 될 수 있을 거 같다.
추가적으로 객체지향의 4가지 특징을 간단하게 개념정리식으로 정리했지만 이 안에는 더 많은 내용을 가지고 있다. 이러한 부분에 대해 추가정리를 하여 기록으로 남기는 작업을 하도록 하자.
'Language > Java' 카테고리의 다른 글
[스진초5기/Java] Overriding과 Overloading (0) | 2023.10.27 |
---|---|
[Java] String[]을 List , List를 String[]로 변환하는 방법 (0) | 2023.10.24 |
[스진초5기/Java] 오버라이딩을 구현할 때 꼭 @Override가 필요할까? [+어노테이션의 역할] (0) | 2023.10.23 |
[Java] 인터프리터와 컴파일러 그리고 Java (1) | 2023.10.23 |
[스친초5기/Java] 인터페이스와 객체지향 (0) | 2023.10.20 |