목표

  • 캡슐화(Encapsulation)에 대해 알아보자.

캡슐화(Encapsulation)

  • 캡슐화란 데이터 구조와 데이터(Attribute)를 다루는 방법(Method)을 결합 시키고, 실제 구현 내용을 외부에서 사용하지 못하도록 은닉하는 것을 말한다.

캡슐화의 목적

  • 오용방지 : 데이터의 잘못된 접근을 막아 데이터를 보호하고 오용을 방지한다.

  • 일관성 : 객체의 내부 구현이 변경되어도, 객체의 사용 방법은 변하지 않는다.

  • 독립성 : 객체의 내부 값이 변경되어도, 다른 객체에 영향을 주지 않는다.

  • 낮은 결합도 : 객체간의 결합도가 떨어진다.

  • 재사용성 : 위 목적을 달성하면, 클래스의 재사용성과 유지보수에 이점을 가져올 수 있다.

오용된 코드

  • 예를들어, 인증에 사용되는 Token을 관리한다고 생각해보자.
public class AuthToken
{
    public bool IsSuccess; // 로그인 성공 여부
    public string AccessToken; // 로그인 성공 시, 리턴되는 토큰
    public string RefreshToken; // 로그인 갱신용 토큰
}

예시

  • AuthClient라는 서버 통신 결과를 받아 AuthToken을 생성해주는 클래스를 만들어 보았다.
  • 여러분이 AuthClient 개발을 해야하고, 누군가에게 AuthToken라는 클래스를 받았다고 생각해보자.
public AuthClient
{
    // 로그인 시도
    public AuthToken SignIn()
    {
        // 객체부터 생성하고, 아래에서 값채워넣는 코딩 스타일은
        // 시간이 지나서 리펙토링 해보겠다고 위아래로 줄바꾸다가 NullPointException가 발생하는 경우가 많음
        AuthToken authToken = new AuthToken();

        // TokenApiApiRequest : TokenApiResponse를 반환하는 REST API라고 가정해보자
        // TokenApiResponse : REST API 응답 결과, accessToken, refreshToken을 리턴한다고 가정해보자

        // 로그인 시도
        TokenApiResponse response = TokenApiApiRequest(SIGNIN);

        // 서버 응답값이 0이면 성공, 아니면 실패
        this.IsSuccess = response.GetResponseCode() == 0 ? true : false;

        // 엑세스 토큰도 내가 넣어야 하고
        this.AccessToken = response.GetAccessToken();

        // 리프레시 토큰도 넣어야 됨
        this.RefreshToken = response.GetRefreshToken();

        return authToken;
    }

    // 로그아웃 
    public AuthToken SignOut()
    {
        AuthToken authToken = new AuthToken();

        // 로그아웃 성공/실패 여부를 리턴하는 함수라고 가정해보자
        TokenApiResponse response = TokenApiApiRequest(SIGNOUT);

        // 로그아웃
        this.IsSuccess = response.GetResponseCode() == 0 ? true : false;

        // 로그아웃이 성공인 경우
        if(isSuccess)
        {
            this.AccessToken = null; 
        }

        // 로그 아웃 실패
        else
        {
            // 로그아웃이 실패하면 값이 없는 것이겠지?
            return null;
        }

        return authToken;
    }

    public AuthToken RefreshToken()
    {
        AuthToken authToken = new AuthToken();

        // 서버 응답 결과 받음
        TokenApiResponse response = TokenApiApiRequest(REFRESH);

        // 이 경우 서버 응답 결과와 로그인 상태를 맞춰 줘야 겠지?
        this.IsSuccess = response.GetResponseCode() == 0 ? true : false;

        // 갱신에 성공했으면
        if(isSuccess)
        {
            this.AccessToken = response.GetAccessToken();
        }
        // 갱신에 실패했으면
        else
        {
            this.AccessToken = null;
        }

        return authToken;
    }
}

