객체의 결합도 의미와 단계 정리

2025. 2. 9. 15:01·클린 코드

정처기 공부 중 해당 부분은 정리해도 좋을 거 같아 정리해본다.

 

결합도는 모듈과 모듈 간의 의존 정도를 의미한다.

 

 

따라서 좋은 소프트웨어는 결합도는 낮을수록 좋다.

 

세기 종류 내용
약함 자료 결합도
(Data Coupling)
모듈간의 인터페이스로 전달되는 파라미터(데이터)를 통해서만
상호 작용이 일어나는 경우
  스탬프 결합도
(Stamp Coupling)
모듈간의 인터페이스로 배열이나 객체, 자료 구조 등이 전달되는 경우
↓ 제어 결합도
(Control Coupling)
어떤 모듈이 다른 모듈 내부의 논리적인 흐름을 제어하는 제어 요소를 전달하는 경우
  외부 결합도
(External Coupling)
어떤 모듈이 외부에 있는 다른 모듈의 데이터를 참조하는 경우( 데이터, 통신 프로토콜 등)
  공통 결합도
(Common Coupling)
여러 개의 모듈이 하나의 공통 데이터 영역(전역 변수 참조 및 갱신)을 사용하는 경우 
강함 내용 결합도
(Content Coupling)
어떤 모듈 내부에 있는 변수나 기능을 다른 모듈에서 사용하는 경우

 

스탬프 결합도 : 용어의 유래가 도장이 찍힐 때 불필요한 부분까지 함께 찍히는 것을 연상시킨다 라고 한다.

즉, 필요한 데이터만 전달하면 되는데, 전체 데이터 구조(객체, 구조체 등)을 통째로 전달하여 불필요한 정보까지 함께

넘어가는 상황을 비유한 것이다.

 

발생하는 문제점은 다음과 같다.

1. 불필요한 데이터가 함께 전달됨 -> 함수가 사용하지 않는 데이터를 포함할 가능성이 큼.

2. 모듈 간 의존성이 증가 -> 데이터 구조가 변경되면 관련된 모든 함수가 영향을 받음.

3. 유지보수가 어려워짐 -> 불필요한 정보까지 포함된 객체를 넘겨서 어디서 어떻게 쓰이는지 추적하기 어렵다.

 

😔나쁜 예 (스탬프 결합도 발생)

class Student {
    String name;
    int age;
    String studentId;
    String address;
}

class ReportGenerator {
    void generateReport(Student student) {  // ❌ 전체 Student 객체를 전달함
        System.out.println("Generating report for: " + student.name);
    }
}

➡️generateReport() 함수는 student.name만 필요하지만, Student 객체 전체를 넘겨서 불필요한 데이터도 함께 전달됨.

 

😊개선된 예 (스탬프 결합도 해결)

class ReportGenerator {
	void generateReport(String studentName) {
    		System.out.println("Generating report for: " + studentName);
    }
}

💡 필요한 데이터(name)만 전달하여 불필요한 결합을 방지! -> 데이터 결합도 단계로 낮춤.

 

🔍 제어 결합도(Control Coupling)라는 이름의 유래

  • "제어(Control)"라는 단어에서 알 수 있듯이 , 한 모듈이 다른 모듈의 흐름을 직접 통제한다는 의미에서 유래됨.
  • 즉, 어떤 기능을 실행할지를 직접 지시하는 플래그나 상태 변수를 전달하면서 발생하는 결합도.
    • 상위 모듈이 하위 모듈의 상세한 처리 절차를 알고있는 것이므로 정보은닉을 위배하는 결합!

😠문제점

1. 모듈 간 강한 의존성 -> 한 모듈의 변경이 다른 모듈의 코드 수정으로 이어짐.

2. 유지보수 어려움 -> 특정 조건(flag)이 추가되면 여러 곳에서 코드 수정이 필요함.

