JAVA - 결합도에 대하여

들어가며

이번 포스트에서는 결합도에 대하여 이야기 해보겠습니다.

결합도?

정보처리기사의 단골 문제이자 개념인 결합도 입니다.

결합도(Coupling)는 소프트웨어의 구성 요소들 간에 서로 얼마나 의존적인지를 나타내는 개념입니다. 높은 결합도는 코드를 변경하기 어렵게 만들고 유지보수를 어렵게 할 수 있으며, 재사용성과 유연성을 저하시킬 수 있습니다. 반면에 낮은 결합도는 코드를 이해하기 쉽고 변경하기 쉽게 만들며, 구성 요소 간의 독립성을 높여 재사용성과 유연성을 향상시킵니다.

 

 

출처 (wmasterj and Fabrice TIERCELIN - wikipedia:en:File:Coupling sketches cropped 1.jpg)

 

 

이전 포스트에서 언급 하였듯이 객체지향 프로그래밍 OOP는 이전 세대인 절차적 프로그래밍의 업그레이드 버전입니다.

절차적 프로그래밍에서 중요한 개념으로 종류는 다음과 같습니다.

 

내용 결합도(높음)

내용 결합도(Content coupling 또는 Pathological coupling)는 하나의 모듈이 다른 모듈의 내부 동작을 수정하거나 내부 동작에 의존하는 상태이다.(예: 다른 모듈의 로컬 데이터에 접근하는 경우)따라서 한 모듈이 데이터를 생성하는 방법(위치, 타입, 타이밍)을 변경하면, 다른 모듈의 변경이 필요하다.

공통 결합도

공통 결합도(Common coupling 또는 Global coupling)는 두 개의 모듈이 같은 글로벌 데이터를 공유하는 상태이다.(예: 전역 변수)공유 자원(변수)를 변경하면, 그 자원(변수)를 사용하는 모든 모듈의 변경이 필요하다.

외부 결합도

외부 결합도(External coupling)는 두 개의의 모듈이 외부에서 도입된 데이터 포맷, 통신 프로토콜, 또는 디바이스 인터페이스를 공유할 때 발생한다. 이는 기본적으로 외부 툴이나 디바이스와의 통신과 관련이 있다.

제어 결합도

제어 결합도(Control coupling)은 하나의 모듈이 다른 모듈으로 무엇을 해야하는지에 대한 정보를 넘겨줌으로써 다른 모듈의 흐름을 제어하는 경우이다.

스탬프 결합도

스탬프 결합도(Stamp coupling 또는 Control coupling)는 모듈들이 데이터 구조를 공유하고, 그 서로 다른 일부만을 사용하는 경우이다.접근할 필요가 없는 필드만 수정되는 경우에도 (데이터의 배치가 변경되므로), 레코드(필드)를 읽는 방법을 변경해야 한다.

자료 결합도

자료 결합도(Data coupling)는 모듈들이 파라메터 등을 통해 데이터를 공유하는 경우이다. 각 데이터가 기본적인 것(elementary piece)이고, 그 데이터들이 공유되는 유일한 데이터이여야 한다. (예, 제곱근을 계산하는 함수로 하나의 정수를 전달하는 경우)

메시지 결합도(Message coupling)(낮음)

가장 낮은 결합도이다. 이는 state decentralization을 통해 이룰 수 있고, 컴포넌트 간의 통신은 파라메터나 메시지 패싱을 통해 이루어져 한다.

결합도 없음(No coupling)

모듈이 어떠한 다른 모듈과도 통신하지 않는 경우이다.

Java에서 결합도 줄이기

읽어보면 결합도는 줄이고 응집성은 높이는 것을 생각하라는 의미입니다.

결국 결합도는 구성 요소 간의 상호 의존성을 줄여야 된다는 의미이고

응집성은 구성 요소 내부의 상호 의존성을 높여야 한다는 의미입니다.

여기서 구성 요소는 모듈 , 클래스 , 객체 등을 의미합니다.

