ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [OOP] Tell, Don't Ask(TDA) 원칙과 캡슐화
    Software Design 2022. 1. 18. 22:59
    반응형

    Tell, Don't Ask. 객체에게 데이터를 요구(Ask) 하지 말고, 객체에게 일을 시켜라(Tell) 정도로 해석할 수 있다.

    객체 지향적 사고방식 원칙 중 하나인데, 특히 객체지향의 핵심인 캡슐화가 잘 이루어진 설계를 하기 위해서 탄생한 원칙이다.

    다음 User 클래스를 통해 캡슐화가 이루어지지 않은 객체의 문제점을 알아보고, TDA 원칙을 통해서 캡슐화를 시켜보자.

    class User {
      public email: string;
      public age: number;
      public createdAt: Date;
    }

    어떤 문제가 있을까?

    어떤 비즈니스 로직을 수행할 때, 사용자의 나이가 20보다 작다면 오류가 나야 한다는 요구사항이 있다고 하자.

    User객체를 매개변수로 전달받는 서비스 메소드에 이 요구사항을 다음처럼 구현하였다.

    doSomething(user: User) {
      if (user.age < 20) {
        throw new Error('Invalid user!');
      }
      // 무언가를 한다
    }

    그런데 요구사항이 바뀌어서 사용자가 gmail을 사용해야 한다는 조건이 추가되었다.

    사용자의 이메일이 gmail인지 검사하는 코드를 추가해주자.

    doSomething(user: User) {
      if (user.age < 20 && !user.emil.endsWith('gmail.com')) {
        throw new Error('Invalid user!');
      }
      // 무언가를 한다
    }

    그리고 사용자가 유효한지 검사하는 동일한 조건문이 여러 서비스에 구현되어 있었다면, 해당 조건이 필요한 서비스를 모두 찾아서 코드를 수정해주자.

    // A.ts
    doA(user: User) {
      if (user.age < 20 && !user.emil.endsWith('gmail.com')) {
        throw new Error('Invalid user!');
      }
      // do A
    }
    
    // B.ts
    doB(user: User) {
      if (user.age < 20 && !user.emil.endsWith('gmail.com')) {
        throw new Error('Invalid user!');
      }
      // do B
    }
    
    // C.ts
    doC(user: User) {
      if (user.age < 20 && !user.emil.endsWith('gmail.com')) {
        throw new Error('Invalid user!');
      }
      // do C
    }

    사용자가 유효한지 검사하는 조건이 여러 서비스에 구현되어 있다면, 해당 조건이 변경될 때마다 사용자를 검사하는 모든 서비스의 코드를 수정해야 할 것이다.

    예제를 통해 확인할 수 있는 객체가 캡슐화가 되어있지 않으면 나타나는 문제점은 크게 두 가지이다.

    • User 객체가 캡슐화가 되지 않았기 떄문에 User의 데이터에 직접 접근할 수 있는 코드를 작성할 수 있다.
    • 요구사항의 변화가 User의 데이터/사용에 변화를 주게 되면, 해당 데이터에 접근하는 모든 코드에 영향을 주게 된다.

    TDA 원칙 적용

    위 예제에서 객체의 데이터를 요구하는 부분은 user의 age와 email에 직접 접근해서 값을 비교하는 부분이다.

    TDA원칙을 통해 올바른 캡슐화가 이루어진 User객체라면, 자신의 데이터들을 외부에 공개하지 않고(정보은닉), 자신의 데이터를 이용해 필요한 정보를 외부에 알려줄 수 있어야 한다.

    User 클래스의 프로퍼티를 private으로 정의해주고, 자신의 데이터를 이용해 유효한 사용자인지를 알려주는 isValid()라는 메소드를 구현해주자.

    class User {
      private email: string;
      private age: number;
      private createdAt: Date;
    
      isValid() {
        return (
          this.age >= 20
          && this.email.endsWith('gmail.com')
        )
      }
    }
    doSomething(user: User) {
      if (!user.isValid()) {
        throw new Error('Invalid user!');
      }
      // 무언가를 한다
    }

    isValid()라는 메소드를 구현했기 때문에 User 객체는 해당 객체를 사용하는 부분에 필요한 기능을 제공하고, 상세 구현을 감출 수 있다.

    이제 어떤 서비스 메소드에서 사용자가 유효한 사용자인지 알아야 한다면, User 객체에게 유효한 사용자인지에 대한 판단을 요구할 수 있게 되었다.

    사용자를 검사하는 요구사항이 변경되더라도, User 객체의 isValid() 메소드만 수정하면 되고 해당 메소드를 사용하는 부분은 더이상 수정할 필요가 없다.

    References

    반응형

    'Software Design' 카테고리의 다른 글

    [OOP] DTO, Entity와 객체지향적 사고  (1) 2022.01.10

    댓글

Designed by Tistory.