[북스터디] 자바 코딩 인터뷰 완벽 가이드_ch6객체지향프로그래밍(2)_캡슐화/상속/다형성

Wang_Seok_Hyeon·2023년 8월 6일
0
post-thumbnail

Intro

정리란 건 참 어려운 것 같다...
책은 거의 다 읽어 가고 또 실습도 하고 정리를 위해 책을 여러 번 이리 저리 보게 되는 건 좋은데
그럴 때 마다 어디를 덜어내야 하나. 어떻게 작성해야 하나 고민이 많은 것 같다.
더 많은 편이 있는데 총 9가지를 3번에 나누어 언급하고
SOLID는 양이 많더라도 한 번에 다 몰아서 업로드를 할 것 같다.

그럼 캡슐화, 상속, 다형성에 대해서 알아 보자!

캡슐화(encapsulation)

4. 캡슐화란 무엇인가?

필수개념.

  • 캡슐화는 객체지향 프로그래밍의 핵심 개념 중 하나다.
  • 캡슐화는 객체 상태가 외부로부터 숨겨진 상황에서 이 상태에 접근하는 일련의 공개(public) 메서드만 노출하는 기법입니다.
  • 캡슐화는 각 객체가 클래스 내에서 객체의 상태를 비공개(private)로 유지할 때 성립합니다.
  • 캡슐화는 정보 은닉(information-hiding) 메커니즘이라고도 합니다.
  • 캡슐화는 느슨한 결합(loosely coupled), 재사용성, 보안 및 테스트하기 쉬운 코드와 같은 여러가지 중요한 이점을 제공합니다.
  • 자바에서 캡슐화는 접근 제어자(public, private, protected)로 구현할 수 있습니다.

실제 답변은 아래와 같은 틀로

캡슐화는 객체지향 프로그래밍의 핵심 개념 중 하나입니다. 캡슐화는 주로 코드와 데이터를 하나의 작업 단위인 클래스로 결합하고 외부 코드가 이 데이터에 직접 접근학지 못하게 하는 방어막 역할을 합니다. 또한 객체 상태를 외부로부터 숨기고 이 상태에 접근하기 위한 일련의 public 메서드를 노출하는 기법입니다. 각 객체가 클래스 안에서 상태를 private으로 유지할 때 캡슐화가 성립되었다고 말할 수 있습니다. 따라서 캡슐화는 정보 은닉 메커니즘이라고 불리기도 합니다. 캡슐화를 이용하는 코드는 몇 가지 장점이 있습니다. 먼저, 느슨한 결합이 가능합니다. 예를 들어 클라이언트 코드와 어긋나지 않는 상태에서 클래스 변수의 이름을 변경할 수 있습니다. 또한 재사용성 할 수 있으며 클래스 내에서 데이터가 어떻게 조작되는지 클라이언트가 인식하지 못하므로 안전합니다. 마지막으로 필드를 테스트하는 것보다 메서드를 테스트하는 편이 더 쉽기 때문에 캡슐화를 이용한 코드는 테스트하기 쉽다는 장점이 있습니다. 자바에서 캡슐화는 public, private, protected와 같은 접근 제어자로 구현할 수 있습니다. 일반적으로 객체가 자체 상태를 관리할 때 상태는 private 변수로 선언되고public 메서드로 접근 및 수정됩니다. 예를 살펴 볼까요? Cat 클래스는 mood(기분), hungry(배고픔),energy(에너지)와 같은 필드로 구성할 수 있스비다. Cat 클래스의 외부 코드는 이러한 필드를 직접 수정할 수는 없지만, play, feed, sleep과 같이 클래스 상태를 내부적으로 수정하는 public 메서드를 호출할 수 있습니다. Cat 클래스는 meow와 같이 클래스 외부에서 접근할 수 없는 private 메서드도 가질 수 있습니다. 이것이 캡슐화 입니다.

추가적인 설명이 필요하면 아래와 같이 코드를 작성할 수 있으며 이를 작성할 때도 계속 설명을 이어 나가야 하며 아래와 같은 플로우를 따르자.

public class Cat {
    private int mood = 50;
    private int hungry = 50;
    private int energy = 50;

    public void sleep() {
        System.out.println("Sleep ...");
        energy++;
        hungry++;
    }

    public void play() {
        System.out.println("Play ...");
        mood++;
        energy--;
        meow();
    }

    public void feed() {
        System.out.println("Feed ...");
        hungry--;
        mood++;
        meow();
    }

    private void meow() {
        System.out.println("Meow!");
    }

    public int getMood() {
        return mood;
    }

    public int getHungry() {
        return hungry;
    }

    public int getEnergy() {
        return energy;
    }
}

상태는 public 메서드인 play, feed, sleep으로만 수정할 수 있으며 다음 코드에서 예를 볼 수 있습니다.

public class Main {
    public static void main(String[] args) {
        Cat cat = new Cat();

        cat.feed();
        cat.play();
        cat.feed();
        cat.sleep();

        System.out.println("Energy: " + cat.getEnergy());
        System.out.println("Mood: " + cat.getMood());
        System.out.println("Hungry: " + cat.getHungry());
    }
}

