[개발자_객체지향_디자인패턴] 캡슐화

박상준·2024년 8월 10일
0

캡슐화?

  • OOP 핵심 원칙중에 하나이다.
  • 객체의 내부 구현을 외부로부터 숨기는 기법이다.
  • 객체 지향 프로그래밍은 코드의 유지보수성 + 재사용성을 향상시킨다.

캡슐화 정의와 목적

  • 정의
    • 캡슐화(encapdulation)
      • 객체가 내부적으로 기능을 어떻게 하는지 감춤
  • 왜?
    • 내부 구현 변경의 유연함을 제공하기 위함이다..

캡슐화의 장점

  1. 변경의 국소화
    1. 한 곳의 구현 변경이 다른 곳에 미치는 영향을 최소화함.
  2. 수정 용이
    1. 프로그램의 수정을 용이하게함.
  3. 내부 구현 보호
    1. 객체의 내부 구현을 외부로부터 격리함으로 보호가능!

절차 지향 VS OOP 방식

절차 지향

  • 데이터와 해당 데이터를 사용하는 로직이 분리되어 있다.
  • 데이터 구조가 변경되는 경우 여러 곳의 코드를 수정해야 하는 문제를 야기할 수 있음.
  • 예시
    public class Member {
        // 다른 데이터
        private Date expiryDate;
        private boolean male;
    
        public Date getExpiryDate() {
            return expiryDate;
        }
    
        public boolean isMale() {
            return male;
        }
    }
    
    // 만료 여부를 확인하는 코드 (여러 곳에서 사용됨)
    if (member.getExpiryDate() != null && member.getExpiryDate().getTime() < System.currentTimeMillis()) {
        // 만료되었을 때의 처리
    }
    
    // 정책 변경 후 (여성 회원은 만료 후 30일 추가 제공)
    long day30 = 1000L * 60 * 60 * 24 * 30; // 30일을 밀리초로 변환
    if ((!member.isMale() && member.getExpiryDate() != null && 
         member.getExpiryDate().getTime() < System.currentTimeMillis() - day30) ||
        (member.isMale() && member.getExpiryDate() != null && 
         member.getExpiryDate().getTime() < System.currentTimeMillis())) {
        // 만료되었을 때의 처리
    }
    1. 만료 여부 확인 로직이 여러 곳에 중복된다.
    2. 정책 변경 시 모든 관련 코드를 찾아서 수정해야 한다.
    3. 코드가 복잡해지고 가독성이 떨어진다.

객체지향 방식

  • 데이터와 해당 데이터를 사용하는 로직을 하나의 객체 안에 묶는다.
  • 내부 구현 변경이 외부에 미치는 영향 최소화 가능
    public class Member {
        private Date expiryDate;
        private boolean male;
    
        public boolean isExpired() {
            return expiryDate != null && expiryDate.getTime() < System.currentTimeMillis();
        }
    
        // 정책 변경 후
        public boolean isExpired() {
            if (male) {
                return expiryDate != null && expiryDate.getTime() < System.currentTimeMillis();
            } else {
                long day30 = 1000L * 60 * 60 * 24 * 30; // 30일을 밀리초로 변환
                return expiryDate != null && expiryDate.getTime() < System.currentTimeMillis() - day30;
            }
        }
    }
    ---
    // 사용 코드
    if (member.isExpired()) {
        // 만료되었을 때의 처리
    }
    • 사용코드 변경이 필요없이, 변경의 영향이 최소화된다.
    • 코드의 가독성과 유지보수성이 향상된다.

캡슐화를 효과적으로 구현하기 위한 2 가지 규칙

첫번째..

