느리더라도 꾸준히

[OOP]객체지향 프로그래밍(OOP) - (3) 캡슐화 본문

Java

[OOP]객체지향 프로그래밍(OOP) - (3) 캡슐화

테디규 2022. 11. 17. 00:50

목표

객체 지향적이지 않은 코드를 객체 지향으로 바꾸면서 객체지향의 4가지 특징을 이해한다.

최종적으로 객체 지향적인 코드를 만들어보고, 이를 미리 구현 해 놓은 Springframework를 이해해보자.

(1) 상속

(2) 추상화 및 다형성를 통한 DI(Dependency Injection)

(3) 캡슐화

(4) SRP(Single-Relationship Principle)

정리

캡슐화

  • 객체의 세부적인 동작을 객체 내부로 감추고, 외부로는 객체의 메서드를 사용할 수 있는 최소한의 통로만 열어두는 기법이다.
  • 캡슐화로 객체의 필드 또는 메서드를 정보 은닉 시킬 수 있다. 정보 은닉시 결합도를 낮추어(객체 간 의 관계를 최소화) 객체마다 응집도(자율성)이 높은 코드를 작성시켜준다.
  • 이는 OOP의 철학인 유연하고 재사용성이 높은 코드를 작성할 수 있도록 만들어 준다.

본문

아직 남은 문제

추상화와 다형성을 이용하여 변경과 확장에 유연한 코드를 작성했지만, 아직 문제가 남아있습니다.

MartService 할인 서비스 코드를 봅시다.

public class MartService {
    private Customer[] customers;
    int originalPrice;
    // 이걸 배열대신에 Map형식으로 하면, 이름값으로 원하는 할인조건을 적용할수 있을듯하다.
    HashMap<String, DiscountCondition> discountConditionHashMap;

    public MartService(Customer[] customers, int originalPrice
            , HashMap<String, DiscountCondition> discountConditionHashMap) { // 추가
        this.customers = customers;
        this.originalPrice = originalPrice;
        this.discountConditionHashMap = discountConditionHashMap;
    }

    HashMap<Customer, Integer> discountedHashMap = new HashMap<>();

    // 주요 로직 : 고객마다의 할인 요금을 구해서 출력해주자!!
    public void service(){
        // 각 고객마다 할인을 적용해본다.
        DiscountAllCustomers();

        // 고객마다의 할인 요금 출력하기
        printDiscountedPrice();
    }

    private void DiscountAllCustomers() {
        for(Customer customer : customers){
            String customerType = customer.getCustomerType();
            if(customerType == "일반") discountedHashMap.put(customer, originalPrice);
            else {
                discountedHashMap.put(customer,
                        discountConditionHashMap.get(customerType).applyDiscountPolicy(originalPrice));
            }
        }
    }

    private void printDiscountedPrice() {
        System.out.println("할인 요금을 출력합니다.");
        Set<Customer> keySet = discountedHashMap.keySet();
        Iterator<Customer> iter = keySet.iterator();
        while(iter.hasNext()){
            Customer customer = iter.next();
            System.out.printf("고객이름 : %s , 할인된요금 : %d\n",
                    customer.getName(),
                    discountedHashMap.get(customer));
        }
    }
}
    private void DiscountAllCustomers() {
        for(Customer customer : customers){
            String customerType = customer.getCustomerType();
            if(customerType == "일반") discountedHashMap.put(customer, originalPrice);
            else {
// 할인조건에 알맞은 할인 정책을 적용시키는 로직을 서비스 로직에서 노출하고 있다.
                discountedHashMap.put(customer,
                        discountConditionHashMap.get(customerType).applyDiscountPolicy(originalPrice));
            }
        }
    }

객체 지향 프로그래밍에서 객체는 자신이 보유한 데이터와 관련된 기능을 자율적으로 처리할 수 있어야 하며, 그러면서도 다른 객체와 유기적인 협력 관계에 참여할 수 있어야 합니다.( 이를 응집도가 높고 결합도가 낮다고 표현합니다.)

MartService 객체는 고객에게 적합한 핸드폰 할인 가를 적용하고 이를 출력해주는 데이터와 기능을 가지고 있으면 됩니다. 그런데 메서드 DiscountAllCustomers() 안쪽을 보면 할인 조건을 value로 가지는 Map을 가지고 있습니다. 그러므로 할인 조건과 할인정 책을 결정하는 기능을 가지고 있다고 말할 수 있습니다.

