객체지향의 캡슐화

JTH·2023년 2월 24일
0

객체지향 프로그래밍에서 캡슐화

  • 객체의 상태와 행위를 하나의 단위로 묶는 것을 의미합니다. 즉, 객체 내부에 속한 데이터와 그 데이터를 처리하는 메서드를 외부에서 직접 접근할 수 없도록 제어하는 것입니다.

캡슐화의 목적은 다음과 같습니다.

  1. 정보 은닉: 객체의 내부 상태를 외부에서 직접 접근하지 못하도록 함으로써 객체의 불변성을 보장하고, 객체의 내부 구현을 숨기고 보호할 수 있습니다.

  2. 코드 재사용성: 객체를 사용하는 외부 코드에서는 객체의 내부 구현에 대해 신경쓰지 않아도 되므로, 코드의 재사용성이 증가합니다.

  3. 유지보수성: 객체 내부의 변경이 외부 코드에 미치는 영향을 제한함으로써, 유지보수성을 향상시킵니다.

캡슐화를 구현하는 방법은 다음과 같습니다.

  1. 접근 제어자: 객체 내부의 데이터와 메서드에 접근할 수 있는 권한을 제어합니다. 자바에서는 public, protected, private 등의 접근 제어자를 제공합니다.

  2. getter와 setter 메서드: 객체 내부의 데이터에 접근하고 값을 변경하는 메서드를 제공합니다. 이를 통해 데이터에 대한 직접적인 접근을 막을 수 있습니다.

  3. 인터페이스: 객체의 외부 인터페이스를 정의하여 객체의 내부 구현을 숨기고, 외부에서는 인터페이스만을 사용하여 객체를 조작할 수 있도록 합니다.

캡슐화를 통해 객체지향 프로그래밍에서는 객체의 내부 상태와 행위를 하나의 단위로 묶어서 관리하며, 이를 통해 정보 은닉, 코드 재사용성, 유지보수성을 향상시킬 수 있습니다.

알고나면 공중제비 3바퀴 돌수있는 캡슐화에 대해

https://bperhaps.tistory.com/entry/%EC%BA%A1%EC%8A%90%ED%99%94%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-%EC%96%B4%EB%96%A4-%EC%9D%B4%EC%A0%90%EC%9D%B4-%EC%9E%88%EB%8A%94%EA%B0%80

아래의 내용은 위 링크에 자세히..

캡슐화는 객체의 속성과 행위를 하나로 묶고 구현 내용을 외부에 감춘다는것.. 이게 왜 중요한가??

class Capsule {
    int number;
    
    public Capsule(int number) {
        this.number = number;
    }
    
    public double getHalf() {
        return number / 2;
    }
}

class Main {
    public static void main(String[] args) {
        Capsule capsule = new Capsule(10);
        System.out.println(capsule.getHalf());
    }
}

간단한 소스이다. int값을 초기값으로 갖는 객체가 있고, 그 값의 절반을 반환하는 gethalf() 라는 메소드가 존재한다.

여기서 캡슐화는? 바로 Capsule 클래스 자체를 의미한다.

캡슐화를 위키와 조금 다르게 말하면, 아래와 같이 말할 수 있다.

데이터와, 데이터를 처리하는 행위를 묶고, 외부에는 그 행위를 보여주지 않는 것.

그렇다면 위 소스에서 정말 위의 정의를 만족하는지 보자. Capsule 클래스는 int라는 데이터를 가지고 있다. 그리고 getHalf라는 데이터를 처리하는 행위또한 가지고 있다. 마지막으로 Main메소드의 입장에서, Capsule 클래스의 getHalf() 를 사용할 수는 있지만 구현이 어떻게 되어 있는지는 알 수 없다.

와! 위 정의를 만족한다! 그래, 그러면 캡슐화라는게 이런거란건 알겠다. 그런데 데이터와, 행위를 하나로 묶고, 그걸 외부에 노출시키지 않는게 왜 중요한가?

아래와 같이 코드를 작성하더라도 같은 행위가 아닌가?

class Capsule {
    int number;
    
    public Capsule(int number) {
        this.number = number;
    }
    
    public int getNumber() {
        return number;
    }
}

class Main {
    public static void main(String[] args) {
        Capsule capsule = new Capsule(10);
        System.out.println(capsule.getNumber() / 2D);
    }
}

그렇다, 결과론적으론 같은 결과를 도출한다. 하지만 이런 코드를 작성하면 여러가지 문제점이 발생할 수 있다.

그 문제점이 무엇인지 한번 아래에서 살펴보자.

- 캡슐화를 지키기 위한 규칙중에는 "Tell, Don't Ask" 라는 원칙이 있다.

객체 내부의 데이터를 꺼내와서 처리하는게 아닌, 객체에게 처리할 행위를 요청하라는 행위이다. 이러한 행위를 우리는 "객체에 메세지를 보낸다" 라고 말한다.

