open:bridge-pattern

Bridge Pattern

Hurece’s Sour Man의 채용 대행사 여직원들이 구인 요청에 맞는 후보들를 확인하고 있다. 문제는 직무는 고객들이 만드는데, 자격 요건은 채용 대행사에서 만든다는 것이다. 그들이 유연하게 협업할 수 있는 방법을 제공하라.

이브: 문제를 이해하지 못 하겠어요.

페드로: 저는 조금 경험이 있어요. 채용 대행사의 시스템이 아주 이상한데요, 자격 요건들을 인터페이스로 정의해요.

interface JobRequirement {
    boolean accept(Candidate c);
}

페드로: 모든 구체적인 자격 요건은 이 인터페이스를 구현한 것이에요.

class JavaRequirement implements JobRequirement {
    public boolean accept(Candidate c) {
        return c.hasSkill("Java");
    }
}

class Experience10YearsRequirement implements JobRequirement {
    public boolean accept(Candidate c) {
        return c.getExperience() >= 10;
    }
}

이브: 무슨 말인지 알겠어요.

페드로: 여기서 고려해야 할 점은, 이 자격 요건의 계층도가 채용 대행사에서 설계된다는 거죠.

이브: 그렇군요.

페드로: 그리고 Job 계층도가 있는데요, 또한 특정 직무(job)들은 이 Job의 서브 클래스들이죠.

이브: 왜 각 직무마다 클래스가 있어야 되죠? 그건 객체여야 해요.

페드로: 이 시스템은 객체보다는 클래스가 더 인기가 있을 때 설계되었죠. 그래서 여지껏 그대로에요.

이브: 클래스가 객체보다 인기가 있었다고요?!

페드로: 네, 잘 들어요. 자격 요건이 있는 직무는 완전히 분리된 계층도인데요, 그것은 고객에 의해 개발돼요. 그래서 이 두 계층도를 브릿지 패턴으로 분리해서, 각자가 독자적으로 유지되도록 해보죠.

abstract class Job {
    protected List<? extends JobRequirement> requirements;

    public Job(List<? extends JobRequirement> requirements) {
        this.requirements = requirements;
    }

    protected boolean accept(Candidate c) {
        for (JobRequirement j : requirements) {
            if (!j.accept(c)) {
                return false;
            }
        }
        return true;
    }
}

class CognitectClojureDeveloper extends Job {
    public CognitectClojureDeveloper() {
        super(Arrays.asList(
                  new ClojureJobRequirement(),
                  new Experience10YearsRequirement()
        ));
    }
}

이브: 그래서 브리지는 어디에 있죠?

페드로: JobRequirement, JavaRequirement, ExperienceRequirement이 한 계층도죠?

이브: 네.

페드로: Job, CongnitectClojureDeveloperJob, OracleJavaDeveloperJob이 또 다른 계층도죠.

이브: 오, 이제 알겠어요. Job과 JobRequirement의 링크가 브릿지네요.

페드로: 맞아요! 이것이 채용 대행사가 이 시스템을 이용해서 후보자를 찾는 방법이예요.

Candidate joshuaBloch = new Candidate();
(new CognitectClojureDeveloper()).accept(joshuaBloch);
(new OracleSeniorJavaDeveloper()).accept(joshuaBloch);

페드로: 여기에 포인트가 있어요. 고객은 Job 은 추상으로, JobRequirement는 구현으로 사용하죠. 고객들은 단지 직무(job)에 설명을 달아서 만들고, 채용 대행사는 그 설명을 특정 JobRequirement의 집합으로 바꾸죠.

이브: 알겠어요.

페드로: 제가 지금까지 이해한 바로는 클로저는 이 패턴을 defprotocol과 defrecord로 흉내낼 수 있겠네요.

이브: 네, 하지만 이 문제 자체를 다시 보고 싶어요

페드로: 뭐가 잘못되었나요?

이브: 여기에는 일정한 절차가 있어요. 고객은 job 포지션을 만들고, 채용 대행사는 job 포지션을 자격 요건의 집합으로 바꾸고, 그들의 후보자 데이타베이스에서 맞는 사람들 고르는 스크립트를 실행합니다.

페드로: 맞아요.

이브: 그래서 이미 의존성이 있는 거죠. 왜냐하면 채용 대행사가 채용 직무 없이는 아무것도 할 수 없으니까요.

페드로: 글쎄요, 네. 하지만 채용 대행사는 자격 요건 체계를 만들 수 있어요. 채용 직무가 무엇인지 몰라도.

이브: 왜 그렇게 해야 하죠?

페드로: 나중에 이것은 Job 생성자에서 재사용할 수 있고, 그래서 채용 대행사는 같은 일을 두 번 하지 않아도 돼요.

이브: 오케이, 알겠어요, 하지만 이 문제는 인위적이에요. 기본적으로 우리가 필요한 것은 추상과 구체 사이의 협업 방법입니다.

페드로: 아마도요. 하지만 브릿지 패턴으로 이 특수한 문제를 클로저로 어떻게 푸는지 알고 싶어요.

이브: 쉽죠. adhoc 계층도를 사용합시다.

페드로: 추상용 계층도인가요?

이브: 네, job 계층도는 추상이죠, 그리고 사람들은 계층도를 보강할 필요가 있어요.

;; abstraction
(derive ::clojure-job ::job)
(derive ::java-job ::job)
(derive ::senior-clojure-job ::clojure-job)
(derive ::senior-java-job    ::java-job)

이브: 채용 대행사는 개발자들과 같이 마찬가지로, 이 추상의 구현을 제공합니다

;; implementation
(defmulti accept :job)

(defmethod accept :java [candidate]
  (and (some #{:java} (:skills candidate))
       (> (:experience candidate) 1)))

이브: 나중에, 새 채용 직무가 만들어졌는데 자격 요건은 개발되어 있지 않고, 그러한 job 타입을 위한 accept 메소드 구현도 없으면, adhoc 계층도의 기존 accept들이 사용돼요.

페드로: 흠?

이브: 누군가 새로운 ::senior-java를 :java의 일의 자식으로 만들었다고 합시다.

페드로: 오, 그리고 채용 대행사는 분기값이 ::senior-java인 accept 메소드 구현을 제공하지 않으면, 분기값이 ::java인 메소드가 호출되는 건가요?

이브: 당신은 빠르게 배우는 군요.

페드로: 하지만 이것이 정말 브릿지 패턴인가요?

이브: 여기에는 브릿지가 없어요, 하지만 추상과 구체가 독립적으로 유지될 수 있습니다.


  • open/bridge-pattern.txt
  • 마지막으로 수정됨: 2021/11/22 14:15
  • 저자 127.0.0.1