본문 바로가기
스터디/오브젝트

14장 - 일관성 있는 협력

by 검은도자기 2023. 10. 26.

개요

이번 장에서는 일관성 있는 협력이 왜 중요한지에 대한 내용을 자세히 소개합니다.

 

 

왜 일관성 있는 협력이 중요할까?

앱을 개발하다 보면 유사한 요구사항을 반복적으로 추가하거나 수정하게 되는 경우가 있습니다.

유사한 요구사항을 계속 추가해야 하는 상황에서 각 협력이 서로 다른 패턴을 따를 경우에는 전체적인 설계의 일관성이 서서히 무너지게 되면서 코드를 이해하기도 어렵고 코드 수정으로 인해 버그가 발생할 위험성도 높아집니다.

이러한 문제를 해결하기 위해서는 객체들의 협력 방식을 일관성 있게 만들어야 합니다.

일관성 있는 설계가 가져다주는 장점은 유사한 기능을 구현하는 데 드는 시간과 노력을 대폭 줄일 수 있으며, 코드가 이해하기 쉬워진다는 것입니다.

특정한 문제를 유사한 방법으로 해결하고 있다는 사실을 알면 문제를 이해하는 것만으로도 코드의 구조를 예상할 수 있게 됩니다.

따라서 객체들의 협력이 전체적으로 일관성 있는 유사한 패턴을 따른다면 시스템을 이해하고 확장하기 위해 요구되는 정신적인 부담을 크게 줄일 수 있게 됩니다.

 

 

일관성을 고려하지 않은 설계 예시

// Dog 클래스: 'bark' 메서드를 사용하여 소리를 냅니다.
class Dog {
    void bark() {
        System.out.println("Woof!");
    }
}

// Cat 클래스: 'meow' 메서드를 사용하여 소리를 냅니다.
class Cat {
    void meow() {
        System.out.println("Meow!");
    }
}

// Bird 클래스: 'chirp' 메서드를 사용하여 소리를 냅니다.
class Bird {
    void chirp() {
        System.out.println("Chirp!");
    }
}

public class InconsistentDesignExample {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.bark();  // Dog의 소리를 출력

        Cat cat = new Cat();
        cat.meow();  // Cat의 소리를 출력

        Bird bird = new Bird();
        bird.chirp();  // Bird의 소리를 출력
    }
}

 

현재 구현의 가장 큰 문제점은 이 클래스들이 유사한 문제를 해결하고 있음에도 불구하고 설계에 일관성이 없다는 것입니다.

이 클래스들은 모두 소리를 낸다는 공통의 목적을 공유하지만 소리 구현하는 메소드 이름은 완전히 다릅니다.

다시 말해 개념적으로는 연관돼 있지만 구현 방식에 있어서는 완전히 제각각이라는 것입니다.

비일관성은 두 가지 상황에서 발목을 잡습니다.

하나는 새로운 구현을 추가해야 하는 상황이고, 또 다른 하나는 기존의 구현을 이해해야 하는 상황입니다.

이 장애물이 문제인 이유는 개발자로서 우리가 수행하는 대부분의 활동이 코드를 추가하고 이해하는 일과 깊숙이 연관돼 있기 때문입니다.

 

 

일관성 있는 협력을 설계를 어떻게 해야 할까?

일관성 있는 설계를 만드는 데 가장 훌륭한 조언은 다양한 설계 경험을 익히라는 것입니다??

풍부한 설계 경험을 가진 사람은 어떤 변경이 중요한지, 그리고 그 변경을 어떻게 다뤄야 하는지에 대한 통찰력을 가지게 됩니다.

따라서 설계 경험이 풍부하면 풍부할수록 어떤 위치에서 일관성을 보장해야 하고 일관성을 제공하기 위해 어떤 방법을 사용해야 하는지를 직관적으로 결정할 수 있습니다.

 

일관성 있는 설계를 위한 두 번째 조언은 널리 알려진 디자인 패턴을 학습하고 변경이라는 문맥 안에서 디자인 패턴을 적용해 보는 것입니다.

디자인 패턴은 특정한 변경에 대해 일관성 있는 설계를 만들 수 있는 경험 법칙을 모아놓은 일종의 설계 템플릿입니다.

디자인 패턴을 학습하면 빠른 시간 안에 전문가의 경험을 흡수할 수 있습니다.

 

협력을 일관성 있게 만들기 위해 다음과 같은 기본 지침을 따르는 것이 도움이 됩니다.

 

  • 변하는 개념을 변하지 않는 개념으로부터 분리하라.
  • 변하는 개념을 캡슐화하라.