3. 캡슐화 위반 -> 모듈 내부의 실행 논리를 외부에서 결정하므로 모듈의 자율성이 떨어짐.

 

class ReportGenerator {
	void generateReport(int reportType) {
    	if (reportType == 1) {
        	generatePDFReport();
        } else if (reportType == 2) {
        	generateExcelReport();
        }
    }
    
    void generatePDFReport() {
    	System.out.println("Generating PDF Report");
    }
    
    void generateExcelReport() {
    	System.out.println("Generating Excel Report");
    }
    
}
  • generateReport() 메서드가 reportType이라는 플래그를 통해 어떤 방식으로 보고서를 생성할지 직접 결정함.
  • 즉, 호출하는 모듈이 실행 흐름을 직접 지시하는 행태이므로 제어 결합도가 높음.

🔨 해결 방법 : 다형성(Polymorphism) 활용

전략 패턴(Strategy Pattern)을 적용하여 제어 흐름을 내부로 숨기는 방식

 

interface Report {
	void generate();
}

class PDFReport implements Report {
	public void generate(){
    	System.out.println("Generating PDF Report");
    }
}

class ExcelReport implements Report {
	public void generate() {
    	System.out.println("Generating Excel Report");
    }
}

class ReportGenerator {
	void generateReport(Report report) {
    	report.generate();
    }
}

}

// 사용 예
public class Main {
    public static void main(String[] args) {
        ReportGenerator generator = new ReportGenerator();
        
        Report pdfReport = new PDFReport();
        Report excelReport = new ExcelReport();
        
        generator.generateReport(pdfReport);  // PDF 보고서 생성
        generator.generateReport(excelReport);  // Excel 보고서 생성
    }
}

 

✅ 제어 흐름을 호출하는 쪽이 아닌 내부에서 결정하도록 설계

✅이제 새로운 보고서 타입이 추가되더라도 기존 generateReport() 코드를 수정할 필요 없음!

 

외부 결합도 (External Coupling)

  • 모듈이 외부에 있는 다른 모듈의 데이터를 참조할 때의 결합도
  • 외부의 데이터, 통신 프로토콜 등을 공유할때 발생 (참조할 데이터가 외부 모듈에 위치할 때)
  • 어떤 외부 모듈에서 반환한 값을 다른 모듈에서 참조하는 경우
  • 참조되는 데이터의 범위를 각 모듈에서 제한할 수 있다.

 

외부 결합도 (External Coupling)란? 

- 외부 결합도는 모듈이 외부 요소(파일 , 데이터베이스, 네트워크, API 등)와 직접적으로 결합되어 있는 경우 발생하는 결합도이다.

즉, 모듈이 특정 외부 시스템(DB, 파일, 네트워크, 하드웨어 등)에 강하게 의존하는 상태를 의미한다.

 

🔍 외부 결합도(External Coupling)라는 이름의 유래

  • "외부(External)"라는 단어에서 알 수 있듯이, 내부 모듈 간의 결합이 아니라, 외부 시스템과의 결합을 의미한다.
  • 예를 들어, 어떤 클래스가 특정 데이터베이스(MySQL)나 외부 API(Google API 등)에 직접 연결되면 해당 시스템에 의존적이 되므로 결합도가 높아진다.
  • 이러한 결합도를 낮추는 것이 유지보수성과 확장성을 높이는 데 중요하다.

😠 외부 결합도가 높을 때 발생하는 문제점

1. 외부 시스템 변경 시 내부 코드 수정 필요

  • 특정 데이터베이스(DB)나 API에 종속되면, 변경이 있을 때 코드 수정이 필수적이다

2. 테스트하기 어려움

- 네트워크 DB 등 외부 시스템과 강하게 연결된 경우, 단위 테스트(Unit Test)를 수행하기 어렵다

 

3. 모듈의 독립성 저하

  • 다른 시스템이 장애를 일으키면 , 이 모듈도 정상적으로 동작할 수 없음.
  • 예: API 서버가 다운되면, 해당 기능도 사용할 수 없음.

