Products
인터페이스

[ONEJET] 자바 객체지향 프로그래밍 이해를 위한 가이드 및 예제

게시일
2025/05/10 07:36
작성자
태그
자습서

1. 자바 객체지향 프로그래밍 (OOP) 소개

1.1 OOP란 무엇인가?

객체지향 프로그래밍(OOP)은 소프트웨어 설계를 "객체" 중심으로 구성하는 프로그래밍 패러다임입니다. 객체는 데이터(속성 또는 상태)와 해당 데이터를 조작하는 메서드(함수 또는 행위)를 함께 묶은 단위입니다. 이는 일련의 명령어에 초점을 맞춘 절차적 프로그래밍과는 대조적인 접근 방식입니다. OOP는 시스템을 더 작고 관리하기 쉬운 객체로 분해하여 복잡한 소프트웨어 시스템 개발을 용이하게 합니다. 또한, 프로그래밍에서 현실 세계의 개체와 그 상호 작용을 모델링하는 것을 목표로 합니다.   OOP의 기본적인 변화는 객체 내에서 데이터와 행위를 밀접하게 결합하는 데 있습니다. 이는 현실 세계의 개체가 속성과 행동을 모두 갖는 방식과 유사합니다. 이러한 통합은 시스템을 더 직관적이고 체계적으로 구성하는 데 도움이 됩니다. 전통적인 프로그래밍 방식에서는 데이터와 함수가 분리되어 있지만, OOP는 이들을 객체라는 응집력 있는 단위로 결합합니다. 이러한 캡슐화는 시스템의 이해도와 조직력을 향상시킵니다.

1.2 OOP 사용의 이점

OOP는 여러 가지 중요한 이점을 제공합니다. 상속을 통해 코드 재사용성이 향상되어 기존 클래스를 기반으로 새로운 클래스를 구축할 수 있습니다. 유지보수성 또한 향상되는데, 변경 사항이 여러 함수에 걸쳐 분산되는 대신 클래스 내에 국한되는 경우가 많기 때문입니다. OOP는 코드를 더 체계적이고 관리하기 쉽게 만들어 디버깅과 이해를 용이하게 합니다. 또한, 상속과 추상화를 통해 코드의 유연성과 확장성을 높일 수 있습니다. 캡슐화는 데이터에 대한 무단 접근을 방지하여 데이터 보안을 강화합니다. OOP는 코드의 여러 컴포넌트를 분리하여 모듈화된 프로그래밍을 촉진하며 , 명확하게 정의된 인터페이스를 가진 재사용 가능한 코드를 생성합니다.   OOP는 상속과 구성을 통해 코드 중복을 줄이고 유지보수성을 향상시키는 "DRY(Don't Repeat Yourself)" 원칙을 장려합니다. 공통 기능을 기본 클래스나 인터페이스에 정의함으로써 동일한 코드를 여러 곳에서 반복 작성할 필요가 없어지므로, 코드베이스가 더 효율적이고 관리하기 쉬워집니다.  

1.3 OOP의 주요 원칙

OOP의 핵심에는 네 가지 주요 원칙이 있습니다. 캡슐화(Encapsulation) , 상속(Inheritance) , 다형성(Polymorphism) , 그리고 추상화(Abstraction) 입니다. 이러한 원칙들은 객체지향 설계의 기초를 형성하며, 코드를 구성하고 재사용하며 확장하는 방법을 정의합니다.  

1.4 자바가 객체지향 언어인 이유

자바는 이러한 네 가지 핵심 OOP 원칙을 모두 지원하므로 진정한 객체지향 언어라고 할 수 있습니다. 자바의 설계 철학은 클래스와 객체라는 개념에 깊이 뿌리내리고 있으며, 거의 모든 것이 객체로 취급됩니다(기본 데이터 유형은 객체로 캡슐화될 수 있음). 자바 언어는 이러한 OOP 원칙을 쉽게 구현할 수 있도록 내장된 메커니즘과 구문을 제공합니다. 일부 다중 패러다임 언어와 달리, 자바의 기본 구조와 구문은 객체지향 원칙을 지원하고 강제하도록 설계되어 개발자를 객체지향적 사고 방식으로 안내합니다.  