출력 결과는 다음과 같습니다.

Feed ...
Meow!
Play ...
Meow!
Feed ...
Meow!
Sleep ...
Energy: 50
Mood: 53
Hungry: 49

상속(inheritance)

5 상속이란 무엇인가?

필수개념.

  • 상속은 객체지향 프로그래밍의 핵심 개념 중 하나다.
  • 상속을 통해 다른 객체를 기반으로 하는 새로운 객체를 만들 수 있다.
  • 상속은 객체가 다른 객체의 코드를 재사용할 수 있도록 허용하여 코드의 재사용성을 유지한다. 또한 각 객체만의 로직도 추가할 수 있다.
  • 상속은 IS-A 관계라고 하며 부모-자녀 관계다.
  • 자바에서 상속은 extends 키워드로 구현할 수 있다.
  • 상속된 객체는 슈퍼클래스라고 하고, 슈퍼클래스를 상속받은 객체는 서브클래스라다.
  • 자바에서는 여러 개의 클래스를 상속할 수 없다.

실제 답변은 아래와 같은 틀로

상속은 객체지향 프로그래밍의 핵심 개념 중 하나입니다. 상속을 통해 다른 객체를 기반으로 객체를 만들 수 있습니다. 상속은 다른 객체가 상당히 유사하고 몇 가지 공통된 로직을 공유하지만 완전히 동일하지는 않을 때 유용합니다. 상속은 객체가 다른 객체의 코드를 재사용할 수 있도록 허용하여 코드의 재사용성을 유지하고 각 객체만의 로직도 추가할 수 있습니다. 따라서 상속을 구현하려면 공통된 로직을 재사용하고 다른 클래스의 고유 로직을 추출해야 합니다. 이를 IS-A 관계라고 하며 부모-자녀 관계라고도 합니다. 상속 관계는 Foo 가 Buzz를 상속할 때 ‘Foo IS-A Buzz’ 와 같이 표현할 수 있습니다. 예를 들어 ‘고양이 IS-A 고양이과의 동물’ 또는 ‘기차 IS-A 교통 수단’과 같이 상속 관계를 표현할 수 있습니다. IS-A 관계는 클래스의 계층 정의에 사용하는 작업 단위입니다. 자바에서 상속은 extends 키워드로 부모 클래스로부터 자식 클래스를 파생시켜 구현합니다. 자식 클래스는 부모의 필드와 메서드를 재사용할 수 있으며 자신만의 필드와 메서드를 추가할 수 있습니다. 상속된 객체는 슈퍼클래스 또는 부모 클래스라고 하고 슈퍼클래스를 상속받은 객체는 서브클래스 또는 자식 클래스라고 합니다. 자바에서는 여러 개의 클래스를 상속할 수 없습니다. 따라서 서브클래스 또는 자식 클래스는 2개 이상의 슈퍼클래스 또는 부모 클래스로부터 필드와 메서드를 상속받을 수 없습니다. 예를 들어 employee(직원) 클래스는 부모 클래스로서 소프트웨어 회사의 모든 직원의 공통 로직을 정의할 수 있고, Programmer(개발자) 클래스는 자식 클래스로서 Employee 클래스를 상속받아 정의된 공통 로직을 사용하고 개발자에게 특화된 로직을추가할 수 있습니다. 다른 클래스도 Programmer나 Employee 클래슬르 상속할 수 있습니다.

추가적인 설명이 필요하면 아래와 같이 코드를 작성할 수 있으며 이를 작성할 때도 계속 설명을 이어 나가야 하며 아래와 같은 플로우를 따르자.

Employee 클래스는 꽤 간단합니다. 이 클래스는 직원의 이름을 포함합니다.

public class Employee {
    private String name;
    
    public Employee(String name) {
        this.name = name;        
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }   
}

Programmer 클래스는 Employee를 상속받습니다. 직원 중 개발자의 이름을 포함합니다. 단, 개발자의 경우 속한 팀 이름도 있습니다.

public class Programmer extends Employee {
    private String team;

    public Programmer(String name, String team) {
        super(name);
        this.team = team;
    }

    public String getTeam() {
        return team;
    }

    public void setTeam(String team) {
        this.team = team;
    }
}

그럼 이제 Programmer 인스턴스를 생성하고 Employee 클래스에서 상속받은 getName 메서드와 Programmer 클래스에서 상속받은 getTeam 메서드를 호출항려 상속을 테스트 해보곘습니다.

public class Main {
    public static void main(String[] args) {
        Programmer p = new Programmer("김수한무", "거북이와 두루미");

        String name = p.getName();
        String team = p.getTeam();

        System.out.println(name + " is assigned to the " + team + " team");
    }
}

출력 결과는 아래와 같습니다.

이번에는 다형성이다. 첫 도입부에서 썼던 내용의 반복이지만 예시 등이 추가되었다 :)

다형성(polymorphism)

6 다형성이란 무엇인가?

