ReflectionClass + Factory Method

타일에서 각각의 SNS 로그인 방식을 설계했을 때 공부했던 Factory Method 패턴을 가볍게 이야기 하고자 합니다.

Factory Method 패턴이란?

  • 디자인 패턴에서 Factory Method 분류는 생성패턴에 속합니다.
  • 객체를 생성 하게 해주는 인터페이스를 정의하고, 서브클래스 factory method에서 생성할 인스턴스를 결정 합니다.

특성

  • 인스턴스를 생성하기 위해 복잡한 로직을 캡슐화 하기 때문에 OOP의 캡슐화 장점이 있습니다.
  • 어떤 인스턴스를 생성할지 factory method는 알지 못하고 알필요가 없습니다.(그저 생산할뿐..)
  • factory method의 확장을 통해 다양한 타입의 인스턴스를 생성할 수 있는 확장성이 있다.(물론 복잡도가 높아지면 위험해지는 상속구조를 가질수 있다..)

ReflectionClass 이란?

원래 reflection은 여러 진영에서 사용되는 개념입니다. php는 v5 이후 에 나온것으로 추정되고, 이번 프로젝트하면서 php도 있다는 걸 알게 되었습니다.(주로 java쓸때만 써봤던 개념..)

  • reflection 언어적 해석은 '투영, 반사' 정도 라고 보면 될것 같습니다.
  • 개발자적 해석으로 보면 "원하는 것에 투영시켜서 정보를 가져온다" 라고 생각하면 될 것 같습니다.

ReflectionClass 개념

그렇다면 ReflectionClass 는 "class (원하는것)을 투영시켜서 class정보 를 자져온다." 라고 정의 할 수 있습니다.

php 공식 문서를 보면 알 수 있습니다. => The ReflectionClass class reports information about a class.

ReflectionClass::newInstanceArgs

ReflectionClass는 class에 대한 정보를 얻는거지만 저희는 얻으려는게 아니고 얻은 정보를 이용하여 instance를 생성해야 합니다.
그래서 필요한 것이 ReflectionClass::newInstanceArgs 입니다.

  • 구현법

     /*
      ReflectionClass::newInstanceArgs 의 원형입니다.
      인자로 생성할 instance의 파라미터를 맞춰서 array 형식으로 전달하면 됩니다.
     */
     public object ReflectionClass::newInstanceArgs ([ array $args ] )
    

실전돌입!

시나리오

  • 카카오, 페이스북, 트위터는 각각 다른 방식의 로그인 로직이 필요합니다.
  • 하지만 다음과 같이 동일한 프로세스로 진행됩니다.
    • login -> vendor -> redirect 이후 -> callback call
  • 로그인 Controller 는 1개입니다.
  • 로그인 Controller 에서 어떤 SNS 방식인지 확인 후 올바른 SNS클래스 생성하여 로그인처리 해야 한다.

결과적으로 3개의 SNS 클래스, 1개의 로그인 Controller(entry point) 필요 할 거 같습니다.

심플 UML

구현

interface ISocial{
    public function login();
    public function handleCallback();
}

class Kakao implements ISocial{
    public function login(){}
    public function handleCallback(){}
}

class Facebook implements ISocial{
    public function login(){}
    public function handleCallback(){}
}

class Twitter implements ISocial{
    public function login(){}
    public function handleCallback(){}
}

// factory 

abstract class AbstractFactoryMethod{
    abstract function createFactory($social);
}

class FactoryMethod extends AbstractFactoryMethod{
    public function function createFactory($social){
        // 첫글자 대문자 변형 후 클래스 이름 생성
        $className = ucfirst(strtolower($social));

        if(class_exists($className)){
            $parameters = [/* args */];
            // reflection class 로 class 이름으로 투영된 클래스 정보 가져온다. 
            $ref = new \ReflectionClass($className);
            // reflection class 인스턴스를 파라미터와 함께 생성한다.
            $instance = $ref->newInstanceArgs( $parameters );

            return $instance;
        }else{
              throw new Exception("Invalid class name.");
        }
    }
}

class LoginController {
    protected $socialFactory;

    public function __construct(){
        // factory 생성
        $this->socialFactory = new SocialFactoryMethod;
    }

    public function login($social='facebook'){
        // instance 생성
        $this->socialFactory->createSocialAccount($social);   
    }

}

대부분 인터넷에 퍼져있는 다른 예제를 보면, FactoryMethod내에 분기문을 통해 생성하려는 ConcreterProduct 클래스를 생성합니다.
그런데 실제적으로 저희는 사용하려는 클래스 이름이 명확하게 설계가 되어있기 때문에 FactoryMethod에 생성하려는 클래스이름을 인자로 넘겨 ReflectionClass로 생성할 수 있는 장점이 있습니다.

결론

지금 까지 Factory Method를 이용한 SNS 로그인 구현 설계를 하였습니다.
가볍고 간단한 예제를 통해 이야기를 하였지만, 실상은 디자인패턴의 정형화된 틀에 맞춰서 하기 보다 확장과 변형된 설계로 실프로젝트에 서비스 되는 거 같습니다. 저희도 그렇게 하고 있구요.
하지만 이런 개념적인 개념은 느낌적으로 흐름을 파악하는 것을 기저에 있어야 확장과 변형을 할 수 있는 거 같습니다.
도움이 되셨으면 좋겠내요., 그럼..이만..