2. 캡슐화: 데이터와 메서드 보호

2.1 정의 및 목적

자바의 핵심 OOP 원칙인 캡슐화는 데이터(변수)와 해당 데이터를 조작하는 메서드(함수)를 클래스라는 단일 단위로 묶는 것을 포함합니다. 주요 목적은 private 필드를 사용하고 public getter 및 setter 메서드를 제공하여 객체의 데이터를 무단 접근 및 수정으로부터 보호하는 것입니다. 캡슐화를 통해 데이터 숨김이 가능하며, 데이터는 제어된 방식으로만 접근하거나 수정할 수 있습니다. 객체의 내부 컴포넌트에 대한 직접적인 접근을 제한함으로써 캡슐화는 데이터의 우발적인 수정을 방지하고 데이터 무결성을 유지하는 데 도움이 됩니다.   캡슐화는 마치 여러 가지 약(데이터)을 담고 있고 정해진 방식(메서드)으로만 약을 꺼낼 수 있는 캡슐과 같습니다. 내부 작용 방식은 숨겨져 있습니다. 이 비유는 캡슐화가 데이터를 묶고 보호하면서 제어된 상호 작용을 위한 인터페이스를 제공하는 방식을 시각화하는 데 도움이 됩니다.  

2.2 자바에서 캡슐화 달성

자바에서 캡슐화를 달성하는 핵심 메커니즘은 클래스 변수(데이터 멤버)를 private으로 선언하는 것입니다. 이러한 private 변수에 대한 제어된 접근을 허용하기 위해 public getter 메서드(접근자 메서드)가 제공되어 private 필드의 값을 검색합니다. 마찬가지로, public setter 메서드(변경자 메서드)는 private 변수의 값을 제어된 방식으로 수정하는 데 사용됩니다.   데이터 멤버에 private 접근 제어자를 사용하고 public getter와 setter를 사용하는 것은 자바에서 캡슐화를 시행하기 위한 표준 방식입니다. 이러한 규칙은 외부 코드가 객체의 내부 상태를 직접 조작할 수 없도록 보장하여 객체의 무결성을 유지합니다. Getter와 setter는 인스턴스 변수에 대한 접근 지점 역할을 하며, 읽기 및 수정 방법을 제어합니다. public 메서드를 사용하여 private 데이터와 상호 작용함으로써 클래스는 규칙과 유효성 검사 논리를 적용할 수 있습니다.  

2.3 코드 예제

