Back-End (Web)/JAVA

[JAVA] 객체지향 프로그래밍

JABHACK 2024. 11. 12. 09:54

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

 

객체지향

객체지향은 자바에서 가장 중요한 기능 중 하나로 체계적인 프로그래밍을 가능하게 만들어준다. 사전적 의미로는, 현실 세계에서 어떠한 제품을 만들기 위해 부품들을 하나씩 조립해서 완성시키는 것처럼 소프트웨어 또한 필요한 부품들을 만들고 하나씩 조립해서 하나의 완성된 프로그램을 만들 수 있는데 이러한 기법을 ‘객체지향 프로그래밍’이라고 부른다.

 

단순히 말하면 기능, 혹은 데이터 별로 쪼개어 하나의 부품단위로 만들고 이를 잘 합쳐서 프로그램을 만든다 보면 된다.

자바의 객체지향

 

객체

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

자동차의 속성과 행위

 

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

 

객체 간의 협력

 

객체간의 상호작용을 말한다.

    • 사람이 자동차의 가속 페달을 밟으면 자동차는 이에 반응하여 속도를 올리며 앞으로 이동합니다.
    • 사람이 자동차의 브레이크 페달을 밟으면 자동차는 이에 반응하여 속도를 줄이며 정지합니다.

 

자바에서 객체는 메서드를 통해서 서로 상호작용하면서 데이터를 주고 받을 수 있다.

 

예를 들어 사람이 자동차 객체가 가지고 있는 가속페달 메서드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