CS

객체의 응집도 정리

Ark B 2025. 4. 15. 19:05

우연적 응집

  • 정의 :
    모듈 내의 구성 요소들이 아무런 관련 없이 단순히 하나의 모듈에 모여 있는 경우입니다. 각 기능들 사이에
    논리적, 기능적 연관성이 전혀 없으며, 단지 우연히 함께 모여 있을 뿐이다.
  • 특징:
    • 가장 낮은 응집도 단계
  • 예시 :
    하나의 유틸리티 클래스에 파일 입출력, 숫자 계산, 문자열 포맷팅 등 서로 관련 없는 기능을 모두 넣은 경우
public class Miscellaneous {
    public void calculateSum(int a, int b) {
        System.out.println("Sum: " + (a + b));
    }
    
    public void printMessage(String message) {
        System.out.println("Message: " + message);
    }
    
    public void saveFile(String filename) {
        // 파일 저장 로직 (전혀 관련없는 기능)
    }
}

논리적 응집

  • 정의:
    논리적으로 관련 있다고 판단되는 기능들을 하나의 모듈(또는 클래스)로 묶은 경우이다.
  • 특징:
    기능들이 개념적으로 관련되어 있어 한 모듈에 모여 있으나, 호출 시점이나 조건에 따라 서로 다른 기능이 실행됨.
    클라이언트가 어떤 기능을 선택할지는 매개변수나 조건에 따라 결정됨.
  • public interface FileOperations {
        void open(String filename);
        void close(String filename);
        void delete(String filename);
    }
    
    public class FileManager implements FileOperations {
        @Override
        public void open(String filename) {
            System.out.println("Opening file: " + filename);
        }
    
        @Override
        public void close(String filename) {
            System.out.println("Closing file: " + filename);
        }
    
        @Override
        public void delete(String filename) {
            System.out.println("Deleting file: " + filename);
        }
    }
     파일과 관련된 여러 작업을 하나의 인터페이스로 묶었지만, 각 메서드는 호출되는 시점이나 목적이 다를 수 있으므로
    논리적 응집을 나타낸다.

시간적 응집

특징:

  • 시간이나 시점에 따라 함께 실행되어야 하는 작업들이 한 모듈에 묶임.
  • 작업들이 서로 기능적으로 관련이 없더라도, "같은 시점(예: 시작 시, 종료 시)"에 실행된다는 공통점으로 묶임

예시:
프로그램 시작 시에 실행되는 초기화 작업들을 한데 모은 클래스

public class Initializer {
    public void initializeAll() {
        loadConfiguration();
        initDatabase();
        setupLogging();
    }
    
    private void loadConfiguration() {
        // 설정 로딩
        System.out.println("Configuration loaded.");
    }
    
    private void initDatabase() {
        // 데이터베이스 연결 초기화
        System.out.println("Database initialized.");
    }
    
    private void setupLogging() {
        // 로깅 시스템 설정
        System.out.println("Logging setup complete.");
    }
}

 

절차적 응집

특징:

  • 모듈 내의 작업들이 특정한 절차(순서)에 따라 실행되어야 하는 경우
  • 각 작업들이 순서대로 실행되지만, 반드시 출력-입력 관계로 연계되지는 않을 수 있음
  • "절차에 따른 순서"라는 공통점 때문에 묶이는 경우.

예시:

주문 처리 시 단계별로 작업을 수행하는 클래스

public class OrderProcessor {
    public void processOrder(Order order) {
        validateOrder(order);
        reserveInventory(order);
        confirmPayment(order);
        shipOrder(order);
    }
    
    private void validateOrder(Order order) {
        // 주문 유효성 검사
    }
    
    private void reserveInventory(Order order) {
        // 재고 예약
    }
    
    private void confirmPayment(Order order) {
        // 결제 확인
    }
    
    private void shipOrder(Order order) {
        // 주문 발송 처리
    }
}

 

교환적 응집

특징:
모듈 내의 여러 작업이 같은 데이터 집합이나 같은 정보에 대해 작동함.

여러 작업이 동일한 입력(예: 하나의 객체나 데이터 구조)을 공유하여 , 관련 데이터에 대해 여러 처리를 수행함

public class CustomerDataHandler {
    private Customer customer;

    public CustomerDataHandler(Customer customer) {
        this.customer = customer;
    }
    
    public void validate() {
        // customer 정보의 유효성 검사
        if (customer.getName() == null || customer.getName().isEmpty()) {
            throw new IllegalArgumentException("Invalid customer name");
        }
    }
    
    public void process() {
        // customer 데이터를 가공
        customer.setProcessed(true);
    }
    
    public void save() {
        // customer 정보를 데이터베이스에 저장
        System.out.println("Customer " + customer.getName() + " saved.");
    }
}

 

 

순차적 응집

모듈 내의 각 구성 요소가 순차적으로 실행되며, 한 단계의 출력이 다음 단계의 입력으로 사용됨.

전체 프로세스가 단계별 데이터 흐름을 따라 진행됨.

 

예시:

데이터를 읽고 처리한 후 결과를 기록하는 일련의 작

public class DataPipeline {
    // 순차적으로 수행: 읽기 → 처리 → 기록
    public void executePipeline() {
        String data = readData();
        String processed = processData(data);
        writeData(processed);
    }
    
