본문 바로가기

OBJECT

오브젝트 챕터7<객체 분해>

 

기능 관점에서 하나의 시스템을 구현 가능한 수준으로 분해하는 예시가 소개되었습니다.

 

비록 이 방식이 정답은 아니지만, 설계 관점에서 인사이트를 얻었습니다.

 

// 시스템에 대한 추상적인 최상위 문장을 기술.
직원의 급여를 계산한다.

-----
// 최상위 문장을 다음과 같이 좀 더 세부적 절차로 구체화.
직원의 급여를 계산한다.
  사용자로부터 소득세율을 입력받는다.
  직원의 급여를 계산한다.
  양식에 맞게 결과를 출력한다.

-----
// 프로그램 언어로 구현 가능한 정도까지 기능을 분해.
직원의 급여를 계산한다.
  사용자로부터 소득세율을 입력받는다.
     "세율을 입력하세요:" 라는 문장을 화면에 출력한다.
     키보드를 통해 세율을 입력받는다
  직원의 급여를 계산한다.
      전역 변수에 저장된 직원의 기본급 정보를 얻는다
      급여를 계산한다.
  양식에 맞게 결과를 출력한다.
      "이름: {직원명}, 급여: {계산된 금액}" 형식에 따라 출력 문자열을 생성한다.

 


프로시저 추상화와 데이터 추상화

이해한 부분을 토대로 구성해본 다이어그램입니다.

 

 


 

하향식(Top-Down)방식의 문제

하향식 설계와 관련된 모든 문제의 원인은 결합도로 귀결됩니다.

 

  1. 시스템은 하나의 메인 함수로 구성돼 있지 않다.
    • 유지보수하며 기능의 가감이 이루어지기 때문에.
  2. 기능 추가나 요구사항 변경으로 인해 메인함수를 빈번하게 수정해야 한다.
    • 새로운 기능을 추가할 대마다 매번 메인 함수를 수정해야 한다.
  3. 비즈니스 로직이 사용자 인터페이스와 강하게 결합된다.
    • 설계 초기 단계부터 입력 IO양식을 함께 고민하도록 강요한다. 결과적으로 비즈니스 로직과 UI로직이 밀접하게 결합된다.
  4. 하향식 분해는 너무 이른 시기에 함수들의 실행 순서를 고정시키기 때문에 유연성과 재사용성이 저하된다.
    • 설계 시작시점부터 시스템이 무엇(what)(여기서 무엇을 역할이라고 이해했습니다.)을 해야하는지가 아니라 어떻게 동작해야 하는지에 집중하게 만든다.
  5. 데이터 형식이 변경될 경우 파급효과를 예측할 수 없다.
    • 어떤 함수가 어떤 데이터를 사용하고 있는지 추적이 어렵다. 따라서 데이터 변경으로 어떤 함수가 영향을 받을지 예상하기 어렵다.

 

 

 

 


 

 

 

시스템의 변경을 관리하는 기본적인 전략

 

변경의 방향에 맞춰 시스템을 분해한다.

 

즉, 함께 변경되는 부분을 하나의 구현단위로 묶고 퍼블릭 인터페이스를 통해서만 접근하도록 만드는 것이다.

 

 

 


 

 

모듈에 대한 요구사항

 

낮은 복잡성, 노출되지 않은 변경 가능성.

 

 

모듈의 장점

  • 모듈 내부의 변수가 변경되더라도 모듈 내부에만 영향을 미친다.
  • 비즈니스 로직과 사용자 인터페이스에 대한 관심사를 분리한다.
  • 전역 변수와 전역 함수를 제거함으로써 네임스페이스 오염을 방지한다.

 

 

저자는 이 파트에서 모듈의 핵심은 데이터라고 했습니다.

 

"모듈은 기능이 아니라 데이터를 중심으로 시스템을 분해하는 것" 이라고 했는데요, 여기서 데이터 중심 설계와 혼동해서는 안됩니다.

 

"모듈의 핵심은 데이터다."그런데 객체지향 설계에서는 "데이터 중심의 설계를 지양해야 한다."고 하니, 이 두 가지가 서로 모순처럼 느껴질 수 있습니다.

 

