< 해당 내용의 사진은 한빛출판사의 이미지를, 강의 내용은 내일 배움 캠프를 통해 서술했습니다. >

 

객체

객체란 세상에 존재하는 물체를 뜻하며 식별이 가능한 것을 말한다. 강하지, 반려동물, 자동차와 같이 물질적인 것 부터 개념적인 부분까지 식별이 가능하면 객체라 불릴 수 있다. 객체는 일정한 속성을 가지고 있으며 행동을 할 수 있다.

자동차의 속성과 행위

 

이처럼 현실 세계에 있는 객체를 소프트웨어의 객체로 설계하는 것을 ‘객체 모델링’이라고 부른다.


객체지향

객체 지향 프로그래밍은 컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 여러 개의 독립된 단위, 즉 "객체"들의 모임으로 파악하고자 하는 것이다. 각각의 객체는 메시지 를 주고받고, 데이터를 처리할 수 있다. (협력)

 

• 객체 지향 프로그래밍은 프로그램을 유연하고 변경이 용이하게 만들기 때문에 대규모 소프 트웨어 개발에 많이 사용된다.

 

자바의 객체지향

위 처럼 사람, 자동차 2가지 객체를 만들고 그들의 특징을 엮어서 '자동차를 타고 이동하는 사람'을 만들어 낼 수 있다. 


다형성

https://velog.io/@lsj16632/%EB%8B%A4%ED%98%95%EC%84%B1%EC%9D%B4%EB%9E%80JAVA

 

다형성은 "하나의 인터페이스(또는 메서드)가 여러 가지 형태를 가질 수 있다"는 의미입니다. 이를 통해 코드를 더 유연하고 확장 가능하게 만들어 줍니다.

 

위의 예시처럼 자동차는 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. 다형성의 장점

  1. 유연한 설계 가능.
  2. 변경에 대한 영향 최소화.
  3. 확장성 향상.
  4. 코드 재사용성 증가.
  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.
    }
}

 

더보기

코드 설명

  1. Animal 클래스: eat() 메소드를 가지는 부모 클래스입니다.
  2. Dog 클래스: Animal 클래스를 상속받아 bark() 메소드를 추가했고, eat() 메소드를 오버라이딩하여 개에 맞는 동작을 정의했습니다.
  3. 오버라이딩: 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에서 데이터를 상속받아 사용한다.

상속의 특징

  1. 코드 재사용성: 기존에 작성한 클래스의 코드와 기능을 자식 클래스에서 재사용할 수 있습니다. 이는 중복 코드를 줄이고 코드 유지보수를 쉽게 해줍니다.
  2. 계층 구조 생성: 부모-자식 관계의 계층 구조를 만들어 시스템을 구조적으로 관리할 수 있습니다. 이를 통해 클래스 간의 관계를 명확히 할 수 있습니다.
  3. 확장성: 새로운 기능이 필요할 때 기존 클래스에 추가하기보다 자식 클래스를 만들어 확장할 수 있습니다. 이렇게 하면 기존 클래스는 변경하지 않고 기능을 확장할 수 있어 코드의 안정성이 높아집니다.
  4. 다형성(Polymorphism): 상속을 통해 생성된 자식 클래스는 부모 클래스의 메소드를 자신의 방식대로 재정의(오버라이딩)할 수 있습니다. 이를 통해 부모 클래스를 참조하는 변수로 여러 자식 클래스의 인스턴스를 다룰 수 있습니다.

상속의 장점

  1. 코드 중복 감소: 자식 클래스가 부모 클래스의 속성과 메소드를 상속받아 사용할 수 있으므로 중복 코드를 줄일 수 있습니다.
  2. 유지보수 용이: 공통 기능을 부모 클래스에서 관리하기 때문에 수정 사항이 있을 때 모든 자식 클래스에 일관되게 반영할 수 있습니다. 예를 들어, 부모 클래스의 메소드를 수정하면 자식 클래스에서도 그 변화가 반영됩니다.
  3. 모듈화: 관련 있는 기능을 부모 클래스에 모아 두고, 세부적인 기능은 자식 클래스에 추가하여 코드가 모듈화되어 구조적인 프로그램을 작성할 수 있습니다.
  4. 다형성: 상속을 통해 다형성을 구현하여 코드의 유연성과 확장성을 높일 수 있습니다. 이를 통해 동일한 부모 클래스를 참조하는 변수가 다양한 형태로 동작할 수 있습니다.

 