위 언급한 추상 클래스 - 추상 메서드를 사용하여 인터페이스화 시키는 방법을 코드를 통해 이해해 보겠습니다.

강한 결합도의 예시

public class ShoppingCart {
    private Item item;

    public void addItem(Item item) {
        this.item = item;
        // 장바구니에 상품을 추가하는 로직
    }

    public void checkout() {
        // 결제 처리하는 로직
        PaymentProcessor paymentProcessor = new PaymentProcessor();
        paymentProcessor.processPayment(item.getPrice());
    }
}

public class PaymentProcessor {
    public void processPayment(double amount) {
        // 결제 처리하는 로직
    }
}

위의 코드에서 ShoppingCart 클래스는 결제 처리를 위해 PaymentProcessor 클래스를 직접 생성하고 호출합니다. 이는 **ShoppingCart**가 **PaymentProcessor**에 강하게 결합되어 있음을 보여줍니다. 만약 결제 처리 방식을 변경하려면 ShoppingCart 클래스의 코드를 수정해야 합니다.

낮은 결합도의 예시

public class ShoppingCart {
    private PaymentProcessor paymentProcessor;

    public ShoppingCart(PaymentProcessor paymentProcessor) {
        this.paymentProcessor = paymentProcessor;
    }

    public void checkout() {
        // 결제 처리하는 로직
        paymentProcessor.processPayment();
    }
}

public interface PaymentProcessor {
    void processPayment();
}

public class CreditCardProcessor implements PaymentProcessor {
    @Override
    public void processPayment() {
        // 신용카드 결제 처리하는 로직
    }
}

public class PayPalProcessor implements PaymentProcessor {
    @Override
    public void processPayment() {
        // PayPal 결제 처리하는 로직
    }
}

위의 코드에서 ShoppingCart 클래스는 결제 처리에 대한 인터페이스인 **PaymentProcessor**를 사용합니다. 이렇게 하면 ShoppingCart 클래스가 특정 결제 처리 방식에 의존하지 않고, 다양한 결제 처리 방식을 지원할 수 있습니다. 이것이 낮은 결합도를 보여주는 예시입니다.

개인적인 생각

공부해보며 지금까지 작성했던 코드들에 대한 생각을 하였습니다.

분명 공부하며 개념적인 이해를 하였다고 생각하였는데 막상 코드를 작성할 때는

이런 원리를 적용하지 않고 기능 구현에만 너무 초점을 두었다는 생각을 하게 되었습니다.

특히, 결합도에 대해 생각해보면 구현에 집중해 수정 사항 반영이 상당히 힘들었던 점들이

생각 났습니다.

보통 작성을 할때 1번 코드처럼 작성하지 다중 상속이 필요한 경우가 아니면

interface를 사용하지 않았던 것으로 생각합니다.

최근 프로젝트에서 칵테일을 만드는 로직에서 좌표계 값을 선언해두고 관련된 위치를 찾아 Gcode로 변환하는 코드를 작성한 경험이 있는데

이때 추상 메서드와 인터페이스를 활용해서 작성했다면 훨씬 좋은 코드를 작성할 수 있었을 것이라는 생각이 듭니다.

의존성을 줄이는 방법들

위 이야기한 결합도에 대한 이야기는 결국 의존성에 대한 이야기 입니다.

내부요소 - 클래스 , 객체 간의 의존성이 높아지면 한쪽이 문제가 생겼을 때 도미노처럼

문제가 발생하기 때문입니다.

그리고 그중에서 interFace를 활용하는 방법 즉, 추상화를 사용하는 방법에 대해서 이야기하였는데요

이외에 여러 방법이 있습니다.

  • 의존성 주입(Dependency Injection)
  • 객체 간의 의존성을 외부에서 주입하여 결합도를 낮춥니다.
  • 의존성 역전 원칙(Dependency Inversion Principle, DIP)
  • 고수준 모듈이 저수준 모듈에 의존하도록 하는 것이 아니라, 둘 다 추상화된 인터페이스에 의존하도록 설계합니다.
  • 캡슐화(Encapsulation)
  • 클래스의 내부 상태를 외부에서 직접 접근하지 못하도록 하여 결합도를 낮춥니다.