외부 결합도(External Coupling)란?

**외부 결합도(External Coupling)**는 모듈이 외부 요소(파일, 데이터베이스, 네트워크, API 등)와 직접적으로 결합되어 있는 경우 발생하는 결합도입니다.
즉, 모듈이 특정 외부 시스템(DB, 파일, 네트워크, 하드웨어 등)에 강하게 의존하는 상태를 의미합니다.


🔍 외부 결합도(External Coupling)라는 이름의 유래

  • "외부(External)"라는 단어에서 알 수 있듯이, 내부 모듈 간의 결합이 아니라, 외부 시스템과의 결합을 의미합니다.
  • 예를 들어, 어떤 클래스가 특정 데이터베이스(MySQL)나 외부 API(Google API 등)에 직접 연결되면 해당 시스템에 의존적이 되므로 결합도가 높아집니다.
  • 이러한 결합도를 낮추는 것이 유지보수성과 확장성을 높이는 데 중요합니다.

❌ 외부 결합도가 높을 때 발생하는 문제점

  1. 외부 시스템 변경 시 내부 코드 수정 필요
    • 특정 데이터베이스(DB)나 API에 종속되면, 변경이 있을 때 코드 수정이 필수적임.
    • 예: MySQL에서 PostgreSQL로 변경하면 DB 연결 로직을 전부 수정해야 함.
  2. 테스트하기 어려움
    • 네트워크, DB 등 외부 시스템과 강하게 연결된 경우, 단위 테스트(Unit Test)를 수행하기 어려움.
    • 예: DB가 필요하거나, API 응답이 있어야 테스트가 가능함.
  3. 모듈의 독립성 저하
    • 다른 시스템이 장애를 일으키면, 이 모듈도 정상적으로 동작할 수 없음.
    • 예: API 서버가 다운되면, 해당 기능도 사용할 수 없음.

📖 예제: 외부 결합도가 높은 코드

🔴 나쁜 예 (외부 결합도가 높음)

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

class DatabaseHandler {
    Connection connect() throws SQLException {  // ❌ 특정 DB(MySQL)에 직접 연결
        return DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "user", "pass");
    }
}
  • 문제점:
    • MySQL 데이터베이스에 직접 연결 → DB 변경 시 코드 수정 필요.
    • DB 연결 정보가 하드코딩되어 있어 보안상 위험.
    • DB가 없으면 테스트가 불가능함.

🛠 해결 방법: 외부 결합도 줄이기

1️⃣ 의존성 주입 (Dependency Injection) 사용

  • DB 연결을 직접 하지 않고, 외부에서 주입받도록 수정하여 결합도를 낮춤.
import java.sql.Connection;

class DatabaseHandler {
    private Connection connection;

    public DatabaseHandler(Connection connection) {  // ✅ 외부에서 DB 연결 주입
        this.connection = connection;
    }

    void fetchData() {
        // DB 데이터 가져오기
    }
}

✅ 이제 DB 연결을 변경할 때 코드 수정이 필요 없음!
✅ Mock 객체를 사용하여 단위 테스트가 가능해짐.


2️⃣ 인터페이스를 활용한 추상화

  • 특정 DB(MySQL, PostgreSQL)와 결합되지 않도록 인터페이스를 활용하여 추상화할 수 있음.
interface DatabaseConnection {
    Connection getConnection();
}

class MySQLConnection implements DatabaseConnection {
    public Connection getConnection() {
        return DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "user", "pass");
    }
}

class PostgreSQLConnection implements DatabaseConnection {
    public Connection getConnection() {
        return DriverManager.getConnection("jdbc:postgresql://localhost:5432/mydb", "user", "pass");
    }
}

class DatabaseHandler {
    private DatabaseConnection dbConnection;

    public DatabaseHandler(DatabaseConnection dbConnection) {  // ✅ 인터페이스 활용
        this.dbConnection = dbConnection;
    }