캡슐화의 기본 예로는 nameage와 같은 private 필드와 이러한 필드에 접근하고 수정하기 위한 public getName(), setName(), getAge(), setAge() 메서드를 가진 Student 클래스가 있습니다.  
public class Student { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { if (age > 0) { this.age = age; } else { System.out.println("나이는 양수여야 합니다."); } } public static void main(String args) { Student student = new Student(); student.setName("홍길동"); student.setAge(25); System.out.println("이름: " + student.getName() + ", 나이: " + student.getAge()); student.setAge(-5); // 유효성 검사 메시지 출력 System.out.println("나이: " + student.getAge()); // 변경되지 않은 나이 출력 } }
Java
복사
또 다른 일반적인 예는 private balance 필드와 잔액을 조회하고 입금 및 출금하는 public getBalance(), deposit(), withdraw() 메서드를 가진 BankAccount 클래스입니다. deposit()withdraw() 메서드는 유효성 검사 논리를 포함할 수 있습니다. Setter 메서드는 나이가 양수이고 합리적인 범위 내에 있는지 확인하는 것과 같이 private 변수에 유효한 값만 할당되도록 유효성 검사를 구현할 수 있습니다. 캡슐화는 특정 속성에 대해 getter 메서드만 제공하고 setter 메서드를 제공하지 않음으로써 읽기 전용 클래스를 만드는 데에도 사용할 수 있습니다. 반대로, 특정 속성에 대해 setter 메서드만 제공하고 getter 메서드를 제공하지 않음으로써 쓰기 전용 클래스를 만들 수 있습니다.  

2.4 캡슐화의 이점

인스턴스 변수를 private으로 선언하면 클래스 외부로부터의 직접적인 접근이 제한되어 우발적인 수정이 방지되고 데이터 무결성이 보장되므로 데이터 숨김이 가능합니다. 클래스의 public 인터페이스(getter 및 setter)가 동일하게 유지되는 한 클래스의 내부 구현을 변경해도 외부 코드는 영향을 받지 않으므로 유연성이 향상됩니다. 필드를 private으로 유지하고 접근을 제어하는 public 메서드를 제공하면 코드를 더 쉽게 이해하고 수정할 수 있으므로 유지보수성이 향상됩니다. Setter 메서드를 통해 유효성이 검사된 안전한 값만 할당할 수 있으므로 데이터 무결성이 유지됩니다. 민감한 데이터는 직접 접근할 수 없으므로 보호되어 보안이 강화됩니다. 캡슐화는 코드 컴포넌트를 클래스 내에서 분리하여 모듈화된 프로그래밍을 용이하게 합니다. 잘 캡슐화된 클래스는 명확하게 정의된 인터페이스를 가진 자체 포함 단위이므로 프로그램의 다른 부분이나 다른 프로젝트에서 더 쉽게 재사용할 수 있습니다.   캡슐화를 통해 클래스 필드를 읽기 전용(getter만 제공) 또는 쓰기 전용(setter만 제공)으로 만들 수 있어 데이터 접근에 대한 세밀한 제어가 가능합니다. 이 기능은 객체 생성 후 특정 속성을 수정해서는 안 되거나 변경하지 않고 보기만 해야 하는 시나리오에서 유용합니다.  

3. 상속: 코드 재사용 및 계층 구조 구축

3.1 정의 및 목적

자바에서 상속은 클래스(서브클래스 또는 자식 클래스)가 다른 클래스(슈퍼클래스 또는 부모 클래스)의 특징(필드 및 메서드)을 상속할 수 있게 하는 기본적인 OOP 개념입니다. 상속의 주된 목적은 서브클래스가 슈퍼클래스의 메서드와 변수를 다시 작성하지 않고도 사용할 수 있도록 하여 코드 재사용성을 높이는 것입니다. 또한, 상속은 클래스 계층 구조를 생성하여 코드를 보다 체계적이고 관리하기 쉬운 방식으로 구성하는 데 도움이 됩니다. 슈퍼클래스와 서브클래스 간에 "is-a" 관계를 설정하여 서브클래스가 슈퍼클래스의 특수한 유형임을 나타냅니다.   상속을 통해 원래 코드를 수정하지 않고 클래스의 기능을 확장할 수 있으며, 이는 소프트웨어 설계의 개방/폐쇄 원칙을 준수합니다. 기본 클래스를 상속함으로써 새 클래스는 부모 클래스의 일반적인 동작을 유지하면서 특정 기능이나 기존 기능을 수정하거나 확장할 수 있습니다.  

3.2 슈퍼클래스(부모) 및 서브클래스(자식)

슈퍼클래스(부모 또는 기본 클래스라고도 함)는 다른 클래스가 상속하는 속성(필드 및 메서드)을 가진 클래스입니다. 서브클래스(자식, 파생 또는 확장 클래스라고도 함)는 슈퍼클래스의 속성과 메서드를 상속하는 클래스입니다. 서브클래스는 자체의 추가 속성과 메서드를 가질 수도 있습니다.  

3.3 extends 키워드

자바에서 서브클래스를 선언할 때 extends 키워드를 사용하여 상속할 슈퍼클래스를 지정합니다.  

3.4 메서드 오버라이딩 및 @Override 어노테이션

메서드 오버라이딩을 통해 서브클래스는 슈퍼클래스에 이미 정의된 메서드에 대한 자체의 특정 구현을 제공할 수 있습니다. 서브클래스의 오버라이드된 메서드는 슈퍼클래스의 메서드와 동일한 이름, 매개변수 및 반환 유형을 가져야 합니다. @Override 어노테이션은 서브클래스의 메서드가 슈퍼클래스의 메서드를 오버라이드하려고 함을 명시적으로 나타내는 데 사용됩니다. 이는 컴파일러가 메서드 시그니처가 슈퍼클래스 메서드와 일치하지 않는 경우 오류를 포착하는 데 도움이 됩니다.  

3.5 super 키워드

super 키워드는 서브클래스 내에서 슈퍼클래스의 생성자를 호출하는 데 사용됩니다. 이는 일반적으로 서브클래스 생성자의 첫 번째 명령문이며, 슈퍼클래스에서 상속된 멤버가 적절하게 초기화되도록 합니다. super는 서브클래스 내에서 슈퍼클래스의 메서드와 필드에 접근하는 데에도 사용할 수 있으며, 특히 서브클래스가 메서드를 오버라이드했거나 슈퍼클래스와 동일한 이름을 가진 필드를 가진 경우에 유용합니다.  

3.6 상속 유형

단일 상속: 서브클래스가 하나의 슈퍼클래스만 상속합니다. 자바는 클래스에 대해 이를 지원합니다.   • 다단계 상속: 클래스가 다른 클래스를 상속하고, 그 클래스는 또 다른 클래스를 상속하여 상속 체인을 형성합니다.   • 계층적 상속: 여러 서브클래스가 단일 슈퍼클래스에서 직접 상속합니다.   • 다중 상속: 자바는 클래스에 대해 직접적인 다중 상속(하나의 클래스가 둘 이상의 직접적인 슈퍼클래스를 상속하는 것)을 지원하지 않습니다. 이는 "다이아몬드 문제"를 피하기 위함입니다. 그러나 인터페이스를 통해 달성됩니다.   • 혼합 상속: 둘 이상의 상속 유형(예: 계층적 및 다중 상속)의 조합입니다.  

3.7 코드 예제

일반적인 예로는 brand와 같은 속성과 startEngine()과 같은 메서드를 가진 Vehicle 슈퍼클래스와 Vehicle을 상속하고 자체의 특정 속성과 동작을 가진 CarBike와 같은 서브클래스가 있습니다.  
// 슈퍼클래스 class Vehicle { String brand; public Vehicle(String brand) { this.brand = brand; System.out.println("Vehicle 생성자 호출"); } public void startEngine() { System.out.println("엔진 시작"); } public void displayBrand() { System.out.println("브랜드: " + brand); } } // 서브클래스 class Car extends Vehicle { int numberOfDoors; public Car(String brand, int numberOfDoors) { super(brand); // 슈퍼클래스 생성자 호출 this.numberOfDoors = numberOfDoors; System.out.println("Car 생성자 호출"); } public void displayDetails() { super.displayBrand(); // 슈퍼클래스 메서드 호출 System.out.println("문 개수: " + numberOfDoors); } } public class Main { public static void main(String args) { Car myCar = new Car("Toyota", 4); myCar.startEngine(); myCar.displayDetails(); } }
Java
복사
다단계 상속은 Animal 클래스, Animal을 상속하는 Dog 클래스, Dog을 상속하는 Labrador 클래스의 예로 설명할 수 있습니다. 계층적 상속은 Animal 슈퍼클래스와 그로부터 상속받는 DogCat 서브클래스를 통해 보여줄 수 있습니다.  

3.8 상속의 이점

상속은 서브클래스가 슈퍼클래스의 필드와 메서드를 다시 작성하지 않고 직접 사용할 수 있으므로 코드 재사용성을 크게 향상시킵니다. 또한 메서드 오버라이딩을 가능하게 하여 런타임 다형성을 달성하고 서브클래스가 상속된 메서드의 동작을 사용자 정의하거나 확장할 수 있도록 합니다. 상속은 계층적 분류를 지원하여 객체 간의 실제 관계를 반영함으로써 코드베이스를 보다 체계적이고 이해하기 쉽게 만듭니다.  

4. 다형성: 하나의 인터페이스, 다양한 구현

4.1 정의 및 목적

"많은 형태"를 의미하는 그리스어에서 유래한 다형성은 객체가 특정 클래스 유형에 따라 다르게 동작할 수 있도록 하는 핵심 OOP 개념입니다. 이를 통해 단일 작업(메서드 호출)이 컨텍스트 또는 객체의 실제 런타임 클래스에 따라 다양한 방식으로 수행될 수 있습니다. 다형성은 유연성을 제공하여 서로 다른 클래스의 객체를 공통 슈퍼클래스의 객체로 취급할 수 있도록 합니다.   다형성을 통해 동일한 인터페이스를 통해 서로 다른 유형의 객체에 접근할 수 있습니다. 이를 통해 다양한 객체 유형으로 작동할 수 있는 보다 일반적이고 재사용 가능한 코드를 작성할 수 있습니다.  

4.2 컴파일 시간 다형성 (메서드 오버로딩)

컴파일 시간 다형성(정적 다형성이라고도 함)은 메서드 오버로딩을 통해 달성됩니다. 메서드 오버로딩은 클래스가 동일한 이름을 가지지만 매개변수 목록이 다른 여러 메서드를 가질 때 발생합니다(매개변수의 수, 유형 또는 순서가 다름). 컴파일러는 메서드 호출 중에 전달된 인수를 기반으로 호출할 오버로드된 메서드를 결정합니다. 일반적인 예로는 서로 다른 수 또는 유형의 인수를 취할 수 있는 Calculator 클래스의 여러 add() 메서드가 있습니다.  

4.3 런타임 다형성 (메서드 오버라이딩)

런타임 다형성(동적 다형성 또는 동적 메서드 디스패치라고도 함)은 메서드 오버라이딩을 통해 달성됩니다. 메서드 오버라이딩은 서브클래스가 슈퍼클래스에 이미 정의된 메서드에 대한 특정 구현을 제공할 때 발생합니다. 실행될 메서드는 참조 유형이 아닌 객체의 실제 유형에 따라 런타임에 결정됩니다. 슈퍼클래스 유형의 참조 변수가 서브클래스 유형의 객체를 참조하는 업캐스팅은 런타임 다형성을 달성하는 데 필수적입니다. 일반적인 예로는 Animal 슈퍼클래스의 makeSound() 메서드가 DogCat 서브클래스에 의해 오버라이드되어 특정 소리를 내는 것을 들 수 있습니다.  

4.4 코드 예제

메서드 오버로딩은 서로 다른 매개변수 목록을 가진 여러 개의 add() 메서드를 포함하는 Calculator 클래스를 통해 보여줄 수 있습니다.  
class Calculator { public int add(int x, int y) { return x + y; } public int add(int x, int y, int z) { return x + y + z; } public double add(double x, double y) { return x + y; } public static void main(String args) { Calculator calc = new Calculator(); System.out.println(calc.add(5, 10)); // 출력: 15 System.out.println(calc.add(5, 10, 15)); // 출력: 30 System.out.println(calc.add(3.5, 2.7)); // 출력: 6.2 } }
Java
복사
메서드 오버라이딩은 Animal 슈퍼클래스와 makeSound() 메서드를 오버라이드하는 DogCat 서브클래스를 통해 설명할 수 있습니다. 런타임 다형성은 Shape 슈퍼클래스와 draw() 또는 area() 메서드를 오버라이드하는 RectangleCircle 서브클래스를 통해 보여줄 수 있습니다.  
class Animal { public void makeSound() { System.out.println("동물이 소리를 냅니다."); } } class Dog extends Animal { @Override public void makeSound() { System.out.println("멍멍"); } } class Cat extends Animal { @Override public void makeSound() { System.out.println("야옹"); } } public class Main { public static void main(String args) { Animal animal1 = new Animal(); Animal animal2 = new Dog(); // 업캐스팅 Animal animal3 = new Cat(); // 업캐스팅 animal1.makeSound(); // 출력: 동물이 소리를 냅니다. animal2.makeSound(); // 출력: 멍멍 (Dog의 구현) animal3.makeSound(); // 출력: 야옹 (Cat의 구현) } }
Java
복사

4.5 다형성의 이점

다형성은 동일한 메서드나 클래스를 다양한 유형의 객체와 함께 사용할 수 있도록 하여 코드 재사용성을 높입니다. 또한 서로 다른 클래스의 객체를 공통 슈퍼클래스의 객체로 취급할 수 있도록 하여 유연성을 제공합니다. 다형성은 추상 클래스나 인터페이스를 사용하여 구체적인 유형 대신 일반적인 유형(슈퍼클래스 또는 인터페이스)으로 작업할 수 있도록 하여 추상화를 지원합니다. 또한 Java는 런타임에 객체의 실제 유형에 따라 적절한 메서드를 선택하여 프로그램에 동적 동작을 제공합니다. 다형성은 코드 유지보수를 단순화하고 슈퍼클래스 참조를 사용하는 기존 코드를 수정하지 않고 새로운 서브클래스를 쉽게 추가할 수 있도록 하여 확장성을 향상시킵니다.  

5. 추상화: 복잡성 숨기기 및 필수 요소 표시

5.1 정의 및 목적

자바에서 추상화는 복잡한 구현 세부 정보를 숨기고 사용자에게 필수적인 기능만 보여주는 기본적인 OOP 개념입니다. 이를 통해 개발자는 구현 세부 정보에 얽매이지 않고 강력하고 유지보수 가능하며 재사용 가능한 코드를 더 쉽게 설계할 수 있도록 객체의 단순화된 표현으로 작업할 수 있습니다. 추상화를 통해 개발자는 내부 구현 세부 정보를 이해할 필요 없이 클래스 및 메서드와 상호 작용할 수 있습니다. 추상화의 목적은 관련 없는 세부 정보를 무시하고 복잡성을 줄여 복잡한 시스템을 단순화하여 개발자가 객체가 어떻게 작동하는지보다는 무엇을 하는지에 집중할 수 있도록 하는 것입니다.   실제 세계에서 추상화는 커피 머신을 사용하는 것과 비슷합니다. 복잡한 내부 메커니즘을 이해하지 않고도 커피를 만드는 방법을 알기만 하면 됩니다. 이 비유는 추상화가 기본 복잡성을 숨기면서 사용자 친화적인 인터페이스를 제공하는 방법을 보여줍니다.  

5.2 자바에서 추상화 달성

자바는 추상화를 달성하는 두 가지 주요 방법을 제공합니다. 바로 추상 클래스인터페이스입니다. 추상 클래스는 부분적인 추상화를 제공하는 반면, 인터페이스는 (Java 8 이전에는) 100% 추상화를 제공합니다.  

5.3 추상 클래스

자바의 추상 클래스는 자체적으로 인스턴스화할 수 없으며 일반적으로 다른 클래스의 청사진 역할을 합니다. 추상 클래스는 abstract 키워드를 사용하여 선언됩니다. 추상 클래스는 추상 메서드(본문이 없는 메서드)와 구체적인 메서드(본문이 있는 메서드)를 모두 포함할 수 있습니다. 추상 클래스의 서브클래스는 서브클래스도 추상으로 선언되지 않는 한 모든 추상 메서드에 대한 구체적인 구현을 제공해야 합니다.   추상 클래스는 다른 클래스의 청사진 역할을 하며, 특정 구현을 서브클래스에 맡기면서 공유 속성과 메서드를 제공할 수 있습니다. 이 메커니즘은 서브클래스에서 특정 구조와 동작을 강제합니다.  

5.4 인터페이스

자바에서 인터페이스는 추상 메서드, 정적 메서드(Java 8부터), 기본 메서드(Java 8부터) 및 정적 상수를 포함할 수 있는 참조 유형입니다. 인터페이스를 구현하는 클래스는 모든 추상 메서드에 대한 구현을 제공해야 합니다. 인터페이스는 완전한(100%) 추상화를 달성하는 데 사용됩니다. 클래스는 여러 인터페이스를 구현하여 여러 소스에서 동작을 상속받을 수 있으므로 일종의 다중 상속을 달성할 수 있습니다. 인터페이스는 클래스가 준수해야 하는 메서드 집합을 지정하는 계약 역할을 하며, 동작에 중점을 둡니다. 이는 유연성을 향상시키고 결합도를 낮추는 "구현이 아닌 인터페이스에 대한 프로그래밍" 접근 방식을 촉진합니다.  

5.5 코드 예제

추상 메서드 area()를 가진 추상 Shape 클래스는 CircleRectangle과 같은 구체적인 서브클래스에 의해 구현됩니다.  
// 추상 클래스 abstract class Shape { String color; // 추상 메서드 (구현 없음) abstract double area(); // 추상 클래스는 생성자를 가질 수 있습니다. public Shape(String color) { System.out.println("Shape 생성자 호출"); this.color = color; } // 구체적인 메서드 public String getColor() { return color; } public abstract String toString(); } class Circle extends Shape { double radius; public Circle(String color, double radius) { // Shape 생성자 호출 super(color); System.out.println("Circle 생성자 호출"); this.radius = radius; } @Override double area() { return Math.PI * Math.pow(radius, 2); } @Override public String toString() { return "Circle 색상은 " + super.getColor() + "이고 면적은: " + area(); } } class Rectangle extends Shape { double length; double width; public Rectangle(String color, double length, double width) { // Shape 생성자 호출 super(color); System.out.println("Rectangle 생성자 호출"); this.length = length; this.width = width; } @Override double area() { return length * width; } @Override public String toString() { return "Rectangle 색상은 " + super.getColor() + "이고 면적은: " + area(); } } public class Test { public static void main(String args) { Shape s1 = new Circle("Red", 2.2); Shape s2 = new Rectangle("Yellow", 2, 4); System.out.println(s1.toString()); System.out.println(s2.toString()); } }
Java
복사
draw() 메서드를 가진 Drawable 인터페이스는 CircleRectangle과 같은 클래스에 의해 구현됩니다. 추상 메서드 makeSound()를 가진 추상 Animal 클래스는 DogCat 클래스에 의해 확장됩니다. start()stop() 메서드를 가진 Vehicle 인터페이스는 Car 클래스에 의해 구현됩니다.  

5.6 추상화의 이점

추상화는 불필요한 세부 정보를 숨김으로써 복잡한 시스템과의 상호 작용을 단순화합니다. 공통 인터페이스와 동작을 정의하여 코드 재사용성을 높입니다. 내부 구현 세부 정보를 숨김으로써 보안을 강화하여 무단 접근 및 수정으로부터 코드를 보호합니다. 복잡한 시스템을 더 작고 관리하기 쉬운 컴포넌트로 분해하여 모듈화를 용이하게 합니다. 기존 코드를 수정하지 않고 새로운 기능을 쉽게 추가할 수 있도록 하여 확장성을 촉진합니다. 구현 변경 사항이 추상화에 의존하는 코드에 영향을 미치지 않도록 합니다. 이러한 분리는 시스템을 더욱 강력하고 진화하기 쉽게 만듭니다.  

6. 자바의 고급 OOP 개념 (선택 사항)

6.1 연관, 집합 및 구성

연관: 한 클래스의 객체가 다른 클래스의 객체와 상호 작용할 수 있는 두 개의 별도 클래스 간의 일반적인 관계를 나타냅니다. 이는 "uses-a" 관계입니다.   • 집합: "has-a" 관계를 나타내는 연관의 특수한 형태입니다. 전체-부분 관계를 나타내며, 부분은 전체와 독립적으로 존재할 수 있습니다. 예: DepartmentEmployees를 가지지만, 부서가 해체되어도 직원은 존재할 수 있습니다.   • 구성: "has-a" 관계를 나타내는 집합의 더 강력한 형태입니다. 그러나 구성에서는 부분이 전체와 독립적으로 존재할 수 없습니다. 이는 더 강력한 의존성을 의미합니다. 예: CarEngine을 가집니다. 엔진은 일반적으로 자동차 외부에서는 존재하거나 작동할 수 없습니다.  

6.2 OOP 원칙을 활용하는 디자인 패턴

디자인 패턴은 소프트웨어 설계에서 일반적인 문제에 대한 재사용 가능한 솔루션입니다. 많은 디자인 패턴이 핵심 OOP 원칙에 크게 의존합니다. • 팩토리 패턴: 생성할 정확한 클래스를 지정하지 않고 서로 다른 클래스의 객체를 생성하기 위해 추상화를 사용합니다.   • 싱글톤 패턴: 캡슐화를 사용하여 클래스의 인스턴스가 하나만 존재하도록 보장하고 이에 대한 전역 접근 지점을 제공합니다.   • 전략 패턴: 알고리즘의 패밀리를 정의하고 각각을 캡슐화하여 서로 바꿔 사용할 수 있도록 함으로써 다형성을 활용합니다.   • 옵저버 패턴: 상속 및 인터페이스를 사용하여 한 객체와 그 종속 객체 간에 일대다 의존성을 정의하여 한 객체의 상태가 변경되면 모든 종속 객체에 자동으로 알리고 업데이트합니다.   • 어댑터 패턴: 캡슐화 및 잠재적으로 상속을 활용하여 호환되지 않는 인터페이스를 가진 객체가 함께 작동할 수 있도록 합니다.  

7. 결론: 자바 개발에서 OOP 원칙 적용

OOP는 객체와 그 상호 작용을 중심으로 코드를 구성하여 모듈화되고 재사용 가능하며 유지보수가 용이한 코드를 생성할 수 있도록 하는 강력한 소프트웨어 개발 패러다임을 제공합니다. 효과적인 자바 프로그래밍을 위해서는 캡슐화, 상속, 다형성, 추상화라는 핵심 OOP 원칙에 대한 깊은 이해가 필수적입니다. SOLID 원칙(단일 책임, 개방/폐쇄, 리스코프 치환, 인터페이스 분리, 의존성 역전) 및 DRY 원칙 준수, 상속보다 구성을 선호하는 것과 같은 객체지향 설계의 모범 사례를 따르면 보다 강력하고 확장 가능한 애플리케이션을 만들 수 있습니다. 이러한 OOP 개념을 효과적으로 적용함으로써 자바 개발자는 복잡한 애플리케이션을 보다 효율적으로 구축하고 시간이 지남에 따라 코드를 더 쉽게 관리, 이해 및 수정할 수 있습니다.  
단일 상속: 서브클래스가 하나의 슈퍼클래스만 상속합니다. 자바는 클래스에 대해 이를 지원합니다.
다단계 상속: 클래스가 다른 클래스를 상속하고, 그 클래스는 또 다른 클래스를 상속하여 상속 체인을 형성합니다.
계층적 상속: 여러 서브클래스가 단일 슈퍼클래스에서 직접 상속합니다.
다중 상속: 자바는 클래스에 대해 직접적인 다중 상속(하나의 클래스가 둘 이상의 직접적인 슈퍼클래스를 상속하는 것)을 지원하지 않습니다. 이는 "다이아몬드 문제"를 피하기 위함입니다. 그러나 인터페이스를 통해 달성됩니다.
혼합 상속: 둘 이상의 상속 유형(예: 계층적 및 다중 상속)의 조합입니다.
연관: 한 클래스의 객체가 다른 클래스의 객체와 상호 작용할 수 있는 두 개의 별도 클래스 간의 일반적인 관계를 나타냅니다. 이는 "uses-a" 관계입니다.
집합: "has-a" 관계를 나타내는 연관의 특수한 형태입니다. 전체-부분 관계를 나타내며, 부분은 전체와 독립적으로 존재할 수 있습니다. 예: DepartmentEmployees를 가지지만, 부서가 해체되어도 직원은 존재할 수 있습니다.
구성: "has-a" 관계를 나타내는 집합의 더 강력한 형태입니다. 그러나 구성에서는 부분이 전체와 독립적으로 존재할 수 없습니다. 이는 더 강력한 의존성을 의미합니다. 예: CarEngine을 가집니다. 엔진은 일반적으로 자동차 외부에서는 존재하거나 작동할 수 없습니다.
팩토리 패턴: 생성할 정확한 클래스를 지정하지 않고 서로 다른 클래스의 객체를 생성하기 위해 추상화를 사용합니다.
싱글톤 패턴: 캡슐화를 사용하여 클래스의 인스턴스가 하나만 존재하도록 보장하고 이에 대한 전역 접근 지점을 제공합니다.
전략 패턴: 알고리즘의 패밀리를 정의하고 각각을 캡슐화하여 서로 바꿔 사용할 수 있도록 함으로써 다형성을 활용합니다.
옵저버 패턴: 상속 및 인터페이스를 사용하여 한 객체와 그 종속 객체 간에 일대다 의존성을 정의하여 한 객체의 상태가 변경되면 모든 종속 객체에 자동으로 알리고 업데이트합니다.
어댑터 패턴: 캡슐화 및 잠재적으로 상속을 활용하여 호환되지 않는 인터페이스를 가진 객체가 함께 작동할 수 있도록 합니다.