그렇다면 왜 캡슐화를 지키기 위해서는 데이터를 객체로부터 받아와서 처리하면 안된다고 하는걸까? 캡슐화의 장점을 살펴보면 그 이유를 간단히 이해할 수 있다.

캡슐화를 통해 우리가 얻을 수 있는 이점중 가장 큰것은 코드의 중복을 피할 수 있다는 점과, 데이터를 처리하는 동작 방식을 외부에서 알 필요가 없다는 점이다.

코드의 중복을 피한다는 점과, 동작 방식을 외부에서 알 필요가 없다는 것 또한, 객체지향을 처음 접하면 그게 왜 중요한지 이해가 안될 수 있다. 이럴때는 예제를 통한 설명이 가장 확실하다.

  • 어떤 물품의 10% 할인된 금액을 구해야 한다고 생각해 보자. 만일 데이터를 객체에서 받아와서 처리를 한다면 우리는 비즈니스 로직에 다음과 같은 코드를 추가할 것이다.
public void foo(Goods goods) {
    double discountedPrice = goods.getPrice() * 0.9;
    var(discountedPrice);
}
  • 상품의 가격을 가지고 와서 10프로 할인된 가격을 구하고, 다른 로직으로 넘겼다. 즉, 위 코드는 데이터를 객체로 부터 받아와서 처리하는 로직을 구현하고 있다(아마 많이 본 형식의 코드일 것 이다). 그렇다면, 만일 10프로 할인된 금액을 다른 로직에서도 사용하게 된다면 어떻게 될까?
public void foo(Goods goods) {
    double discountedPrice = goods.getPrice() * 0.9;
    var(discountedPrice);
}

public void foo2(Goods goods) {
    double discountedPrice = goods.getPrice() * 0.9;
    var2(discountedPrice);
}
  • 코드의 중복이 일어났다. 혹자는 그거 몇줄 안되는 코드 좀 중복 나면 어때?? 라고 생각할 수 있다. 하지만 위와 같은 로직이 서비스에서 수백번 필요하다 생각해 보자... 그걸 일일이 타이핑, 혹은 복붙하는 행위는 고역일 것 이다. 또는, 코드를 작성하는 코더가 변경되었다고 했을 때, 10프로 할인 로직을 아래처럼 작성해 버릴 수도 있다.
public void foo(Goods goods) {
    double discountedPrice = goods.getPrice() - goods.getPrice() * 0.1;
    var(discountedPrice);
}
  • 코드의 중복뿐 아니라 파편화 까지 일어났다.

좋다. 코드의 중복이 안좋다는것은 이제 알겠다. 그러면 데이터를 처리하는 방식의 외부에 드러나지 않는것은 어떤면에서 이점이 있을까?

위 예제에서 요구사항이 변경되어 10프로 할인된 금액이 아니라 20프로 할인된 금액으로 로직을 바꿔야 한다고 생각해 보자. 그렇다면 위와 같이 코드가 작성되었다면, 우리는 도대체 몇줄의 코드를 고쳐야 하는가? 그래, IDE의 검색기능을 이용해 편히 고칠 수 있다고 해 보자. 하지만 위에서 말한 것 처럼 코더가 바뀌어서 10프로 할인 로직이 다른 코드가 존재한다면? 이것또한 검색으로 찾을 수 있을것인가? 아마 매우 어려울 것 이다. 또한 이런 현상으로 인해 10프로 할인 로직을 20프로 할인로직으로 변경하지 못한 코드가 하나라도 존재한다면, 서비스에 큰 타격이 있을 것이다.

그렇다면 이번에는 데이터를 객체로 부터 받아오는게 아닌, 객체에게 처리를 요청하는 방식의 코드를 작성하면 어떻게 될까?

class Goods {
    int price = 10000;
    ...
    public int getDiscountedPrice() {
        return price * 0.9;
    }
}

public void foo(Goods goods) {
    double discountedPrice = goods.getDiscountedPrice();
    var(discountedPrice);
}

10프로 할인된 금액을 도출하는 로직이 객체 안으로 이동했다. 그에따라 foo()에서 할인된 금액을 생성하는 부분도, 비즈니스 로직이 10프로를 할인 하는게 아닌 Goods에게 "메세지를 보내서(메소드를 호출하여)" 데이터를 가지고 있는 Goods가 스스로 처리하도록 소스가 변경되었다.

그렇다면 이제 위에서 말했던 문제들을 다시 적용시켜 보자. 위와 같이 할인된 금액을 사용하는 로직이 수백개가 있고, 요구사항이 20프로를 할인하도록 변경됐다. 우리는 무엇을 바꾸면 되는가? getDiscountedPrice()의 로직을 수정하면 된다. 데이터를 처리하는 방식이 외부에 드러나는게 아닌, 객체 스스로 처리하도록 하니 모든 문제가 해결됐다.

이제 캡슐화의 강력함을 이해 하였는가?

필자는 이해하고 공중제비를 3바퀴나 돌았다.

공중제비를 돌다못해 탈수 되었습니다.

profile
//

0개의 댓글