    void fetchData() {
        Connection conn = dbConnection.getConnection();
        // 데이터 가져오기
    }
}

✅ 데이터베이스가 변경되어도 DatabaseHandler 클래스를 수정할 필요 없음!
✅ MySQL → PostgreSQL 변경 시 DatabaseConnection의 구현체만 변경하면 됨.


3️⃣ 외부 API 결합도를 낮추기 (Adapter 패턴 활용)

  • 특정 API(Google, Naver 등)에 직접 의존하지 않도록 Adapter 패턴 적용.

🔴 나쁜 예 (외부 API에 직접 의존)

import java.net.HttpURLConnection;
import java.net.URL;

class WeatherService {
    String getWeather() throws Exception {  // ❌ 특정 API에 직접 의존
        URL url = new URL("https://api.weather.com/v1/location/12345");
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        // API 요청 및 응답 처리...
        return "Sunny";
    }
}
  • 문제점:
    • API URL이 하드코딩되어 있음 → API 변경 시 코드 수정 필요.
    • 특정 API에 종속됨 → 다른 API로 변경하기 어려움.

🟢 개선된 예 (Adapter 패턴 활용)

interface WeatherAPI {
    String getWeather();
}

class WeatherAPIAdapter implements WeatherAPI {
    public String getWeather() {
        // API 호출 및 데이터 변환
        return "Sunny";
    }
}

class WeatherService {
    private WeatherAPI weatherAPI;

    public WeatherService(WeatherAPI weatherAPI) {  // ✅ 외부 API 결합도 낮춤
        this.weatherAPI = weatherAPI;
    }

    String getWeatherInfo() {
        return weatherAPI.getWeather();
    }
}

✅ API가 변경되어도 WeatherService 코드는 수정할 필요 없음!
✅ Mock API를 만들어 테스트 가능.


📌 외부 결합도를 줄이는 방법 정리

방법 설명 적용 예시

의존성 주입(DI) DB 연결을 직접 하지 않고 외부에서 주입받음 new DatabaseHandler(mySQLConnection)
인터페이스 활용 특정 DB/API에 종속되지 않도록 인터페이스 적용 DatabaseConnection interface 사용
Adapter 패턴 적용 특정 API에 의존하지 않도록 어댑터 클래스를 활용 WeatherAPIAdapter 사용
설정 파일 활용 DB URL, API 키 등을 하드코딩하지 않고 설정 파일에서 관리 application.properties 사용

💬 결론

**외부 결합도(External Coupling)**라는 이름은 모듈이 "외부 시스템(파일, DB, API)"과 직접 연결되면서 결합되는 현상에서 유래되었습니다.

  • 외부 결합도가 높으면, 특정 기술에 의존성이 커지고 유지보수가 어려워집니다.
  • 이를 해결하기 위해 의존성 주입(DI), 인터페이스 추상화, Adapter 패턴 등을 적용하여 결합도를 낮춰야 합니다. 🚀

공통 결합도(Common Coupling)란?

**공통 결합도(Common Coupling)**는 여러 모듈이 같은 전역 변수(Global Variable)나 공통 데이터 영역(Shared Data) 를 공유할 때 발생하는 결합도입니다.
즉, 모듈들이 동일한 데이터를 공유하고 수정할 수 있는 경우 공통 결합도가 존재한다고 말합니다.


🔍 공통 결합도(Common Coupling)라는 이름의 유래

  • "공통(Common)"이라는 단어에서 알 수 있듯이, 여러 모듈이 같은 데이터를 공유하는 특징을 가짐.
  • 보통 전역 변수(Global Variable), 공통 데이터베이스 Table, Static 변수, 싱글톤 객체 등이 원인이 됨.

