OOP-Java) 캡슐화 (Encapsulation)
안녕하세요! 오늘은 OOP(객체 지향 프로그래밍)에서 중요한 이론 중 하나인 캡슐화에 대해서 알아보겠습니다!

캡슐화? (Wiki)
객체의 속성(data fields)과 행위(methods)를 하나로 묶고, 실제 구현 내용 일부를 외부에 감추어 은닉한다.
위 인용의 설명을 보면 “실제 구현의 내용 일부를 외부에 감추어 은닉한다”라고 되어있습니다.
‘은닉’이라는 단어 때문에 캡슐화 자체의 의미가 약간은 흐려질 수 있는데 캡슐화와 은닉화는 목적이 다른 행위로 분리하되, 캡슐화를 통해 은닉을 할 수 있다라고 이해하는게 개인적으론 이해하기 더 편리했습니다.
실제로 캡슐화는 은닉을 위해서 사용되지만 복잡한 프로그램에서 캡슐화는 코드의 재사용성, 가독성을 높여주어 유지보수를 용이하게 해주는 아주 기특한(?) 이론입니다.
제가 이해한 캡슐화의 필요성을 한마디로 정리하자면,
“캡슐화를 통해 외부로부터의 접근을 통제하여 자율성이 높은 객체를 만든다”가 되겠습니다!
일단은 예제를 통해서 어떤 얘기를 하는 건지 좀 더 자세히 알아보겠습니다.
제대로 캡슐화&은닉이 안된 예제를 보겠습니다.
예제에서는 통장 잔액을 조회하는 로직을 작성해보겠습니다.
예제 1 나쁜 코드
package kr.devf.blog.encapsulation;
public class MoneyMoney{
//Member variable
public Kbank kbank;
public String name;
//Constructor
public MoneyMoney(String name, float money){
this.name = name;
this.kbank = new Kbank(name, money);
}
//Getter of Kbank
public Kbank getKbank(){
return this.kbank;
}
}
public class Kbank{
public Map<String, Float> account;
public final String unit = "$";
public Kbank(String name, float money){
this.account = new HashMap<>();
this.account.put(name,money);
}
public Float getMoney(String name){
return this.account.get(name);
}
}
//Main(or Business) logic
public class Main{
public static void main(String[] args){
MoneyMoney myMoney = new MoneyMoney("woorim", 5000f);
//우림은 부자인가? 500만원 넘게 있으면 부자라고 해보자..ㅎㅎ
float myWon = myMoney.getKbank().getMoney("woorim") * 1300f / 10000f;
if( myWon > 500f){
System.out.println("우림은 알고보니 부자였다!");
}else{
System.out.println("우림은 역시나 빈곤했다..#(");
}
}
}
위 예제 1 코드를 보면 많은 문제가 있습니다.
- 모든 접근 제어자가 Public으로 되어 있어 모든 객체에서 직접 접근이 가능합니다.
- 이를 통해 객체 내부의 구현을 모두 유추할 수 있고 객체의 자율성을 잃으며, 단순히 코드만 분리한 셈이 됩니다. (위 구현에서는 Getter를 사용했지만 사실 수정자 없이도 접근할 수 있는 상태인 것 입니다.)
- 그럼 접근 제어자를 Private로 바꾸면 되는거 아니야? -> 아닙니다. 좀 더 완전하고 제대로된 캡슐화를 위해선 Getter도 수정이 필요합니다.
- 현재 예제의 구현에선 멤버 변수의 이름을 그대로 Getter로 생성하였습니다. 만약 은행이 Kbank가 아니고 농협은행이 된다면? 내부 구현만 수정하고 비즈니스 로직에서 getKbank()를 그대로 사용할 경우 향후 코드 수정시 혼동을 가져올 가능성이 크고, MoneyMoney객체가 하위에 은행 객체를 바로 가지고 있다는 것을 메서드 이름을 통해 노출하였습니다.
- 우리가 자주 애용하는 피자집의 레시피가 바뀌었다고 해서 피자집에 주문할 때 “새롭게 바뀐 더 맛있는 피자로 주세요!”라고 해야할까요?
- 저장된 데이터가 달러 유닛이기 때문에 이를 한국의 ‘만원’ 단위로 변경하는 비즈니스 로직이 MainService에 구현되어 있습니다.
- 이는 객체의 응집도는 낮추고 결합도는 높이게 되는 나쁜 코드라고 볼 수 있습니다. 우리는 항상 객체의 자율성을 보장하기 위해 객체가 스스로 내부 상태를 변경하여 서비스를 인터페이스(메서드) 단위로만 제공할 수 있도록 해야 합니다!
- 이것은 마치 우리가 피자집으로부터 재료를 받아 직접 피자를 굽는 것과 다를 바가 없습니다..
그럼, 위 문제를 개선한 진짜 캡슐화된 예제를 보겠습니다.
예제 2 캡슐화
package kr.devf.blog.encapsulation;
public class Moneymoney{
//Member variable
private Kbank kbank;
private String name;
private final float oneDolar = 1300f;
//Constructor
public MoneyMoney(String name, float money){
this.name = name;
this.kbank = new Kbank(name, money);
}
//Getter of Kbank
public float getMyTotalWon(){
return this.kbank.getMoney() * this.oneDolar / 10000f;
}
public float getMyTotalDolar(){
return this.kbank.getMoney();
}
}
public class Kbank{
private Map<String, Float> account;
private final String unit = "$";
private String name;
public Kbank(String name, float money){
this.name = name;
this.account = new HashMap<>();
this.account.put(name,money);
}
public Float getMoney(){
return this.account.get(this.name);
}
}
//Main(or Business) logic
public class Main {
public static void main(String[] args){
MoneyMoney myMoney = new MoneyMoney("woorim", 5000); //Name, Money(dolar)
//우림은 부자인가? 500만원 넘게 있으면 부자라고 해보자
float myWon = myMoney.getMyTotalWon();
if( myWon > 500){
System.out.println("우림은 알고보니 부자였다!");
}else{
System.out.println("우림은 역시나 빈곤했다..#(");
}
}
}
위 예제 2에선 예제 1의 모든 문제를 해결하였습니다.
MoneyMoney 객체에서 getMyTotalWon()을 호출하여 내부의 구현을 알 수 없게 은닉하였고, MoneyMoney객체에 은행 객체가 추가되거나 각 은행 객체의 수정시에도 비즈니스 로직을 완전히 분리하여 서비스 로직에선 코드의 변경이 필요 없게 끔 변경하였습니다.
캡슐화를 통한 완전한 은닉을 위해선 프로그램 설계시 저수준 모듈과 고수준 묘듈을 잘 분리하여 변경이 잦을 것 같은 로직을 객체로서 응집하여 완전히 분리하는 과정이 필요합니다.
결국 캡슐화는 객체간 의존성(or 결합도)를 낮춰주고 저수준의 객체 변경시에도 고수준 코드에 영향을 주지 않도록 해야하는 것입니다.
또한, 완전한 은닉을 위해선 최대한 응집된 객체 내부의 상태 변경을 통해 서비스를 제공할 수 있도록 해야합니다. 예를 들어 getMyTotal..()을 호출할 때 매개변수로 이름(name 변수)를 제공한다면 이는 매개변수로 하여금 내부 구현을 노출 시킬 수 있으며 보일러플레이트 코드를 생성하는 안 좋은 예시입니다.
질문과 수정 요청은 언제나 환영입니다! 감사합니다.
#캡슐화 #OOP #객체지향프로그래밍
중장년층 구인구직 사이트 추천합니다.