책정리/GoF 디자인 패턴

13장 서브시스템의 명확한 정의 문제(Façade 패턴)

GONII 2019. 1. 30. 22:25
    객체지향 설계에서는 주로 객체들 사이의 관계와 클래스 구조로 설계 결과를 표현한다. 그러나 이런 표현 방법은 설계하는 시스템이 커질수록 사용하는 객체와 클래스가 많아지기 때문에 눈에 설계 결과를 파악하기 힘든 문제를 발생시킨다. 이런 문제를 해결하기 위해서는 객체 여러 개나 클래스를 묶어 하나의 설계 구성 요소로 취급할 있는 방법이 필요한데 이를 위해 객체지향 설계에서 제시하고 있는 방법이 서브시스템을 정의하는 것이다.
    여기서 서브시스템은 임의로 정의하는 것이 아니라 서로 밀접하게 관련된 객체나 클래스들을 묶고, 이를 추상화(Abstraction)시켜 정의한다. 따라서 서브시스템을 설계의 기본 구성 요소로 활용하게 되면 복잡한 설계 내용도 눈에 대강의 뼈대를 파악하는 것이 가능하다.
    그러나 서브시스템 정의 문제점은 객체나 클래스를 정의할 때처럼 명확한 구분이 존재하지 않는다는 것이다. 어떤 객체나 클래스를 서브시스템에 포함시킬지 말지의 여부가 명쾌하게 결정되지 않는다는 것이다. 왜냐하면 시스템에 포함된 객체나 클래스들은 모두 직접 또는 간접적으로 서로 관계를 맺게 되는데 서브시스템에 하나의 객체나 클래스를 포함시키게 되면 그와 직접적으로 관련된 다른 객체나 클래스도 서브시스템에 포함시켜야 가능성이 크고 이렇게 포함된 객체나 클래스는 또다시 자신과 직접적으로 연결된 객체나 클래스들과 연결되기 때문에 어디까지를 동일한 서브시스템 범주에 포함시켜야 할지가 명확하지 않기 때문이다.
    이런 문제들을 해결하면서 복잡한 설계 내용을 서브시스템 단위로 간략히 정의할 있는 방법을 알아본다.
    • 문제 사례 설명
    이용자가 어떤 검색 조건을 입력했을 해당 조건을 만족하는 자료를 데이터베이스로부터 찾아주는 프로그램을 작성한다.
    기본적으로 이용자가 입력한 검색 조건이 제대로 것인지를 검사하는 작업 외에 데이터베이스에 자료를 찾기 위해 데이터베이스 관리 시스템을 접속해야 하고, 검색에 맞는 SQL문장을 생성해야 한다. 생성된 SQL문장을 데이터베이스 관리 시스템에 전달하고 결과를 받아와야 하며, 받아온 결과를 기반으로 이용자에게 보여줄 최종 화면을 생성해야 것이다.
    이처럼 번의 검색 요청을 처리하기 위해서는 많은 복잡한 과정들을 거쳐야 한다. 그러면 검색 프로그램을 이용하는 모든 프로그램들이 이런 복잡한 과정들을 모두 알아야만 할까?
    • 다양한 접근 방법 및 FACADE 패턴
    주어진 문제를 다시 정리하면 데이터베이스로부터 자료를 검색하기 위한 클래스들이 이미 존재하고 있는 상황이다. 이용자가 입력한 검색 조건이 적합한 것인지를 검사해주기 위한 클래스, 검색 조건을 SQL문장으로 생성해주기 위한 클래스, 데이터베이스 관리 시스템에 접속해서 SQL문장의 실행을 요청하는 클래스, SQL 문장의 실행 결과를 저장하기 위한 클래스 등이 이미 존재한다.
    클래스들은 상호간에 복잡한 관계를 가지며, 이런 상황에서 이들 클래스를 이용해서 데이터베이스에서 자료를 검색하는 프로그램을 작성하려면 어떻게 하는 것이 좋을까?
    • 기본적인 방법: 클래스들의 직접 활용 방법
    일차적으로 접근하게 되는 방법은 그냥 주어진 클래스들을 직접 활용해서 검색 프로그램을 작성하는 것이다.


    [그림 13-1]에서 제시하고 있는 방법은 Client 일일이 필요한 클래스의 객체를 생성하고, 이를 활용하는 형태다. Client 데이터베이스로부터 자료를 검색해내기 위해서는 언제 어떤 클래스의 객체를 생성해야 하며, 어떤 과정을 거쳐 검색 작업을 수행해야 하는지를 모두 알고 이에 맞추어 작업을 수행하는 것이다.
    • 좀더 나은 방법: Proxy 패턴
    Client 클래스들을 직접 활용해서 데이터베이스로부터 자료를 검색하는 방법의 문제는 Client 모든 작업을 일일이 직접 수행해야 한다는 것이었다. 이는 기존 클래스들을 재사용하고자 하는 입장에서는 불편한 요소라고 있다. 왜냐하면 Client 모든 작업을 일일이 직접 수행하기 위해서는 클래스들간의 관계를 정확히 파악해야 하는데 이는 무척 부담이 되기 때문이다.
    우리가 바라는 해결책은 Client 직접 모든 작업을 수행하는 것이 아니라 작업 수행에 필요한 입력 요소와 그에 따른 결과만을 Client 직접 관여하고 중간 과정은 블랙박스 형태로 만들어주는 것이다. 이를 위한 한가지 방법은 중간 과정을 수행하기 위한 클래스를 별도로 정의해서 Client 이를 이용하는 형태를 취하는 것이다. , Client 수행하던 역할을 대신할 클래스를 두는 것이다.
    방법은 15장에 있는 Proxy패턴이며, 자체로써 훌륭한 해결책이 있다. 그런데 방법을 적용할 경우 가지 문제는 서로 다른 사람이 서로 다른 프로젝트에서 앞서 정의된 클래스를 이용하고자 경우 별도로 Client 역할을 대행해주는 클래스를 정의하고 사용할 것이라는 점이다. 이런 문제는 개별 프로젝트 단위로는 문제가 있으나, 전체적으로 모든 프로젝트의 결과물들이 모여서 유지, 보수된다면 동일한 역할을 수행하는 클래스가 여러개 존재하는 결과가 되어 유지, 보수에 어려움을 초래할 것이다.
    • 패턴 활용 방법: Facade 패턴
    문제의 원인은 Client마다 별도로 검색 과정을 수행하기 위한 클래스를 정의했기 때문이다. 따라서 이를 해결하기 위해서는 미리 검색 과정을 수행하기 위한 클래스를 정의해서 Client에게 제공하는 것이다. [그림 13-2] 같이 DBHandler라는 클래스를 미리 정의하고 클래스를 모든 Client들이 공유해서 사용하게 하는 것이다.
    이렇게 하면 Client마다 별도로 검색 과정을 수행하기 위한 클래스를 정의해서 사용하는 문제를 깨끗이 해결할 있다. 더불어 이렇게 클래스간의 관계가 정립되면 얻을 있는 하나의 장점은 자연스럽게 서브시스템이 정의될 있다는 것이다. 왜냐하면 DBHandler 클래스를 경계로 Client 사용하는 다른 클래스들과 DBHandler 의해 사용되는 클래스들이 명확히 구분 지어질 있기 때문이다.


    이처럼 여러 개의 클래스들이 밀접한 관계를 가지고 있으며 전체적으로 하나의 역할을 수행할 , 역할을 대표하기 위한 클래스를 정의하고 외부 Client들이 일일이 클래스를 직접 다루지 않더라도 대표 클래스를 통하여 원하는 기능을 제공받을 있도록 만든 방식을 Facade패턴이라고 한다. Facade 패턴에서 여러 클래스들이 제공하는 역할을 대표하는 클래스를 Facade 클래스라고 하며, 클래스를 경계로 자연스럽게 서브시스템을 정의하는 것이 가능하다. 이때 Facade 클래스는 클래스 정의 public으로 공개되는 인터페이스처럼 서브 시스템을 대표하는 인터페이스의 역할을 한다고 있다.
    • 샘플 코드
    [소스 13-1] Facade 패턴을 적용한 데이터베이스 검색 프로그램 소스코드
    #include <iostream>
    #include <string>
    #include <map>
    #include <list>
    #include <iterator>
    using namespace std;
     
    class SearchCond
    {
    public:
    SearchCond(map<string, string> nvList)
    {
    condList_ = nvList;
    }
     
    bool CheckCond()
    {
    // name, value 쌍들의 리스트에 대해 검색 조건을 검사
    //
    return true;
    }
    private:
    map<string, string> condList_;
    };
     
    class ListData
    {
    public:
    int GetCount()
    {
    return result_.size();
    }
     
    void InitIterator()
    {
    iter_ = result_.begin();
    }
     
    ListData GetNextData()
    {
    return *iter_++;
    }
    private:
    list<ListData> result_;
    list<ListData>::iterator iter_;
    };
     
    class Database
    {
    public:
    Database()
    {
    // 데이터베이스 관리 시스템과 연결
    }
     
    bool Execute(string sql, ListDBResult& reulst)
    {
    // sql 수행 결과를 result에 설정하되,
    // sql수행 과정에서 문제가 있으면 return false
    return true;
    }
    };
     
    class SQLGenerator
    {
    public:
    string GeneratorSQL(SearchCond cond)
    {
    string sql;
     
    // 검색 조건에 따라 sql 문장을 생성
    return sql;
    }
    };
     
    class DBHandler
    {
    public:
    bool Search(map<string, string> nvList, ListDBResult& result)
    {
    SearchCond cond(nvList);
    if (!cond.CheckCond())
    {
    cout << "잘못된 검색 조건" << endl;
    return false;
    }
     
    SQLGenerator generator;
    string sql = generator.GeneratorSQL(cond);
     
    return db_.Execute(sql, result);
    }
    private:
    Database db_;
    };
     
    void main()
    {
    map<stirng, string> nvList;
    ListDBResult result;
     
    DBHandler handler;
    handler.Search(nvList, result);
    }
    [소스 13-1] DBHandler 클래스 구현에서 보듯이 데이터베이스 검색과 관련된 일련의 작업들은 모두 클래스에 의해 다루어진다. 경우 Client 단지 입력과 출력을 위한 자료구조를 정의하고 DBHandler 클래스의 객체만 생성해서 원하는 작업을 수행하도록 호출만 해주면 된다.
    Facade 패턴을 활용하면 Client 코딩을 원활히 있고, Facade 클래스에 의해 구분되는 서브시스템을 블랙박스로 다룰 있는 장점이 있다.
    • 구현 관련 사항
    먼저 서브시스템을 구현하는 방식이 여러 가지일 경우를 생각해보자. 동일한 역할을 수행하는 서브시스템을 구현하는 알고리즘이나 클래스 집합들이 여러 존재한다고 해보자. 같은 경우 Facade 패턴을 어떤식으로 적용하는 것이 좋을까?
    일단 Client 입장에서는 서브시스템 내부의 구현 방식이나 클래스 집합이 어떻게 바뀌든 상관없이 동일한 형태로 프로그램 작성이 가능해야 좋을 것이다. 이를 위해서는 Facade 클래스가 Client에게 제공하는 인터페이스가 변경되지 말아야 한다. 반면 Facade 클래스 내부 구현은 서브시스템 구현 형태에 따라 변경 가능해야 것이다.
    가지 조건을 만족시키기 위해서는 Facade 클래스를 추상 클래스로 정의하고, 하위에 구현 방법에 따라 각각 별도의 구체적인 Facade 클래스를 정의하는 방법이 유용할 것이다. Facade 클래스에 상속 관계를 도입하는 것이다. 이렇게 경우 Client 서브시스템의 구현이 어떤 방식으로 이루어지든 동일한 방식으로 서브시스템을 활용할 있게 것이다.
    • FACADE 패턴 정리


    Facade 패턴이 유용한 경우
    • 복잡한 서브시스템에 대해 간단한 인터페이스를 제공하고자 . 서브시스템을 개발해서 제공하는 입장에서 서브시스템이 제공하는 기능이나 역할을 쉽게 사용할 있는 인터페이스를 제공하고자 유용하다.
    • Client 구현 클래스들간에 또는 서브시스템과 다른 서브시스템들간에 의존 관계가 너무 많아 결합도(Coupling) 높을 이를 감소시켜주기 위한 방안으로 Facade 패턴을 적용하면 유용하다.
    • 서브시스템간에 계층화된 설계 구조(Layered Architecture) 가질 Facade 패턴을 이용해서 서브시스템의 진입점을 정의한다면 서브시스템간의 의존관계가 간결해져 유용하다.
    Facade패턴의 장단점
    • Facade 패턴은 Client 입장에서 서브 시스템을 사용할 , 다루어야 객체의 수를 줄여주며, 이를 통해 서브시스템의 사용을 간편하게 만들어준다.
    • Client 서브시스템간의 결합도(Coupling) 높아 복잡할 Facade 패턴을 활용하면 결합도를 낮출 있다. 참고로 좋은 소프트웨어 설계는 응집도(Cohesion) 높고, 결합도(Coupling) 낮아야 하는데, 낮은 결합도는 Client 영향을 주지 않고, 서브시스템 내부를 변경하는 것이 가능하도록 만든다. 또한 Facade 패턴은 객체들간의 의존 관계를 계층화시켜 복잡하거나 회귀적인 의존 관계를 제거시켜줄 있다. 이를 통해 Client 서브시스템은 독립적으로 병행 개발될 있다.
    • Facade 패턴을 적용한 경우, 서브시스템 내부만 변경되었을 때에는 Client 재컴파일할 필요가 없으므로, 대형 시스템의 경우 컴파일 시간을 줄여줄 있다. 같은 맥락에서 서브시스템을 다른 환경으로 이식할 경우에도 Client 소스코드의 수정없이 서브시스템만 변경하면 되므로 이식 과정이 간단해 있다.
    • Facade패턴은 서브시스템에 대한 간결한 인터페이스를 제공하지만, Client 서브시스템 내의 클래스들을 직접 사용하는 것까지는 막지 못한다. 따라서 Client 필요로 경우 서브 시스템 내의 클래스들을 직접 사용할 수도 있다. 이는 Client 서브 시스템이 제공하는 세세한 기능을 사용할 수도 있음을 의미하며, 이를 통해 커스터마이징이 편리해질 있다.
반응형