❌ 공통 결합도의 문제점

  1. 데이터를 수정하면 여러 모듈이 영향을 받음
    • 하나의 모듈이 공유 데이터를 변경하면, 다른 모듈에서도 예기치 않은 버그가 발생할 가능성이 높음.
  2. 코드의 가독성과 유지보수가 어려움
    • 데이터를 어디에서 수정했는지 추적하기 어려움.
    • 특히, 여러 모듈이 같은 전역 데이터를 수정하는 경우, 디버깅이 매우 어려워짐.
  3. 모듈의 독립성이 약해짐
    • 하나의 모듈이 변경되면, 해당 데이터를 사용하는 모든 모듈이 영향을 받음.
    • 즉, 모듈 간 의존성이 증가하여 확장성이 낮아짐.

📖 예제: 공통 결합도의 의미가 드러나는 코드

🔴 나쁜 예 (공통 결합도가 높음)

class SharedData {
    static int globalCounter = 0;  // ❌ 전역 변수 (공통 데이터)
}

class ModuleA {
    void incrementCounter() {
        SharedData.globalCounter++;  // ❌ 공유된 데이터 직접 변경
    }
}

class ModuleB {
    void printCounter() {
        System.out.println("Counter: " + SharedData.globalCounter);  // ❌ 공유된 데이터 사용
    }
}

public class Main {
    public static void main(String[] args) {
        ModuleA a = new ModuleA();
        ModuleB b = new ModuleB();

        a.incrementCounter();
        b.printCounter();  // 예측 불가능한 값이 나올 수도 있음
    }
}

❌ 문제점:

  • SharedData.globalCounter는 여러 모듈(ModuleA, ModuleB)이 직접 수정 가능.
  • 데이터 변경이 어디서 이루어지는지 추적하기 어려움.
  • 멀티스레드 환경에서는 동기화 이슈도 발생할 수 있음.

🛠 공통 결합도를 줄이는 방법

1️⃣ 캡슐화(Encapsulation) 적용

  • 공통 데이터의 직접 접근을 제한하고, Getter/Setter 메서드로 제어.

🟢 개선된 예 (캡슐화 적용)

class SharedData {
    private int globalCounter = 0;  // ✅ 직접 접근 차단

    public synchronized void incrementCounter() {  // ✅ 동기화 적용
        globalCounter++;
    }

    public int getCounter() {
        return globalCounter;
    }
}

class ModuleA {
    private SharedData data;

    public ModuleA(SharedData data) {
        this.data = data;
    }

    void incrementCounter() {
        data.incrementCounter();
    }
}

class ModuleB {
    private SharedData data;

    public ModuleB(SharedData data) {
        this.data = data;
    }

    void printCounter() {
        System.out.println("Counter: " + data.getCounter());
    }
}

✅ 이제 globalCounter는 직접 수정되지 않고 SharedData 내부에서만 관리됨.
✅ 동기화를 적용하여 멀티스레드 환경에서도 안전함.
✅ 모듈 간 의존성을 줄여 유지보수성이 향상됨.


2️⃣ 의존성 주입(Dependency Injection, DI) 사용

  • 공통 데이터를 직접 공유하지 않고, 필요한 모듈에 주입하여 사용.

🟢 개선된 예 (DI 적용)

class CounterService {
    private int counter = 0;

    public void increment() {
        counter++;
    }

    public int getCounter() {
        return counter;
    }
}

class ModuleA {
    private CounterService counterService;

    public ModuleA(CounterService counterService) {
        this.counterService = counterService;
    }

    void incrementCounter() {
        counterService.increment();
    }
}

class ModuleB {
    private CounterService counterService;

    public ModuleB(CounterService counterService) {
        this.counterService = counterService;
    }

    void printCounter() {
        System.out.println("Counter: " + counterService.getCounter());
    }
}

✅ 이제 CounterService가 데이터를 관리하고, 모듈들은 CounterService에 의존성을 주입받아 사용함.
✅ 공통 결합도가 제거되고, CounterService를 교체하거나 확장하기 쉬워짐.


