티스토리 뷰

728x90

응집도

응집도는 클래스나 메서드의 책임이 서로 얼마나 강하게 연결되어 있는지를 측정한다. 즉 어떤 것이 여기저기에 모두 속해있는지를 말한다.

이전 리팩터링 코드에서 BankStatementCSVParser의 응집도는 높다. 이 클래스에서 CSV 데이터 파싱 작업과 관련된 두 메서드를 한 그룹으로 만들었기 때문이다.

 

계산 연산을 하는 BankStatementProcessor라는 별도의 클래스를 추출해보자.

public class BankStatementProcessor {
    private final List<BankTransaction> bankTransactions;

    public BankStatementProcessor(final List<BankTransaction> bankTransactions) {
        this.bankTransactions = bankTransactions;
    }

    public double calculateTotalAmount() {
        double total = 0;
        for (final BankTransaction bankTransaction : bankTransactions) {
            total += bankTransaction.getAmount();
        }
        return total;
    }

    public double calculateTotalInMonth(final Month month) {
        double total = 0;
        for (final BankTransaction bankTransaction : bankTransactions) {
            if (bankTransaction.getDate().getMonth() == month) {

                total += bankTransaction.getAmount();
            }
        }
        return total;
    }

    public double calculateTotalForCategory(final String category) {
        double total = 0;
        for (final BankTransaction bankTransaction : bankTransactions) {
            if (bankTransaction.getDescription().equals(category)) {
                total += bankTransaction.getAmount();
            }
        }
        return total;
    }
}

새로 만든 클래스의 메서드를 사용

public class BankStatementAnalyzer {
    private static final String RESOURCES = "src/main/resources/";

    public void analyze(final String fileName, final BankStatementParser bankStatementParser) throws IOException {

        final Path path = Paths.get(RESOURCES + fileName);
        final List<String> lines = Files.readAllLines(path);

        final List<BankTransaction> bankTransactions = bankStatementParser.parseLinesFrom(lines);

        final BankStatementProcessor bankStatementProcessor = new BankStatementProcessor(bankTransactions);

        collectSummary(bankStatementProcessor);

    }

    private static void collectSummary(final BankStatementProcessor bankStatementProcessor) {

        System.out.println("The total for all transactions is "
                + bankStatementProcessor.calculateTotalAmount());
        System.out.println("The total for transactions in January is "
                + bankStatementProcessor.calculateTotalInMonth(Month.JANUARY));
        System.out.println("The total for transactions in February is "
                + bankStatementProcessor.calculateTotalInMonth(Month.FEBRUARY));
        System.out.println("The total salary received is "
                + bankStatementProcessor.calculateTotalForCategory("Salary"));
    }
}

클래스 수준 응집도

실무에서는 일반적으로 다음과 같은 여섯 가지 방법으로 그룹화

  1. 기능
  2. 정보
  3. 유틸리티
  4. 논리
  5. 순차
  6. 시간

기능

BankStatementCSVParser를 구현할 때 기능이 비슷한 메서드를 그룹화 parseFrom()과 parseLinesFrom()은 CSV 형식의 행을 파싱한다. 내부적으로 parseLinesFrom() 메서드는 parseFrom() 메서드를 사용한다. 이렇게 함께 사용하는 메서드를 그룹화하면 찾기도 쉽고 이해하기도 쉬우므로 응집도를 높인다.

단점으로는 한개의 메서드를 갖는 클래스를 여러개 만들려고 하다보면 클래스가 많아지고 복잡해지는 점이 있다.

정보

같은 데이터나 도메인 객체를 처리하는 메서드를 그룹화 하는 방법 

ex) BankTransaction 객체를 CRUD 기능이 필요해 이런 기능만 제공하는 클래스를 만들어야함.

public class BankStatementDAO {

    public BankTransaction create(final LocalDate date, final double amount, final String description) {
        // ...
        throw new UnsupportedOperationException();
    }

    public BankTransaction read(final long id) {
        // ...
        throw new UnsupportedOperationException();
    }

    public BankTransaction update(final long id) {
        // ...
        throw new UnsupportedOperationException();
    }

    public void delete(final BankTransaction bankTransaction) {
        // ...
        throw new UnsupportedOperationException();
    }
}

정보 응집은 여러 기능을 그룹화하면서, 필요한 일부 기능을 포함하는 클래스 전체를 Dependency로 추가한다는 약점이 있음.

유틸리티

유틸리티 클래스 사용은 낮은 응집도로 이어지므로 자제해야 한다. 메서드가 서로 연관성이 없으므로 클래스 전체의 기능을 추론하기가 어렵다.

논리

public class BankStatementFullParser {

    BankTransaction parseFromCSV(final String line) {
        // ...
        throw new UnsupportedOperationException();
    }

    BankTransaction parseFromJSON(final String line) {
        // ...
        throw new UnsupportedOperationException();
    }

    BankTransaction parseFromXML(final String line) {
        // ...
        throw new UnsupportedOperationException();
    }
}

네 개의 메서드 파싱이라는 논리로 그룹화. 그러나 이들은 본질적으로 다르며 네 메서드는 서로 관련이 없다. 이렇게 그룹화 하면 네 가지 책임을 갖게 되므로 이전에 배웠던 SRP를 위배한다. 이 방법은 권장하지 않는다.

순차

파일을 읽고, 파싱하고, 처리하고 정보를 저장하는 메서드들을 한 클래스로 그룹화. 이렇게 입출력이 순차적으로 흐르는 것을 순차 응집이라고 부른다. 순차 응집은 여러 동작이 어떻게 함께 수행되는지 쉽게 이해할 수 있으나 실전에서 순차응집을 적용하면 한 클래스를 바꿔야 할 여러 이유가 존재하므로 SRP를 위배한다.

각 책임을 개별적으로 응집된 클래스로 분리하는 것이 더 좋은 방법이다.

메서드 수준 응집도

응집도 원칙은 클래스 뿐만 아니라 메서드에도 적용할 수 있는데 메서드가 다양한 기능을 수행할 수록 메서드가 어떤 동작을 하는지 이해하기 점점 어려워진다. 일반적으로 클래스나 메서드 파라미터의 여러 필드를 바꾸는 if/else 블록이 여러 개 있다면 이는 응집도에 문제가 있음

728x90
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함