캡슐화는 이전에도 이야기하였듯이 접근을 제어하는 것이고, 즉 정보를 은닉할 수 있다는 이야기 입니다.

의존성 주입 - DI

의존성 주입 - DI는 스프링 포스트를 작성하면서 추가로 해볼 생각입니다.

따라서 간단하게 이야기 하면 의존 관계를 외부에서 설정하는 방법으로 하나의 디자인 패턴입니다.

디자인 패턴이란 많은 개발자들이 개발을 할 떄 큰 범위에서는 유사한 기능,

세부적인 디테일에서 차이가 발생하는데 이때 큰 범위에서의 스터디 셀러를 정리한 방식을 의미합니다. 보통 이런 문제는 이런식으로 하면 되더라 - 라는 개발 지침서? 정도로 생각하면 됩니다.

이것도 이후 포스트에서 이야기 해 볼 것 입니다.

스프링에서는 주로 xml , 어노테이션 , 자바를 통해 의존성을 주입하는데

xml 방식은 교수님이 현직에 있으실 때 쓰던 방식이고, 최근에는 레거시를 하는 것이 아니라면 굳이 사용하지는 않는다고 알고 있습니다.

어노테이션은 Component , Autowied 등이 있는데 자세한 내용은 해당 포스트를 적으며 설명하고

자바는 자바코드를 통해 선언하는 것으로 @Configuration 을 통해 @Bean을 선언하여 만듭니다.

정리하면 DI는 외부에서 관계를 설정해서 의존성을 줄이는 방법을 말합니다. 즉, 결합도를 줄이는 방법입니다.

의존성 역전 원칙

의존성 역전 원칙(Dependency Inversion Principle, DIP)은 객체 지향 설계 원칙 중 하나로

고수준 모듈이 저수준 모듈에 의존하면 안 되며, 둘 다 추상화에 의존해야 한다는 원칙을 말합니다.

이는 추상화된 인터페이스나 추상 클래스를 통해 상위 수준의 모듈이 하위 수준의 모듈과 상호작용하게 함으로써 의존성을 역전시키는 것을 의미합니다.

이건 SOLID 라고 불리는 OOP 설계 5원칙을 말 할때 쓰는데 이 것도 추상화와 관련된 개념입니다.

자세한 내용은 이후 포스트에서 다루겠습니다.

이번 포스트에서는 결합도에 대한 이야기를 하는 것이니 상세한 내용은 다른 포스트들을 참조해주세요

정리하며

이번 포스트에서는 결합도를 어떻게 줄 일 수 있을지에 대해 이야기해봤습니다.

자바에서는 어떻게 결합도를 줄일 수 있는지 방법에 대해 이야기 해봤습니다.

작성하면서 새로운 개념들이 나오는데 모두 정리하면 너무 길어져서

간단하게만 정리해봤습니다.

개인적인 생각

사실 이번 공부를 하며 약간 충격이였던게, 최근 프로젝트에서 대부분의 기능을 구현하며 정말 짧은시간에 많은 기능구현을 해야한 경험 탓에 그래도 이정도 시간에 이렇게 많이 구현해 낼 수 있으면

좀 잘하는거 아닌가 생각했었던 제 자신을 돌아보게 되었습니다.

코드는 양보다 퀄리티가 중요하다고 늘 듣던 말인데 이런 기본적인 것을 놓치고 하였던 것이

더 성장할 수 있는 계기가 된다고 생각합니다.

'JAVA' 카테고리의 다른 글

JAVA - SOLID에 대하여  (0) 2024.06.03
JAVA-어노테이션에 대하여  (0) 2024.06.03
JAVA - interface  (0) 2024.05.29
JAVA - OOP 실사용  (0) 2024.05.29
JAVA-OverLoding & OverRiding  (0) 2024.05.29