다시 말해 할인 조건(DiscountCondition)에 알맞은 할인 정책(DiscontPolicy)을 적용 시키는 로직을 서비스 로직에서 노출하고 있습니다.(기능인 메서드들을 노출하고 있다.)

이렇게 로직을 노출하는 경우, 객체간 정보 은닉이 제대로 이루어 지지 않으므로 자율적이지 못하게되며 유연하고 재사용성이 높은 코드를 작성할 수 없습니다.

캡슐화를 적용시켜 보자.

캡슐화란?

객체의 세부적인 동작을 객체 내부로 감추고, 외부로는 객체의 메서드를 사용할 수 있는 최소한의 통로만 열어두는 기법입니다.

이전에 객체 필드를 private로 설정하고 getter/setter로 객체의 필드를 숨기는 것도 캡슐화지만, 메서드 로직을 숨겨 메서드를 사용하는 객체가 메서드 로직을 모르도록 만드는 방식도 캡슐화라고 할 수 있습니다.

더 자세한 내용은 캡슐화에대해정확히알아보자.를 참고합시다.

Discount 클래스

public class Discount {
    private int originalPrice;
    private HashMap<String, DiscountCondition> discountConditionHashMap;

    public Discount(int originalPrice, HashMap<String, DiscountCondition> discountConditionHashMap) {
        this.originalPrice = originalPrice;
        this.discountConditionHashMap = discountConditionHashMap;
    }

    public int discount(String key, int originalPrice){
        return discountConditionHashMap.get(key).applyDiscountPolicy(originalPrice);
    }
}

할인 조건들을 필드로 가지며, 할인 조건을 선택하여 주어진 할인 정책을 가져오는 기능을 가지는 클래스 Discount를 생성합니다.

MartService 에서 로직노출이 되는 코드

discountConditionHashMap.get(customerType).applyDiscountPolicy(originalPrice));

위 로직은 여러 할인 조건들 중에서 customerType에 맞는 할인 조건을 선택하고, 그 조건에 정해져있는 할인 정책을 적용 시켜 할인 액을 return 해오는 로직입니다.

그러므로 Discount 객체에는 여러 할인 조건들 중 customerType에 맞는 조건을 가져오도록 key : (”고객타입”), value : 할인 조건을 담은 HashMap을 필드로 가져옵니다.

또한 MartService에 존재하는 customerType, originalPrice 두 가지 값을 인자로 필요로 했으므로, 위 두 가지 변수를 매개변수로 사용하는 메서드를 생성해줍시다. 그리고 최종적으로 할인액이 나올수 있도록 코드를 작성해 줍니다.

캡슐화가 적용된 MartService 클래스

public class MartService {
    private Customer[] customers;
    int originalPrice;
    Discount discountClass; // 추가!

    public MartService(Customer[] customers, int originalPrice
            , Discount discountClass) { // 추가
        this.customers = customers;
        this.originalPrice = originalPrice;
        this.discountClass = discountClass;
    }
...

    private void DiscountAllCustomers() {
        for(Customer customer : customers){
            String customerType = customer.getCustomerType();
            if(customerType == "일반") discountedHashMap.put(customer, originalPrice);
            else {
                discountedHashMap.put(customer,
                        discountClass.discount(customerType, originalPrice));
                                                // 캡슐화 적용!
            }
        }
    }
....

이제 HashMap 대신 Discount 객체를 필드로 사용하고 있습니다. 또한 DiscountAllCustomers() 안쪽에 더 이상 DiscountCondition의 구현체와 할인 조건을 선택하는 로직이 보이지 않습니다.

이렇게 메서드 정보은닉을 통해 객체간의 결합도를 최소한으로 만들어 주는 방식을 캡슐화라고 합니다.

Discount를 필드로 사용하기로 했으니, 이를 주입시켜주도록 생성자와 MartService 객체를 사용하는 MartApp를 수정해줍시다.

Discount 객체를 생성하고 주입시켜주는 MartApp 클래스

public class MartApp {
    // 고객정보
    CustomerRepository customerRepository = new CustomerRepository();
    // ------------ 추가
    Discount discountClass = new Discount(new HashMap<String, DiscountCondition>(){{
        put("학생",new StudentDiscountCondition(new RatePolicy(10)));
        put("직원",new EmployeeDiscountCondition(new AmountPolicy(10000)));
    }});
    Customer[] customers = customerRepository.findAll();
    int originalPrice = new PhoneInfo(1000000, "iphone").getPrice();
    // ----------------
    MartService martService
            = new MartService(customers, originalPrice, discountClass);

    public void start(){
        // 할인 로직 시작
        martService.service();
    }
}
Comments