1. Tell,Don`t Ask

  • 정의
    • 객체에게 데이터를 요청하지 말고, 대신 작업을 수행하도록 지시하라는 원칙임.
  • 절차 지향적 방식을 ASK 라고 합니다.
    // 잘못된 예: 데이터를 가져와서 직접 처리
    if (member.getExpiryDate() != null && member.getExpiryDate().getTime() < System.currentTimeMillis()) {
        // 만료 처리
    }
    • 해당 코드에는 문제점이 3가지 있다.
      1. 객체 내부 구현인 ( 만료 일자 ) 에 대해 외부에서 인지해야한다.
      2. 만료 확인 로직이 어디들어갈지 기억해야한다.. 로직이 중복될 수 있음.
      3. 만료 정책이 변경되면 모든 사용 코드를 수정해야 한다.
  • 객체 지향적 방식을 Tell 이라고 합니다.
    // 올바른 예: 객체에게 작업 수행을 요청
    if (member.isExpired()) {
        // 만료 처리
    }
    • 해당 코드는 장점이 있습니다.
      1. 객체 내부 구현을 숨기고
      2. 만료 확인 로직이 Member 클래스에 캡슐화 되며
      3. 만료 정책이 변경되어도 Member 클래스만 수정하면 됩니다.

2. 데미테르의 법칙 ( Law of Demeter )

  • 정의
    • 최소 지식 원칙 이라고 불리는 데, 객체가 다른 객체의 내부 구조에 대해 가능한 적게 알아야 함.
  • 규칙
    1. 객체는 자신의 직접적인 구성요소의 메서드만 호출해야함
    2. 메서드에 전달된 매개변수의 메서드만 호출해야 한다
    3. 메서드 내에서 생성한 객체의 메서드만 호출해야 함
    4. 객체의 직접적인 구성요소인 객체의 메서드만 호출해야 함
// 데미테르의 법칙을 위반하는 코드
public class Order {
    public Customer getCustomer() {
        return customer;
    }
}

public class Customer {
    public Address getAddress() {
        return address;
    }
}

public class Address {
    public String getStreet() {
        return street;
    }
}

// 사용 코드 (법칙 위반)
String street = order.getCustomer().getAddress().getStreet();
  • 해당 코드는 Order 객체의 내부 구조에 대해 너무 많이 알고 있음..
  • 데미테르의 법칙을 지키기 위해. 각 객체가 자신의 직접적인 관계만을 다루도록 수정해야 한다.
// 데미테르의 법칙을 준수하는 코드
public class Order {
    public String getCustomerStreet() {
        return customer.getStreet();
    }
}

public class Customer {
    public String getStreet() {
        return address.getStreet();
    }
}

// 사용 코드 (법칙 준수)
String street = order.getCustomerStreet();

그럼 2가지 규칙 적용 후의 효과

  1. 캡슐화 UP
  2. 유지보수 UP
  3. 코드 재사용성 UP
  4. 테스트 용이성 UP
    • 객체 간의 의존성이 낮아져서 단위 테스트가 더 쉬워짐.

다만..

  • 맹목적으로 지켜야하는건 아닙니다.
  • 과도하게 해당 법칙을 지키다보면, 되려 너무 많은 메서드가 생겨나서 코드가 읽기 어려워질 수도 있음.

신문 배달부와 지갑

  • 데미테르의 법칙에는 유명한 예제가 하나 존재합니다.
  • 신물 배달부와 지갑 이라는 사례인데요
  • 초기 구현의 경우 데미테르의 법칙을 위반한 사례입니다.
    // 고객
    public class Customer {
        private Wallet wallet;
        
        public Wallet getWallet() {
            return wallet;
        }
    }
    
    // 지갑
    public class Wallet {
        private int money;
        
        public int getTotalMoney() {
            return money;
        }
        
        public void subtractMoney(int debit) {
            money -= debit;
        }
    }
    
    ---
    
    사용자 클래스 - 신문 배달부 클래스
    
    // Paperboy 클래스의 코드
    int payment = 10000;
    Wallet wallet = customer.getWallet();
    if (wallet.getTotalMoney() >= payment) {
        wallet.subtractMoney(payment);
    } else {
        // 다음에 요금 받으러 오는 처리
    }
    • 해당 코드에는 문제점이 있는데요

      1. 신문 배달부가 고객의 지갑을 직접 조작한다는점.
      2. 고객의 내부 구현에 의존하는 문제가 있습니다.
      3. 변경이 되는 경우 아주 취약해집니다.
    • 이를 수정하여 데미테르의 법칙을 준수하는 경우

      public class Customer {
          private Wallet wallet;
      
          public int getPayment(int payment) {
              if (wallet == null) {
                  throw new NotEnoughMoneyException();
              }
              if (wallet.getTotalMoney() >= payment) {
                  wallet.subtractMoney(payment);
                  return payment;
              }
              throw new NotEnoughMoneyException();
          }
      }
      
      ---
      
      사용부 - 
      
      // Paperboy 클래스
      int payment = 10000;
      try {
          int paidAmount = customer.getPayment(payment);
      } catch (NotEnoughMoneyException ex) {
          // 다음에 요금 받으러 오는 처리
      }
    • 신문배달부는 본인이 고객 지갑을 뺏어서 상태를 변화시키기 보다

      • 고객에게 얼마를 내라고 요구를 하게 됩니다.
    • 또한 데미테르의 법칙을 위반 증상으로는

      1. 연속된 get 메서드를 호출한 경우

        value = someObject.getA().getB().getValue();
      2. 임시 변수의 get 호출이 과다하게 많은 경우

        ```jsx
        A a = someObject.getA();
        B b = a.getB();
        value = b.getValue();
        ```

        가 있습니다.

    • 데미테르의 법칙 준수하는 경우 이점으로는

      1. 캡슐화를 강화하고

      2. 유지보수성 증대 등이 있습니다

        이는 캡슐화의 장점과 유사합니다.

profile
이전 블로그 : https://oth3410.tistory.com/

0개의 댓글