    private String readData() {
        return "raw data";
    }
    
    private String processData(String input) {
        return input.trim().toUpperCase();
    }
    
    private void writeData(String output) {
        System.out.println("Output: " + output);
    }
}

기능적 응집 (Functional Cohesion)

특징:

  • 모듈 내의 모든 구성 요소가 단 하나의 명확한 기능을 수행하도록 완벽하게 집중되어 있음.
  • 입력을 받아서 하나의 결과를 도출하는 데 필요한 모든 작업이 협력함.

예시:
두 숫자의 합을 계산하는 단일 작업만 수행하는 클래스

public class SumCalculator { 
	public int calculateSum(int a, int b) {
    	return a + b; 
    } 
}
 

 

 

아래는 사용자의 입력(예: 연산 기호 및 피연산자)을 받아서 연산을 동적으로 선택하고 실행하는 설계 예시입니다. 이 예시에서는 앞서 작성한 Operation 인터페이스와 개별 연산 클래스(Addition, Subtraction, Multiplication, Division)를 사용하며, 운영체제에 따라 사용할 연산을 결정하기 위해 팩토리 패턴을 적용합니다.


1. Operation 인터페이스 및 구체 연산 클래스

(이전 예시와 동일)

Operation.java

public interface Operation {
    double calculate(double a, double b);
}

Addition.java

public class Addition implements Operation {
    @Override
    public double calculate(double a, double b) {
        return a + b;
    }
}

Subtraction.java

public class Subtraction implements Operation {
    @Override
    public double calculate(double a, double b) {
        return a - b;
    }
}

Multiplication.java

public class Multiplication implements Operation {
    @Override
    public double calculate(double a, double b) {
        return a * b;
    }
}

Division.java

public class Division implements Operation {
    @Override
    public double calculate(double a, double b) {
        if(b == 0) {
            throw new IllegalArgumentException("Division by zero is not allowed.");
        }
        return a / b;
    }
}

2. OperationFactory

사용자로부터 입력 받은 연산 기호에 따라 적절한 Operation 구현 객체를 생성하는 팩토리 클래스입니다.

OperationFactory.java

public class OperationFactory {
    public static Operation getOperation(char operator) {
        switch(operator) {
            case '+':
                return new Addition();
            case '-':
                return new Subtraction();
            case '*':
                return new Multiplication();
            case '/':
                return new Division();
            default:
                throw new IllegalArgumentException("Invalid operator: " + operator);
        }
    }
}

3. Calculator 클래스

Calculator 클래스는 Operation 인터페이스에 의존하며, 외부에서 주입받은 연산 객체를 이용해 계산을 수행합니다.

Calculator.java

public class Calculator {
    private Operation operation;

    public Calculator(Operation operation) {
        this.operation = operation;
    }
    
    public double execute(double a, double b) {
        return operation.calculate(a, b);
    }
    
    public void setOperation(Operation operation) {
        this.operation = operation;
    }
}

4. 사용자 입력에 따른 동적 연산 선택 – 클라이언트 코드

사용자로부터 피연산자와 연산 기호를 입력받아, OperationFactory를 통해 해당 연산 객체를 생성한 후 Calculator에 주입하여 계산을 실행합니다.

CalculatorDemo.java

import java.util.Scanner;

public class CalculatorDemo {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // 첫 번째 피연산자 입력
        System.out.print("Enter first number: ");
        double a = scanner.nextDouble();
        
        // 연산 기호 입력: +, -, *, /
        System.out.print("Enter operator (+, -, *, /): ");
        char operator = scanner.next().charAt(0);
        
        // 두 번째 피연산자 입력
        System.out.print("Enter second number: ");
        double b = scanner.nextDouble();
        
        // 팩토리에서 연산 객체 생성
        Operation op = OperationFactory.getOperation(operator);
        
        // Calculator 객체 생성 및 연산 수행
        Calculator calc = new Calculator(op);
        double result = calc.execute(a, b);
        
        System.out.println(a + " " + operator + " " + b + " = " + result);
        scanner.close();
    }
}

설계 포인트 정리

  1. 낮은 결합도:
    • Calculator 클래스는 Operation 인터페이스에만 의존하며, 구체적인 연산 클래스에 직접 결합되지 않습니다.
    • OperationFactory를 통해 외부에서 적절한 구현체를 동적으로 생성하므로, 코드 변경 없이 새로운 연산을 추가할 수 있습니다.
  2. 높은 응집도:
    • 각 연산 클래스는 단일 책임(두 숫자에 대한 계산)만 수행합니다.
    • Calculator 클래스는 연산 수행이라는 역할에 집중되어 있고, 사용자 입력 처리와 분리되어 있습니다.
  3. 확장성:
    • 새 연산(예: 거듭제곱, 루트 계산 등)을 추가할 때, Operation 인터페이스를 구현하는 새로운 클래스를 만들고, OperationFactory에 케이스를 추가하면 됩니다.
    • 기존 Calculator 코드는 수정할 필요 없이 새로운 연산을 지원할 수 있습니다.

이와 같이 사용자 입력에 따라 연산을 교체하는 시나리오를 구현하면, SOLID 원칙에 부합하는 유연하고 확장 가능한 계산기 시스템을 만들 수 있습니다.