하지만 여기서 말하는 데이터가 핵심이라는 것은 단순히 데이터를 모아놓으라는 의미가 아니라, 데이터를 잘 캡슐화 하라는 것입니다.

 

두 개념을 정확히 구분해야합니다.

 

 

 


 

 

프로시저 추상화의 한정된 표현력을 향상시키기 위해 데이터 추상화 도입.

 

하지만 데이터 추상화를 위해서는 특정 기능을 지원하는 프로그래밍 언어가 필요하다.

 

 

 

 

저자는 다형성에 대해서 아래와 같이 정의합니다.

 

클라이언트의 관점에서 두 클래스의 인스턴스는 동일하게 보이지만, 동일한 메시지에 대해 서로 다르게 반응하는 것.

 

 

 

 

OCP(Open-Closed Principle)의 개념이 등장합니다.

 

유지보수의 효율을 높이기 위해 등장한 특성으로, 

 

확장에는 열려있고, 변경에는 닫혀있어야 한다는 개념입니다.

 

쉽게 말하면 새로운 기능을 추가할 때 기존 코드를 건드리지 않고 새로운 기능을 추가할 수 있어야 한다는 것입니다.

 

 

 


 

이번 챕터의 마지막에 저자는 객체지향적 접근법이 모든 경우에서의 정답은 아니라고 했습니다.

 

우리는 타입 추가와 오퍼레이션 추가 중 어느 것이 더 중요한지 선택하고 그에 따라 적합한 설계 방식을 선택해야 합니다.

 

타입 추가가 중요한 경우 객체지향 설계를 선택하고, 오퍼레이션 추가가 중요한 경우 추상 데이터 타입을 선택하는 것이 설계에서 합리적인 접근 방식입니다.


 

 

Java로 변환한 예시 코드입니다.

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("Enter operation (pay/basePays): ");
        String operation = scanner.nextLine();

        switch (operation) {
            case "pay":
                System.out.print("Enter name: ");
                String name = scanner.nextLine();
                double taxRate = getTaxRate(scanner);
                double pay = Employees.calculatePay(name, taxRate);
                System.out.println(describeResult(name, pay));
                break;
            case "basePays":
                System.out.println(Employees.sumOfBasePays());
                break;
            default:
                System.out.println("Invalid operation.");
        }
    }

    private static double getTaxRate(Scanner scanner) {
        System.out.print("Enter tax rate: ");
        return Double.parseDouble(scanner.nextLine());
    }

    private static String describeResult(String name, double pay) {
        return "Name: " + name + ", Pay: " + pay;
    }
}
import java.util.*;

class Employees {
    private static final String[] employees = {"employeeA", "employeeB", "employeeC", "partTimeD", "partTimeE", "partTimeF"};
    private static final double[] basePays = {400, 300, 250, 1, 1, 1.5};
    private static final boolean[] hourlys = {false, false, false, true, true, true};
    private static final int[] timeCards = {0, 0, 0, 120, 120, 120};

    public static double calculatePay(String name, double taxRate) {
        if (isHourly(name)) {
            return calculateHourlyPayFor(name, taxRate);
        } else {
            return calculatePayFor(name, taxRate);
        }
    }

    private static boolean isHourly(String name) {
        int index = Arrays.asList(employees).indexOf(name);
        return hourlys[index];
    }

    private static double calculateHourlyPayFor(String name, double taxRate) {
        int index = Arrays.asList(employees).indexOf(name);
        double basePay = basePays[index] * timeCards[index];
        return basePay - (basePay * taxRate);
    }

    private static double calculatePayFor(String name, double taxRate) {
        int index = Arrays.asList(employees).indexOf(name);
        double basePay = basePays[index];
        return basePay - (basePay * taxRate);
    }

    public static double sumOfBasePays() {
        double result = 0;
        for (int i = 0; i < employees.length; i++) {
            if (!hourlys[i]) {
                result += basePays[i];
            }
        }
        return result;
    }
}