Imaginations crafted into real code.

제어 결합


모듈화 설계: 응집도와 결합도 (제어 결합 해결 방안)

소프트웨어공학 과목에서 모듈화 설계를 공부하며 **응집도(Cohesion)**와 **결합도(Coupling)**를 배우고 있다. 특히 결합도 중 **제어 결합(Control Coupling)**이 어떤 문제를 일으키는지, 그리고 어떻게 해결할 수 있는지에 대해 정리해 본다.

📌 제어 결합 (Control Coupling)의 문제점

제어 결합은 한 모듈이 다른 모듈에 **제어 플래그(Control Flag)**를 전달하여, 받는 모듈의 동작 흐름을 결정하는 방식이다. 아래 예시 코드를 통해 문제점을 살펴보자.

👎 제어 결합 예시 코드

// 인증_처리 모듈
function 인증_처리(사용자_ID, 비밀번호) {
    if (사용자_인증_성공(사용자_ID, 비밀번호)) {
        사용자_등급 = 사용자_정보_가져오기(사용자_ID).등급;
        // 사용자_등급이 제어 플래그 역할을 한다.
        로그인_수행(사용자_ID, 사용자_등급); 
    } else {
        로그인_실패_처리();
    }
}

// 로그인_수행 모듈
function 로그인_수행(사용자_ID, 등급) { // '등급'이 제어 플래그
    if (등급 == "관리자") {
        관리자_로그인_로직();
    } else if (등급 == "일반_사용자") {
        일반_사용자_로그인_로직();
    } else {
        // ... 기타 등급 처리
    }
    로그인_성공_처리(사용자_ID);
}

❗️ 제어 결합의 문제점

위 코드에서 사용자_등급이라는 제어 플래그로그인_수행 모듈에 전달하고 있다. 이 방식은 다음과 같은 문제를 야기한다.

  • 테스트의 어려움: 새로운 등급이 추가될 때마다 로그인_수행 모듈 내부의 if-else if 블록이 변경되어야 하고, 이에 따라 관련 테스트 케이스도 업데이트되어야 한다. 이는 유지보수 비용을 증가시킨다.
  • 높은 의존성: 로그인_수행 모듈은 등급이라는 플래그의 의미와 종류를 알고 있어야 한다. 즉, 인증_처리 모듈과 로그인_수행 모듈 간에 등급 플래그에 대한 암묵적인 합의가 필요하며, 한쪽이 변경되면 다른 쪽도 영향을 받는다. 이는 모듈 간의 결합도를 높인다.

✅ 제어 결합 해결: 전략 패턴(Strategy Pattern) 활용

이러한 제어 결합의 문제를 해결하기 위해, 플래그를 직접 매개변수로 전달하는 대신 **다형적인 객체(Polymorphic Object)**를 전달하는 방법을 사용한다. 이는 디자인 패턴 중 **전략 패턴(Strategy Pattern)**과 유사하다. 로그인의 구체적인 방식을 캡슐화하여 추상화된 인터페이스를 통해 상호작용하도록 변경할 수 있다.

👍 제어 결합 해결 예시 코드 (전략 패턴 활용)

// 로그인 전략 인터페이스/클래스 (추상화된 로그인 행위 정의)
interface 로그인_전략 {
    void 로그인();
}

// 관리자 로그인 전략 구현체
class 관리자_로그인_전략 implements 로그인_전략 {
    void 로그인() {
        // 실제 관리자 로그인 처리 로직
        관리자_로그인_로직(); 
    }
}

// 일반 사용자 로그인 전략 구현체
class 일반_사용자_로그인_전략 implements 로그인_전략 {
    void 로그인() {
        // 실제 일반 사용자 로그인 처리 로직
        일반_사용자_로그인_로직();
    }
}

// 로그인_수행 모듈 (전략 패턴의 Context 역할)
function 로그인_수행(사용자_ID, 로그인_전략_객체) {
    // 제어 플래그 대신 객체의 공통 메서드를 호출
    로그인_전략_객체.로그인(); 
    로그인_성공_처리(사용자_ID);
}

// 인증_처리 모듈 (전략 객체 생성 및 전달)
function 인증_처리(사용자_ID, 비밀번호) {
    if (사용자_인증_성공(사용자_ID, 비밀번호)) {
        사용자_등급 = 사용자_정보_가져오기(사용자_ID).등급;
        로그인_전략_객체 = null; // 초기화

        // 사용자 등급에 따라 적절한 전략 객체 생성
        if (사용자_등급 == "관리자") {
            로그인_전략_객체 = new 관리자_로그인_전략();
        } else if (사용자_등급 == "일반_사용자") {
            로그인_전략_객체 = new 일반_사용자_로그인_전략();
        }
        // ... 기타 등급 처리 및 전략 객체 생성 로직 추가 가능

        로그인_수행(사용자_ID, 로그인_전략_객체); // 추상화된 객체 전달
    } else {
        로그인_실패_처리();
    }
}

✨ 개선점

이 방식은 등급 플래그에 직접 의존하지 않고, 로그인_전략_객체라는 추상화된 객체를 통해 공통적인 로그인() 함수를 호출한다.

  • 로그인_수행 모듈의 독립성 증대: 이제 로그인_수행 모듈은 더 이상 등급 플래그의 종류나 각 등급별 로그인 로직을 알 필요가 없다. 단지 로그인_전략 인터페이스를 구현하는 객체가 로그인() 메서드를 가지고 있다는 사실만 알면 된다. 이는 로그인_수행 모듈의 결합도를 낮추고 응집도를 높인다.
  • 확장성 용이: 새로운 사용자 등급이 추가되더라도, 새로운 로그인_전략 구현체만 추가하고 인증_처리 모듈에서 해당 전략 객체를 생성하여 넘겨주면 된다. 로그인_수행 모듈은 전혀 수정할 필요가 없으므로, 유지보수 및 확장이 훨씬 용이해진다.
  • 테스트 용이성: 각 로그인_전략 구현체를 개별적으로 테스트할 수 있으며, 로그인_수행 모듈은 다양한 전략 객체를 주입하여 테스트할 수 있다.

이러한 방식으로 설계하면, 변화에 유연하게 대응할 수 있는 견고한 소프트웨어를 만들 수 있다. 소프트웨어공학의 모듈화 원칙이 실제 코드에 어떻게 적용되는지 명확히 이해할 수 있는 좋은 예시였다.