내용 결합도(Content Coupling)란?

**내용 결합도(Content Coupling)**는 한 모듈이 다른 모듈의 내부 구현(변수, 메서드 등)을 직접 참조하거나 수정하는 경우 발생하는 결합도입니다.
즉, 모듈이 직접 다른 모듈의 내부 데이터를 변경하는 경우를 의미합니다.


🔍 내용 결합도(Content Coupling)라는 이름의 유래

  • "내용(Content)"이라는 단어에서 알 수 있듯이, 모듈이 다른 모듈의 "내부 구현"을 직접 참조하거나 수정하는 특징을 가짐.
  • 이는 객체지향 프로그래밍에서 "캡슐화(Encapsulation) 원칙"을 위반하는 가장 강한 결합도 중 하나임.

❌ 내용 결합도의 문제점

  1. 모듈의 독립성이 완전히 깨짐
    • 한 모듈이 다른 모듈의 내부 데이터를 직접 변경하면, 두 모듈은 분리될 수 없음.
    • 하나의 모듈이 변경되면, 다른 모듈도 반드시 변경해야 함.
  2. 디버깅과 유지보수가 매우 어려움
    • 내부 데이터를 직접 변경하면, 어디서 문제가 발생하는지 추적하기 어려움.
    • 작은 코드 변경이 예상치 못한 문제를 유발할 가능성이 큼.

📖 예제: 내용 결합도의 의미가 드러나는 코드

🔴 나쁜 예 (내용 결합도가 높음)

class Data {
    public int value = 0;  // ❌ public 필드 (내용 결합도 발생)
}

class ModuleA {
    void modifyData(Data data) {
        data.value = 100;  // ❌ 다른 모듈의 내부 데이터 직접 변경
    }
}

class ModuleB {
    void printData(Data data) {
        System.out.println("Value: " + data.value);
    }
}

❌ 문제점:

  • ModuleA가 Data의 내부 값을 직접 변경함.
  • ModuleB가 Data를 읽을 때, 값이 바뀌었을 수도 있음.
  • Data의 구현이 변경되면, 모든 모듈이 영향을 받음.

🛠 내용 결합도를 줄이는 방법

1️⃣ 캡슐화 적용 (Getter/Setter 사용)

class Data {
    private int value = 0;  // ✅ private으로 데이터 보호

    public void setValue(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}

class ModuleA {
    void modifyData(Data data) {
        data.setValue(100);  // ✅ Getter/Setter 사용
    }
}

class ModuleB {
    void printData(Data data) {
        System.out.println("Value: " + data.getValue());
    }
}

✅ 캡슐화를 통해 내부 데이터를 보호하여 내용 결합도를 줄임.


💬 결론

  • 공통 결합도: 여러 모듈이 공통 데이터(전역 변수, static 변수 등)를 공유할 때 발생.
  • 내용 결합도: 한 모듈이 다른 모듈의 내부 데이터를 직접 수정할 때 발생.



'클린 코드' 카테고리의 다른 글

static 메서드가 Mocking이 불가능한 이유?  (0) 2025.03.04
final 메소드를 stub 해야하는 상황  (0) 2025.02.24
'클린 코드' 카테고리의 다른 글
  • static 메서드가 Mocking이 불가능한 이유?
  • final 메소드를 stub 해야하는 상황
Ark B
Ark B
  • Ark B
    기록
    Ark B
  • 전체
    오늘
    어제
    • 분류 전체보기 (33)
      • 개인 프로젝트 정리 (1)
      • JAVA (7)
      • CS (7)
      • 프로젝트 정리 (6)
        • 인턴 (5)
        • 졸업프로젝트 (0)
      • 코딩테스트 (1)
      • 클린 코드 (3)
      • 책 정리 (1)
        • Effective Java (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    오블완
    티스토리챌린지
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
Ark B
객체의 결합도 의미와 단계 정리
상단으로

티스토리툴바