이 두 가지 지침은 훌륭한 구조를 설계하기 위해 따라야 하는 기본적인 원칙이기도 합니다.

애플리케이션에서 달라지는 부분을 찾아내고, 달라지지 않는 부분으로부터 분리시킵니다.

즉, 코드에서 새로운 요구사항이 있을 때마다 바뀌는 부분이 있다면 바뀌는 부분을 따로 뽑아서 캡슐화를 합니다.

그렇게 하면 나중에 바뀌지 않는 부분에는 영향을 미치지 않은 채로 그 부분만 고치거나 확장할 수 있습니다.

 

 

일관성 있는 협력 설계에 대한 간단한 예시

절차지향 방식의 조건 로직

public class AnimalSound {

    public static void main(String[] args) {
        printAnimalSound("dog", "bark");
        printAnimalSound("cat", "sleep");
    }

    public static void printAnimalSound(String animalType, String action) {
        // 첫 번째 조건 로직: 동물의 종류에 따라 처리
        if (animalType.equals("dog")) {
            // 두 번째 조건 로직: 동작에 따라 처리
            if (action.equals("bark")) {
                System.out.println("Woof!");
            } else if (action.equals("sleep")) {
                System.out.println("Zzz...");
            } else {
                System.out.println("Unknown action");
            }
        } else if (animalType.equals("cat")) {
            // 두 번째 조건 로직: 동작에 따라 처리
            if (action.equals("meow")) {
                System.out.println("Meow!");
            } else if (action.equals("sleep")) {
                System.out.println("Zzz...");
            } else {
                System.out.println("Unknown action");
            }
        } else {
            System.out.println("Unknown animal");
        }
    }
}

 

위 코드에는 다음과 같은 문제점들이 존재합니다.

 

  • 변경의 주기가 서로 다름: 동물의 종류와 동작에 따른 소리 출력 로직이 한 메서드 안에 뭉쳐 있어, 동물의 종류나 동작을 추가하거나 변경할 때마다 printAnimalSound 메서드를 수정해야 합니다.
  • 오류 발생 가능성: 새로운 동물 종류나 동작을 추가하려면 기존 코드의 내부를 수정해야 하므로, 오류가 발생할 확률이 높아집니다. 특히, 조건문이 복잡해질수록 이런 문제는 더욱 심각해질 수 있습니다.

 

위 예제에 새로운 조건이 필요하면 위 if 문들에 새로운 else 절을 추가하게 될 것입니다.

따라서 조건에 따라 분기되는 어떤 로직들이 있다면 이 로직들이 바로 개별적인 변경이라고 볼 수 있습니다.

절차지향 프로그램에서 변경을 처리하는 전통적인 방법은 이처럼 조건문의 분기를 추가하거나 개별 분기 로직을 수정하는 것입니다.

 

 

객체지향 방식의 조건 로직

interface Animal {
    void performAction(Action action);
}

class Dog implements Animal {
    public void performAction(Action action) {
        action.execute();
    }
}

class Cat implements Animal {
    public void performAction(Action action) {
        action.execute();
    }
}

interface Action {
    void execute();
}

class Bark implements Action {
    public void execute() {
        System.out.println("Woof!");
    }
}

class Sleep implements Action {
    public void execute() {
        System.out.println("Zzz...");
    }
}

class Meow implements Action {
    public void execute() {
        System.out.println("Meow!");
    }
}

public class AnimalSound {
    public static void main(String[] args) {
        Animal dog = new Dog();
        Animal cat = new Cat();

        Action dogBark = new Bark();
        Action catSleep = new Sleep();
        Action catMeow = new Meow();

        dog.performAction(dogBark);  // Dog의 소리 출력
        cat.performAction(catSleep);  // Cat이 잠을 잠
        cat.performAction(catMeow);  // Cat의 소리 출력
    }
}

 

객체지향의 방식은 조건 로직을 객체 사이의 이동으로 바꾸는 것입니다.

조건 로직을 객체 사이의 이동으로 대체하기 위해서는 커다란 클래스를 더 작은 클래스들로 분리해야 합니다.

 

그렇다면 클래스를 분리하기 위해 어떤 기준을 따르는 것이 좋을까?

가장 중요한 기준은 변경의 이유와 주기입니다.

클래스는 명확히 단 하나의 이유에 의해서만 변경돼야 하고 클래스 안의 모든 코드는 함께 변경돼야 합니다.

간단하게 말해서 단일 책임 원칙을 따르도록 클래스를 분리해야 한다는 것입니다.

 

