객체의 응집도 정리
우연적 응집
- 정의 :
모듈 내의 구성 요소들이 아무런 관련 없이 단순히 하나의 모듈에 모여 있는 경우입니다. 각 기능들 사이에
논리적, 기능적 연관성이 전혀 없으며, 단지 우연히 함께 모여 있을 뿐이다. - 특징:
- 가장 낮은 응집도 단계
- 예시 :
하나의 유틸리티 클래스에 파일 입출력, 숫자 계산, 문자열 포맷팅 등 서로 관련 없는 기능을 모두 넣은 경우
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();
}
}
설계 포인트 정리
- 낮은 결합도:
- Calculator 클래스는 Operation 인터페이스에만 의존하며, 구체적인 연산 클래스에 직접 결합되지 않습니다.
- OperationFactory를 통해 외부에서 적절한 구현체를 동적으로 생성하므로, 코드 변경 없이 새로운 연산을 추가할 수 있습니다.
- 높은 응집도:
- 각 연산 클래스는 단일 책임(두 숫자에 대한 계산)만 수행합니다.
- Calculator 클래스는 연산 수행이라는 역할에 집중되어 있고, 사용자 입력 처리와 분리되어 있습니다.
- 확장성:
- 새 연산(예: 거듭제곱, 루트 계산 등)을 추가할 때, Operation 인터페이스를 구현하는 새로운 클래스를 만들고, OperationFactory에 케이스를 추가하면 됩니다.
- 기존 Calculator 코드는 수정할 필요 없이 새로운 연산을 지원할 수 있습니다.
이와 같이 사용자 입력에 따라 연산을 교체하는 시나리오를 구현하면, SOLID 원칙에 부합하는 유연하고 확장 가능한 계산기 시스템을 만들 수 있습니다.