다음날 요청사항

  • Authorization Code 로그인 방식 추가
  • Authorization Code는 int 타입으로 관리
  • Authorization Code 로그인인 경우 AccessToken 값은 null로 설정
  • Authorization Code가 발급되면 서버 응답으로 6001 리턴
  • 이 상태면 AuthToken의 상태는 Pending으로 설정

결과

  • 오용방지 :
    • 실수로 Authorization Code 로그인 이후, AccessToken에 null을 넣지 않았습니다.
    • 실수로 AuthCode와 AuthToken 값을 반대로 삽입
  • 일관성 :
    • 서버 응답에 따라 로직 변경 필요. (일관성 없음)
  • 독립성 :
    • 서버 응답값에 로직 의존성 심각
  • 낮은 결합도 :
    • 독립성이 없으니 결합도는 높음
  • 재사용성 :
    • WebAuthClinet를 만든다고 할때, 위 AuthToken은 재사용 가능한가?

캡슐화

  • 그래서 캡슐화를 진행해 보았다.
public class AuthToken
{
    private int responseCode;
    private string accessToken;
    private int authorizationCode;
    private string refreshToken;

    public AuthToken(int responseCode, string accessToken, string refreshToken)
    {
        this.responseCode = responseCode;
        this.accessToken = accessToken;
        this.refreshToken = refreshToken;
    }

    public AuthToken(int responseCode, int authorizationCode)
    {
        this.responseCode = responseCode;
        this.authorizationCode = authorizationCode;
    }

    public bool IsSuccess()
    {
        return responseCode == 0 ? true : false;
    }

    public bool IsPending()
    {
        return responseCode == 6001 ? true : false;
    }

    public int GetResponseCode()
    {
        return this.responseCode;
    }

    public string GetAccessToken()
    {
        return this.accessToken;
    }

    public string GetRefreshToken()
    {
        return this.refreshToken;
    }

    public int GetAuthorizationCode()
    {
        return this.authorizationCode;
    }

    public static AuthToken NewRefreshAuthToken(TokenApiResponse response)
    {
        int responseCode = response.GetResponseCode();
        string accessToken = response.GetAccessToken();
        string refreshToken = response.GetRefreshToken();

        return new AuthToken(responseCode, accessToken, refreshToken);
    }

    public static AuthToken NewSigInToken(TokenApiResponse response)
    {
        int responseCode = response.GetResponseCode();
        string accessToken = response.GetAccessToken();
        string refreshToken = response.GetRefreshToken();
        int authCode = response.GetAuthorizationCode();

        // 토큰 유무 확인
        bool IsTokenSigin = response.GetAccessToken() != null ? true : false

        // 보통 여기서 null체크를 하고, null이라면 서버 응답값이 잘못된 것이므로 thow 날려주면 validation 체크도 완료!

        // 토큰 로그인 인가?
       if(IsTokenSigin)
       {
            return new AuthToken(responseCode, accessToken, refreshToken);
       }
       else
       {
            // 토큰 로그인이 아니면, 코드 로그인이겠군!!
            return new AuthToken(responseCode, authCode);
       }
    }

    public static AuthToken NewSigOutAuthToken(TokenApiResponse response)
    {
        int responseCode = response.GetResponseCode();
        string accessToken = response.GetAccessToken();
        string refreshToken = response.GetRefreshToken();

        return new AuthToken(responseCode, accessToken, refreshToken);
    }
}

캡슐화 이후 여러분이 해야할 작업량은 아래와 같습니다.

public AuthClient
{
    public AuthToken SignIn()
    {
        // 로그인 시도
        TokenApiResponse response = TokenApiApiRequest(SIGNIN);
        return AuthToken.NewSigInToken(response);
    }

    public AuthToken SignOut()
    {
        // 로그아웃
        TokenApiResponse response = TokenApiApiRequest(SIGNOUT);
        return AuthToken.NewSigOutAuthToken(response);
    }

    public AuthToken RefreshToken()
    {
        // 토큰 갱신
        TokenApiResponse response = TokenApiApiRequest(REFRESH);
        return AuthToken.NewRefreshAuthToken(response);
    }
}

+ Recent posts