위 예제에서는 다음과 같은 객체지향 설계 원칙이 적용되어 있습니다.

 

  1. 객체 사이의 이동: Animal과 Action 인터페이스를 통해 조건 로직을 객체 사이의 이동으로 대체하였습니다.
  2. 클래스 분리: Animal과 Action 인터페이스를 구현하는 각각의 클래스를 정의하여 AnimalSound 클래스를 더 작은 클래스들로 분리하였습니다.
  3. 변경의 이유와 주기: 각 클래스는 명확한 단일 책임을 가지며, 동물의 종류나 동작이 변경될 때만 해당 클래스를 수정하면 됩니다.
  4. 일관성 있는 협력 패턴: Animal과 Action 인터페이스를 통해 인스턴스들 사이의 협력 패턴에 일관성을 부여하였으며, 유사한 행동을 수행하는 작은 클래스들이 자연스럽게 역할이라는 추상화로 묶이게 되어 전체 설계의 일관성을 유지하게 됩니다.

 

큰 메서드 안에 뭉쳐있던 조건 로직들을 변경의 압력에 맞춰 작은 클래스들로 분리하면 유사한 행동을 수행하는 작은 클래스들이 자연스럽게 역할이라는 추상화로 묶이게 되고 역할 사이에서 이뤄지는 협력 방식이 전체 설계의 일관성을 유지할 수 있게 됩니다.

 

 

일관성 있게 협력을 만들기 위한 방법 정리

1. 변하는 개념을 변하지 않는 개념으로부터 분리

 

  • 변하는 개념과 변하지 않는 개념을 분리하여, 일관성 있는 협력을 만들어야 합니다.
  • 변하지 않는 부분은 규칙, 변하는 부분은 적용조건이며 이 두 부분을 분리하여야 합니다.

 

2. 변하는 개념을 캡슐화

 

  • 변경을 캡슐화하여 협력을 일관성 있게 만들며, 이를 위해 추상화를 활용합니다.
  • 변하지 않는 부분은 추상화에만 의존하게 만들어, 변하는 부분을 적절히 추상화하고 캡슐화해야 합니다.

 

3. 구체적 협력 구현

 

  • 구간별 정책을 추가하는 등 새로운 기능은 변하지 않는 부분을 재사용하고, 변하는 부분만 구현하여 완성합니다.
  • 이는 코드의 재사용성을 향상시키고 테스트해야 하는 코드의 양을 줄여, 설계의 일관성을 유지하게 합니다.

 

4. 협력 패턴 구축

 

  • 일관성 있는 협력은 변경을 분리하고 캡슐화하는데 중점을 둡니다.
  • 훌륭한 설계자는 변경의 방향을 파악하고, 이에 탄력적으로 대응할 수 있는 다양한 캡슐화 방법과 설계 방법을 익히는 것이 중요합니다.

 

5. 패턴과 프레임워크

 

  • 협력 패턴이 드러나면 객체, 역할, 책임은 계속해서 진화해 나가며, 객체들이 자주 통신하는 경로는 더 효율적이게 됩니다.
  • 패턴과 프레임워크는 객체지향 설계에서 중요한 개념으로, 협력 패턴을 발견하고 구축하는 것이 객체지향 시스템의 개념적 무결성을 유지하는데 도움이 됩니다.

 

 

마무리

오늘은 오브젝트 14장을 스터디하고 이해한 내용들을 정리해 보았습니다.

스터디 전에는 협력이 중요하다 정도로만 이해하고 어떻게 만들어야 할지? 에 대한 고민이 있었습니다.

스터디 후에는 일관성 있는 협력이 왜 중요한지? 어떻게 협력을 만들어야할지? 그리고 설계를 잘하기 위한 방향성에 대한 해답을 얻어서 좋았습니다.

이번 포스팅은 마무리하면서 다음 포스팅에서 뵙겠습니다.

 

 

참고

http://www.yes24.com/Product/Goods/74219491

 

오브젝트 - 예스24

역할, 책임, 협력을 향해 객체지향적으로 프로그래밍하라!객체지향으로 향하는 첫걸음은 클래스가 아니라 객체를 바라보는 것에서부터 시작한다. 객체지향으로 향하는 두번째 걸음은 객체를

www.yes24.com

 

'스터디 > 오브젝트' 카테고리의 다른 글

15장 - 디자인 패턴과 프레임워크  (0) 2023.11.14
13장 - 서브클래싱과 서브타이핑  (0) 2023.10.16
12장 - 다형성  (0) 2023.09.20
11장 - 합성과 유연한 설계  (0) 2023.08.28
10장 - 상속과 코드 재사용  (0) 2023.08.06