필수개념.

  • 다형성은 객체지향 프로그래밍의 핵심 개념 중 하나다.
  • 다형성을 뜻하는 ‘polymorphism’이라는 단어는 그리스어로 ‘많은 형태’를 의미한다.
  • 다형성은 때에 따라 객체가 다르게 동작할 수 있다.
  • 다형성은(컴파일 타임 다형성)메서드 오버로딩(over loading)이 있다.
  • 다형성은 IS-A관계의 경우(런타임 다형성) 메서드 오버라이딩도 있다.

실제 답변은 아래와 같은 틀로

다형성은 객체지향 프로그래밍의 핵심 개념 중 하나입니다. 다형성을 뜻하는 ‘polymor-phism’은 ‘많다’를 의미하는 ‘poly’와 ‘형태’를 의미하는 ‘morph’라는 2개의 그리스어로 구성된 단어입니다. 즉 다형성은 많은 형태를 의미합니다.

좀 더 정확하게 말해, 객체지향 프로그래밍에서 다형성은 객체가 때에 따라 다르게 동작 할 수 있게 해주거나 어떤 동작이 다른 방법으로 동작할 수 있도록 하는 역할을 합니다. 다형성을 구현하는 방법 중 하나는 메서드 오버로딩입니다. 여러 개의 메서드가 동일한 이름을 가지고 있지만 매개변수가 다른 경ㅇ우 컴파일러가 오버로드된 메서드 가운데 어떤 형식을 호출할 것인지 컴파일 시간에 식별할 수 있으므로 컴파일 타임 다형성이라고도 합니다. 이때 오버로드된 메서드의 형태에 따라 객체는 다르게 동작합니다. 예를 들어 Triangle(삼각형)이라는 클래스에는 서로 다른 매개벼수를 가진 여러 개의 draw 메서드를 정의할 수 있습니다. 다형성을 구현하는 또 다른 방법은 메서드 오버라이딩입니다. 메서드 오버라이등은 IS-A 관계가 잇을 때 일반적으로 사용하는 방법이며, 런타임 다형성 또는 동적 메서드 디스패치(dynamic method dispatch)라고 합니다. 보통 여러 가지 메서드를 포함하는 인터페이스 구현으로 시작하며, 각 클래스는 특화된 동작을 수행하기 위해 인터페이스에 있는 메서드를 오버라이드 합니다. 이때 다형성이 타입에 대한 혼란 없이 이 클래스들을 부모 인터페이스와 똑같이 사용할 수 있게 합니다. 런타임에 자바가 이러한 클래스를 구별할 수 있고 어느 클래스가 사용되는지 알고 잇기 때문에 가능한 일입니다. 예를 들어 draw 메서드를 가지는 Shape(모양 이라는 인터페이스가 있다고 할 때, Triabngle,Rectangle(직사각형),Circle(원) 클래스가 Shape 인터페이스 구현에 있는 draw 메서드를 오버라이드해서 각 도형에 해당하는 모양을 그릴 수 있습니다.

추가적인 설명이 필요하면 아래와 같이 코드를 작성할 수 있으며 이를 작성할 때도 계속 설명을 이어 나가야 하며 아래와 같은 플로우를 따르자.

메서드 오버로딩을 활용한 컴파일 타임 다형성

Triangle 클래스는 다음과 같이 세 가지 draw 메서드를 포함합니다.

public class Triangle {
    public void draw() {
        System.out.println("Draw default triangle ...");
    }

    public void draw(String color) {
        System.out.println("Draw a triangle of color " + color);
    }

    public void draw(int size, String color) {
        System.out.println("Draw a triangle of color " + color + " and scale it up with the new size of " + size);
    }
}

다음으로 각 draw 메서드를 어떻게 호출할 수 있는지 보겠습니다.

public class Main {
    public static void main(String[] args) {
        Triangle triangle = new Triangle();

        triangle.draw();
        triangle.draw("red");
        triangle.draw(10, "blue");
    }
}

출력 결과는 아래와 같습니다.

Draw default triangle ...
Draw a triangle of color red
Draw a triangle of color blue and scale it up with the new size of 10

메서드 오버라이딩을 활용한 런타임 다형성

이번에는 런타임 다형성을 구현하는 예제를 살펴 보겠습니다. draw 메서드를 다음과 같이 인터페이스에 선언합니다.

public interface Shape {
    public void draw();
}

Triangle, Rectangle, Circle 클래스는 Shape 인터페이스를 구현하고 해당하는 모양을 그리도록 draw 메서드를 오버라이드합니다.

public class Triangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Draw a triangle ...");
    }
}
public class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Draw a rectangle ...");
    }
}
public class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("Draw a circle ...");
    }
}

이제 삼각형, 직사각형, 원의 각 클래스에 대한 인스턴스를 생성하고 draw 메서드를 호출하겠습니다.

public class Main {
    public static void main(String[] args) {
        Shape triangle = new Triangle();
        Shape rectangle = new Rectangle();
        Shape circle = new Circle();

        triangle.draw();
        rectangle.draw();
        circle.draw();
    }
}

출력 결과를 보면 런타임에 자바가 적합한 draw 메서드를 호출했다는 것을 알 수 있습니다.

profile
하루 하루 즐겁게

1개의 댓글

comment-user-thumbnail
2023년 8월 6일

글 잘 봤습니다.

답글 달기