< 해당 내용의 사진은 한빛출판사의 이미지를, 강의 내용은 내일 배움 캠프를 통해 서술했습니다. >
객체
객체란 세상에 존재하는 물체를 뜻하며 식별이 가능한 것을 말한다. 강하지, 반려동물, 자동차와 같이 물질적인 것 부터 개념적인 부분까지 식별이 가능하면 객체라 불릴 수 있다. 객체는 일정한 속성을 가지고 있으며 행동을 할 수 있다.
이처럼 현실 세계에 있는 객체를 소프트웨어의 객체로 설계하는 것을 ‘객체 모델링’이라고 부른다.
객체지향
객체 지향 프로그래밍은 컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 여러 개의 독립된 단위, 즉 "객체"들의 모임으로 파악하고자 하는 것이다. 각각의 객체는 메시지 를 주고받고, 데이터를 처리할 수 있다. (협력)
• 객체 지향 프로그래밍은 프로그램을 유연하고 변경이 용이하게 만들기 때문에 대규모 소프 트웨어 개발에 많이 사용된다.
위 처럼 사람, 자동차 2가지 객체를 만들고 그들의 특징을 엮어서 '자동차를 타고 이동하는 사람'을 만들어 낼 수 있다.
다형성
다형성은 "하나의 인터페이스(또는 메서드)가 여러 가지 형태를 가질 수 있다"는 의미입니다. 이를 통해 코드를 더 유연하고 확장 가능하게 만들어 줍니다.
위의 예시처럼 자동차는 K3, 아반떼, 테슬라등 여러가지 형태를 가질 수 있다. 차라는 역활이 테슬라라는 구체적인 대상으로 구현된 것이다.
이렇게 역활과 구현으로 구분하면 세상이 단순해지고, 유연해지며 변경이 편리해진다.
1. 실세계 비유
- 운전자 - 자동차:
운전자는 자동차의 내부 구조를 알 필요 없이 운전대, 브레이크, 가속기라는 역할(인터페이스)만 이용하면 된다.
자동차 내부를 바꿔도 운전자는 영향을 받지 않는다. - 공연 무대:
무대에서 배우(역할)가 어떤 사람으로 바뀌어도, 대본(인터페이스)에 따라 공연이 진행된다.
배우가 바뀌더라도 공연은 유지된다. - 키보드, 마우스, USB 등 표준 인터페이스:
다양한 키보드나 마우스가 연결되더라도 표준 인터페이스(USB)를 통해 동작한다.
하드웨어의 내부 구현은 다를 수 있지만, 인터페이스는 일정하다. - 정렬 알고리즘:
정렬을 수행하는 인터페이스를 두고, QuickSort, MergeSort, BubbleSort 등의 구현을 상황에 따라 바꿀 수 있다. - 할인 정책 로직:
다양한 할인 정책(예: 정률 할인, 정액 할인)을 인터페이스로 정의하고 구현을 바꿔도 로직은 변경되지 않는다.
2. 역할과 구현 분리
- 역할과 구현을 분리하면 다음과 같은 장점이 있다:
- 클라이언트는 대상의 역할(인터페이스)만 알면 된다.
- 구현 대상의 내부 구조를 몰라도 된다.
- 내부 구조가 변경되어도 클라이언트에 영향을 주지 않는다.
- 구현 대상을 아예 바꿔도 클라이언트가 영향을 받지 않는다.
- 객체 설계 원칙:
- 객체를 설계할 때 역할(인터페이스)을 먼저 정의하고, 이를 수행하는 구현 객체를 만든다.
- 클라이언트와 서버는 서로 협력 관계를 가지며, 서로 역할에만 의존한다.
3. 자바 언어에서 다형성
- 역할 = 인터페이스
- 예: MemberRepository라는 인터페이스.
- 구현 = 인터페이스를 구현한 클래스
- 예: MemoryMemberRepository, JdbcMemberRepository.
- 실행 시점에 객체 변경 가능:
- public class MemberService { private MemberRepository memberRepository = new MemoryMemberRepository(); } // 구현 객체를 유연하게 변경 가능 public class MemberService { private MemberRepository memberRepository = new JdbcMemberRepository(); }
- 다형성의 본질:
- 실행 시점에 인터페이스를 구현한 객체를 유연하게 변경할 수 있다.
- 클라이언트를 변경하지 않고 서버의 구현 기능을 변경할 수 있다.
4. 스프링과 객체지향
- 스프링에서 다형성의 활용:
- 제어의 역전(IoC): 객체의 생성과 생명주기를 스프링이 관리.
- 의존관계 주입(DI): 필요한 객체를 주입받아 유연한 설계를 가능하게 함.
- 역할과 구현을 분리하여, 객체를 마치 레고 블록처럼 조립 가능.
- 공연 무대에서 배우를 교체하듯, 구현을 간단히 변경 가능.
5. 다형성의 장점
- 유연한 설계 가능.
- 변경에 대한 영향 최소화.
- 확장성 향상.
- 코드 재사용성 증가.
- 구현의 내부 구조를 은닉하여 클라이언트를 단순화.
6. 다형성의 한계
- 역할(인터페이스) 자체가 변하면, 클라이언트와 서버 모두 큰 변경이 필요:
- 예: 자동차 인터페이스를 비행기로 변경.
- USB 인터페이스의 규격 변경.
- 해결 방안:
- 인터페이스를 안정적으로 설계하는 것이 중요.
7. 요약
- 다형성은 객체 간 협력을 기반으로 역할과 구현을 분리해 유연하고 확장 가능한 설계를 가능하게 한다.
- 스프링 프레임워크는 다형성을 극대화하여 객체 지향 설계를 돕는 강력한 도구를 제공한다.
- IoC와 DI를 활용해 구현체를 쉽게 교체하거나 확장할 수 있다.
- 인터페이스 설계의 안정성이 다형성 설계의 핵심이다.
객체 간의 협력
객체간의 상호작용을 말한다.
- 사람이 자동차의 가속 페달을 밟으면 자동차는 이에 반응하여 속도를 올리며 앞으로 이동합니다.
- 사람이 자동차의 브레이크 페달을 밟으면 자동차는 이에 반응하여 속도를 줄이며 정지합니다.
자바에서 객체는 메서드를 통해서 서로 상호작용하면서 데이터를 주고 받을 수 있다.
예를 들어 사람이 자동차 객체가 가지고 있는 가속페달 메서드gasPedal(50);을 호출하면 자동차는 이 메서드에 반응하여 속도 속성의 값을 50으로 수정시킨다. 그러면 사람 객체는 자동차 객체의 브레이크 페달 brakePedal();을 호출한다.
+ 여기서 50은 매개값, 파라미터 라고 불리며 메서드 호출시 데이터를 포함해 호출하게 해준다
또한 자동차 객체는 gasPedal(50); 메서드에서 속도를 바꾸는 작업을 수행한 후 사람 객체에게 실행 결과인 속도의 값을 반환할 수 있습니다. 이때 반환되는 값을 ‘리턴값’이라 표현한다.
객체 간의 관계
- 객체간의 협력에도 종류가 있는데, 크게 사용관계, 포함관계, 상속관계가 존재한다.
사용 관계
- 사람 객체는 자동차 객체를 사용한다.
포함 관계
- 타이어 객체, 차 문 객체, 핸들 객체는 자동차 객체에 포함되어 있다.
상속 관계
- 만약 공장에 자동차만 생산하는 게 아니라 기차도 생산한다고 가정해 보자.
- 자동차와 기차 객체는 하나의 공통된 기계 시스템 객체를 토대로 만들어진다.
- 그렇다면 자동차 객체와 기차 객체는 기계 시스템 객체를 상속받는 상속 관계가 된다.
자바는 효율성을 강조한 언어이다. 위의 blueprint를 Car, Train 객체에서 각각 1번씩 정의하는 방식보다는, 그 상위 객체인 MachineSystem에서 한번만 정의하고 하위 객체에게 주소를 넘겨주는게 메모리 효율성이 높다.
// 부모 클래스
class Animal {
// 공통 메소드
public void eat() {
System.out.println("This animal eats food.");
}
}
// 자식 클래스
class Dog extends Animal {
// 자식 클래스에만 있는 메소드
public void bark() {
System.out.println("The dog barks.");
}
// 부모 클래스의 메소드 오버라이딩
@Override
public void eat() {
System.out.println("The dog eats dog food.");
}
}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
// 상속받은 메소드 사용
dog.eat(); // The dog eats dog food.
dog.bark(); // The dog barks.
}
}
코드 설명
- Animal 클래스: eat() 메소드를 가지는 부모 클래스입니다.
- Dog 클래스: Animal 클래스를 상속받아 bark() 메소드를 추가했고, eat() 메소드를 오버라이딩하여 개에 맞는 동작을 정의했습니다.
- 오버라이딩: eat() 메소드를 자식 클래스에서 재정의하여 다형성을 구현했습니다.
캡슐화
- 캡슐화란 속성(필드)와 행위(메서드)를 하나로 묶어 객체로 만든 후 실제 내부 구현 내용은 외부에서 알 수 없게 감추는 것을 의미합니다. (안전성 확보)
- 외부 객체에서는 캡슐화된 객체의 내부 구조를 알 수 없기 때문에 노출시켜 준 필드 혹은 메서드를 통해 접근할 수 있습니다.
- 필드와 메서드를 캡슐화하여 숨기는 이유는 외부 객체에서 해당 필드와 메서드를 잘못 사용하여 객체가 변화하지 않게 하는 데 있습니다.
- Java에서는 캡슐화된 객체의 필드와 메서드를 노출시킬지 감출지 결정하기 위해 접근 제어자를 사용합니다.
단순히 설명하면, 알약 캡슐처럼 내용을 하나의 알약에 담는다. 다만 알약이 밖에서 볼때 어떤 성분이 있는지 확인할 수 없듯 기본적으로 캡슐화가 된 클래스의 내용은 확인할 수 없다. 하지만 프로그래머가 예외로 설정한 데이터, 메소드에는 접근할 수 있다.
public class User {
// private 접근 제어자를 사용하여 필드를 외부에서 직접 접근하지 못하게 함
private String name;
private int age;
// 생성자를 통해 객체 초기화
public User(String name, int age) {
this.name = name;
this.age = age;
}
// getter 메소드: 필드에 대한 접근을 제공
public String getName() {
return name;
}
public int getAge() {
return age;
}
// setter 메소드: 필드를 수정할 수 있게 제공
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
if(age > 0) { // 유효성 검사를 통해 데이터 보호
this.age = age;
} else {
System.out.println("Age must be greater than 0.");
}
}
}
위의 private는 '개인적으로' 라는 의미로 캡슐만이 알 수 있고 외부에서 접근할 수 없는 데이터나 메소드를 만들어준다.
반대로 public은 '공공적으로' 라는 의미로 외부에서 접근이 가능하고 수정까지도 할 수 있게 해준다.
상속
- 객체지향 프로그래밍에는 부모 객체와 자식 객체가 존재합니다.
- 부모 객체는 가지고 있는 필드와 메서드를 자식 객체에 물려주어 자식 객체가 이를 사용할 수 있도록 만들 수 있습니다.
- 위에서 설명했던 대로, 효율성을 추구하는 자바라는 언어 특성상 굳이 곂치는 내용을 여러번 정의하는 것을 꺼린다. MachineSystem의 자식인 Car, Train에서 bluePrint를 정의하지 않고 부모인 MachineSystem에서 데이터를 상속받아 사용한다.
상속의 특징
- 코드 재사용성: 기존에 작성한 클래스의 코드와 기능을 자식 클래스에서 재사용할 수 있습니다. 이는 중복 코드를 줄이고 코드 유지보수를 쉽게 해줍니다.
- 계층 구조 생성: 부모-자식 관계의 계층 구조를 만들어 시스템을 구조적으로 관리할 수 있습니다. 이를 통해 클래스 간의 관계를 명확히 할 수 있습니다.
- 확장성: 새로운 기능이 필요할 때 기존 클래스에 추가하기보다 자식 클래스를 만들어 확장할 수 있습니다. 이렇게 하면 기존 클래스는 변경하지 않고 기능을 확장할 수 있어 코드의 안정성이 높아집니다.
- 다형성(Polymorphism): 상속을 통해 생성된 자식 클래스는 부모 클래스의 메소드를 자신의 방식대로 재정의(오버라이딩)할 수 있습니다. 이를 통해 부모 클래스를 참조하는 변수로 여러 자식 클래스의 인스턴스를 다룰 수 있습니다.
상속의 장점
- 코드 중복 감소: 자식 클래스가 부모 클래스의 속성과 메소드를 상속받아 사용할 수 있으므로 중복 코드를 줄일 수 있습니다.
- 유지보수 용이: 공통 기능을 부모 클래스에서 관리하기 때문에 수정 사항이 있을 때 모든 자식 클래스에 일관되게 반영할 수 있습니다. 예를 들어, 부모 클래스의 메소드를 수정하면 자식 클래스에서도 그 변화가 반영됩니다.
- 모듈화: 관련 있는 기능을 부모 클래스에 모아 두고, 세부적인 기능은 자식 클래스에 추가하여 코드가 모듈화되어 구조적인 프로그램을 작성할 수 있습니다.
- 다형성: 상속을 통해 다형성을 구현하여 코드의 유연성과 확장성을 높일 수 있습니다. 이를 통해 동일한 부모 클래스를 참조하는 변수가 다양한 형태로 동작할 수 있습니다.
추상화
- 객체에서 공통된 부분들을 모아 상위 개념으로 새롭게 선언하는 것을 추상화라고 합니다
- 자식들 중 공통된 특징(데이터, 메소드)이 있다면 부모에 선언하는 방식
https://velog.velcdn.com/images/dev-mage/post/55d11b06-01b6-42a6-b982-a97489ecbcc6/image.png
1-1이 자동차고, 그 하위가 페라리, 포터, 벤츠라고 하면 자식들은 바퀴가 4개인 특징이 있다. 이런 공통적인 특징이 있다면 1-1인 자동차에서 바퀴가 4개이다를 정의하고 아래 자식들에게 뿌려주면 된다. 이런 역전 방식을 추상화라한다.
추상화의 특징
- 중요한 정보만 노출: 추상화는 클래스나 객체의 중요한 속성과 동작을 중심으로 표현하여, 불필요한 세부 사항을 감춥니다. 사용자는 인터페이스나 메소드의 기본 동작 방식만 알면 되며, 내부 동작 원리를 몰라도 됩니다.
- 추상 클래스와 인터페이스 사용: 추상화는 주로 abstract 키워드로 선언된 추상 클래스나 인터페이스를 통해 구현됩니다. 추상 클래스는 공통 동작을 정의하고, 인터페이스는 반드시 구현해야 할 메소드를 선언함으로써 각기 다른 클래스가 공통된 동작을 갖도록 합니다.
- 구현의 강제성: 추상 클래스나 인터페이스를 사용하면, 상속받는 클래스가 특정 메소드를 반드시 구현하도록 강제할 수 있습니다. 이를 통해 코드의 일관성과 안정성을 높일 수 있습니다.
- 유연한 확장 가능성: 추상화를 사용하면 클래스 간의 결합도를 낮출 수 있어, 코드의 유연성과 확장 가능성이 높아집니다. 시스템이 확장되거나 변경되어도 기존 코드를 최소한으로 수정하면서 기능을 추가할 수 있습니다.
추상화의 장점
- 코드의 단순화: 불필요한 세부 사항을 숨기고 중요한 정보만 제공하여 코드의 복잡성을 줄이고 이해하기 쉽게 만듭니다. 사용자 입장에서 복잡한 내부 로직을 알 필요 없이 필요한 기능만 사용할 수 있습니다.
- 유지보수성 향상: 인터페이스나 추상 클래스는 공통된 작업 방식을 정의하고 구현체는 이를 따르기 때문에, 유지보수가 쉬워집니다. 새로운 기능을 추가하거나 수정할 때 전체 시스템에 미치는 영향을 줄일 수 있습니다.
- 모듈화 및 확장성: 추상화를 통해 각 클래스가 독립적으로 동작하면서도 동일한 인터페이스나 추상 클래스의 규칙을 따르기 때문에, 시스템의 모듈화가 용이해집니다. 이는 새로운 기능을 추가하거나 기존 코드를 수정할 때 유연성을 제공합니다.
- 다형성 지원: 추상 클래스와 인터페이스를 통해 다양한 객체들이 동일한 인터페이스를 따르도록 할 수 있어 다형성(Polymorphism)을 구현하기 쉽습니다. 예를 들어, 동일한 메소드를 다양한 방식으로 구현하여 객체마다 다른 동작을 수행하게 할 수 있습니다.
// 추상 클래스
abstract class Shape {
// 추상 메소드 - 구체적인 구현은 자식 클래스에서 정의
public abstract double calculateArea();
// 공통 메소드
public void display() {
System.out.println("This is a shape.");
}
}
// 구체 클래스 - Circle
class Circle extends Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
// 추상 메소드 구현
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
}
// 구체 클래스 - Rectangle
class Rectangle extends Shape {
private double width;
private double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
// 추상 메소드 구현
@Override
public double calculateArea() {
return width * height;
}
}
public class Main {
public static void main(String[] args) {
Shape circle = new Circle(5);
Shape rectangle = new Rectangle(4, 6);
circle.display(); // This is a shape.
System.out.println("Circle Area: " + circle.calculateArea()); // Circle Area: 78.5398...
rectangle.display(); // This is a shape.
System.out.println("Rectangle Area: " + rectangle.calculateArea()); // Rectangle Area: 24.0
}
}
코드 설명
- 추상 클래스 Shape: Shape 클래스는 calculateArea()라는 추상 메소드를 선언했습니다. 이 메소드는 구체 클래스에서 정의되어야 합니다.
- 구체 클래스 Circle과 Rectangle: Shape 클래스를 상속받아 calculateArea() 메소드를 각각 원과 사각형에 맞게 구현했습니다.
- 추상화의 효과: Shape 클래스를 상속받은 모든 도형 클래스는 calculateArea() 메소드를 반드시 구현하도록 강제됩니다. 이를 통해 각 도형 클래스가 calculateArea() 메소드를 통해 면적을 계산할 수 있다는 점에서 일관된 사용법을 제공하게 됩니다.
객체와 클래스
- 우리는 객체를 생성하기 위해서 설계도가 필요합니다.
- 현실 세계에서는 자동차를 만들기 위해 자동차 설계도를 토대로 자동차를 생산합니다.
- 마찬가지로 소프트웨어에서도 객체를 만들기 위해서는 설계도에 해당하는 클래스가 필요합니다.
- 이때 클래스를 토대로 생성된 객체를 해당 클래스의 ‘인스턴스’라고 부르며 이 과정을 ‘인스턴스화’라고 부릅니다.
- 동일한 클래스로 여러 개의 인스턴스를 만들 수 있습니다.
- 이때 객체와 인스턴스는 거의 비슷한 표현이지만 자세하게 구분해 보자면 아래와 같습니다.
자동차 클래스를 통해 만들어진 하나의 자동차를 인스턴스라고 부르며 이러한 여러 개의 인스턴스들을 크게 통틀어서 자동차 객체라고 표현할 수 있다.
class Car {
String model;
String color;
// 기본 생성자
public Car(String model, String color) {
this.model = model;
this.color = color;
}
// gasPedal 메서드: 속도를 설정하는 메서드로 kmh 값을 speed 필드에 저장하고 반환
public double gasPedal(double kmh) {
speed = kmh; // 자동차의 현재 속도를 kmh 값으로 설정
return speed; // 설정된 속도 반환
}
}
public class Main {
public static void main(String[] args) {
// 인스턴스화 과정
Car car1 = new Car("Sedan", "Red"); // Car 클래스로부터 car1 객체 생성
Car car2 = new Car("SUV", "Blue"); // Car 클래스로부터 car2 객체 생성
System.out.println(car1.model); // Sedan 출력
System.out.println(car2.model); // SUV 출력
}
}
슬슬 헷갈릴 건데 간단히 하면
인스턴스(객체) | car1, car2 |
인스턴스화 | new |
메서드 | public double gasPedal(double kmh) |
클래스 | class Car |
생성자 | public Car(String model, String color) / 생성자는 메서드와 다르게 클래스와 이름이 같다 |
Car car1 = new Car("Sedan", "Red");는 Car 클래스를 사용하여 "Sedan"과 "Red"라는 속성값을 가지는 새로운 Car 객체를 생성하고, 그 객체를 car1이라는 참조형 변수에 참조하도록 하는 코드
'Back-End (Web) > JAVA' 카테고리의 다른 글
[JAVA] 객체의 필드와 메서드 (0) | 2024.11.12 |
---|---|
[JAVA] 클래스 설계와 객체 생성 (0) | 2024.11.12 |
[JAVA] 참조형 자료구조 정리(LIST / STACK / QUEUE / SET / MAP) (0) | 2024.11.11 |
[JAVA] 배열 (0) | 2024.11.11 |
[JAVA] 반복문 (0) | 2024.11.11 |