추상화

  • 객체에서 공통된 부분들을 모아 상위 개념으로 새롭게 선언하는 것을 추상화라고 합니다
  • 자식들 중 공통된 특징(데이터, 메소드)이 있다면 부모에 선언하는 방식 
    https://velog.velcdn.com/images/dev-mage/post/55d11b06-01b6-42a6-b982-a97489ecbcc6/image.png

1-1이 자동차고, 그 하위가 페라리, 포터, 벤츠라고 하면 자식들은 바퀴가 4개인 특징이 있다. 이런 공통적인 특징이 있다면 1-1인 자동차에서 바퀴가 4개이다를 정의하고 아래 자식들에게 뿌려주면 된다. 이런 역전 방식을 추상화라한다.

 

추상화의 특징

  1. 중요한 정보만 노출: 추상화는 클래스나 객체의 중요한 속성과 동작을 중심으로 표현하여, 불필요한 세부 사항을 감춥니다. 사용자는 인터페이스나 메소드의 기본 동작 방식만 알면 되며, 내부 동작 원리를 몰라도 됩니다.
  2. 추상 클래스와 인터페이스 사용: 추상화는 주로 abstract 키워드로 선언된 추상 클래스나 인터페이스를 통해 구현됩니다. 추상 클래스는 공통 동작을 정의하고, 인터페이스는 반드시 구현해야 할 메소드를 선언함으로써 각기 다른 클래스가 공통된 동작을 갖도록 합니다.
  3. 구현의 강제성: 추상 클래스나 인터페이스를 사용하면, 상속받는 클래스가 특정 메소드를 반드시 구현하도록 강제할 수 있습니다. 이를 통해 코드의 일관성과 안정성을 높일 수 있습니다.
  4. 유연한 확장 가능성: 추상화를 사용하면 클래스 간의 결합도를 낮출 수 있어, 코드의 유연성과 확장 가능성이 높아집니다. 시스템이 확장되거나 변경되어도 기존 코드를 최소한으로 수정하면서 기능을 추가할 수 있습니다.

추상화의 장점

  1. 코드의 단순화: 불필요한 세부 사항을 숨기고 중요한 정보만 제공하여 코드의 복잡성을 줄이고 이해하기 쉽게 만듭니다. 사용자 입장에서 복잡한 내부 로직을 알 필요 없이 필요한 기능만 사용할 수 있습니다.
  2. 유지보수성 향상: 인터페이스나 추상 클래스는 공통된 작업 방식을 정의하고 구현체는 이를 따르기 때문에, 유지보수가 쉬워집니다. 새로운 기능을 추가하거나 수정할 때 전체 시스템에 미치는 영향을 줄일 수 있습니다.
  3. 모듈화 및 확장성: 추상화를 통해 각 클래스가 독립적으로 동작하면서도 동일한 인터페이스나 추상 클래스의 규칙을 따르기 때문에, 시스템의 모듈화가 용이해집니다. 이는 새로운 기능을 추가하거나 기존 코드를 수정할 때 유연성을 제공합니다.
  4. 다형성 지원: 추상 클래스와 인터페이스를 통해 다양한 객체들이 동일한 인터페이스를 따르도록 할 수 있어 다형성(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
    }
}
더보기

코드 설명

  1. 추상 클래스 Shape: Shape 클래스는 calculateArea()라는 추상 메소드를 선언했습니다. 이 메소드는 구체 클래스에서 정의되어야 합니다.
  2. 구체 클래스 Circle과 Rectangle: Shape 클래스를 상속받아 calculateArea() 메소드를 각각 원과 사각형에 맞게 구현했습니다.
  3. 추상화의 효과: 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

+ Recent posts