'Dynamic Object Model'에 해당되는 글 1건

  1. 2016.10.01 Adaptive Object Model(AOM) 패턴 및 그 구현

Adaptive Object Model(이하 AOM) 패턴은 간단히 이야기 하자면 동적 객체 생성 패턴이다. 그래서 Dynamic Object Model 패턴이라고도 불린다.

이 패턴은 몇 개의 다른 패턴들이 결합되어 생겨난 패턴이다. 이에 대한 자세한 설명은 넥스트리(Nextree)의 블로그에서 확인할 수 있다.(http://www.nextree.co.kr/p2469/)

그리고 이 패턴을 응용하여 다양한 시도들이 이루어지고 있는데 이에 대한 자료는 Adaptive Object Model 패턴 공식 홈페이지(http://adaptiveobjectmodel.com/)에서 확인할 수 있다.

이 패턴은 실제 제품에도 활용된 사례가 있다. Tridium 사에서 개발한 Niagara Platform이라는 빌딩용 네트워크 시스템에 탑재되는 Niagara 소프트웨어가 이 패턴을 통해 구현되어 있다.(https://www.niagara-community.com/Comm_Home)


이 패턴은 객체의 동적 선언과 동적 생성이라는 두가지 특성을 모두 지원해주는 패턴이다. 그 자체가 매우 다이나믹한 특성을 가지고 있고, 이에 따라 다양한 파생 구조들이 생겨날 수 있다. 여기에서는 가장 기본이 되는 형태인 동적 선언과 동적 생성에 대해서 초점을 맞춰 보고자 한다.


만약 어떤 어플리케이션이 매우 다양한 대상을 취급한다고 가정하자. 특히 공장이나 빌딩, 대규모 상업 단지와 같이 각종 설비들이 설치될 수 있고, 이에 대한 통합적인 관리가 필요하다고 가정하자. 이런 경우에는 특히 기존에 잘 사용하던 장비를 다른 회사의 장비로 교체할 수도 있고, 새로운 장비들을 추가할 수도 있다. 이런 경우 새로운 장치는 기존에 통합 관리 시스템이 만들어질 당시에는 존재하지 않는 것일 수도 있다.

이런 장비들을 소프트웨어로 매핑하기 위해서는 수많은 장비들을 포용할 수 있는 객체를 만들어야 한다. 즉 각 장비들은 그와 대응되는 객체가 정의 되어 있어야만 정확하게 정보를 얻고 저장할 수 있다. 하지만 이미 이야기 했다 시피 장비의 종류는 너무나도 다양하고, 매번 새로운 장비들이 개발되게 되어 있다. 이런 상황에서는 아무리 객체 설계를 잘 했다고 해도 언젠가는 그 객체로는 장비 정보를 제대로 표현할 수 없게 될 것이다.

또한 현장에서의 요구사항은 항상 바뀌게 마련이다. 현장마다 장비의 종류가 각각 다르다. 그런 현장들을 지원하기 위해 매번 소프트웨어를 다시 컴파일 할 수는 없는 노릇이다. 즉, 소프트웨어가 동작하는 동안에도 새로운 장비들의 정보를 보여줄 객체를 생성할 수 있어야 하며, 이 객체는 수 많은 장치들을 모두 커버할 수 있는 객체여야 한다.

즉 문제의 핵심은 어떻게 실시간에 새롭게 정의된 객체를 생성할 수 있느냐 하는 것이다.


실세계의 장치를 지원할 객체를 디자인 한다고 하면 우리는 다음과 같은 방식들을 사용할 수 있다.

- 우선 공통된 정보들을 모아서 상위 클래스로 선언한다.(예를 들어 모든 종류의 장치를 나타내는 Device 클래스를 선언한다.)

- 그리고 상위 클래스의 속성들을 선언한다.

- 개별 디바이스 정보를 담을 하위 클래스를 선언한다. 당연히 상위 클래스의 속성들을 상속 받아야 한다.

- 하위 클래스의 속성을 선언한다.

- 장치들은 모두 하위 장치들을 포함할 수 있다. 따라서 속성 중에는 다른 장치가 포함될 수도 있다. (일종의 부품이나 참조 객체와 같은 성격이다.)


이런 방식은 우리가 객체지향 언어들을 통해서 일반적으로 장치에 대한 클래스를 선언할 때 사용하는 모델링 방식이다. 따라서 동적으로 객체를 생성할 수 있으려면, 위에서 이야기 한 상속 개념, 참조 개념을 모두 구현할 수 있어야만 한다. AOM 패턴은 상속이나 참조 개념들을 포함한 동적 객체를 모사하여 생성해 줄 수 있는 패턴이다.


AOM 패턴의 클래스 다이어그램


AOM 패턴은 TypeSquare라는 사각형 형태의 다이어그램을 가지게 된다.

이 다이어그램에서 왼쪽은 객체화되고 시간에 따라서 가변적인 정보들을 담는 객체이다. 오른쪽 두 클래스는 타입에 관한 정보를 가지고 있게 된다. 오른쪽 클래스들이 가진 정보는 일반적으로 변경되지 않는다.

우선 Klass와 KlassType에 대해 알아보자.

이 둘의 관계는 객체지향 프로그래밍에서 말하는 객체(Klass) - 클래스(KlassType)의 관계와 같다고 생각하면 된다. 우선 KlassType은 클래스명에 해당하는 name을 가지고 있다. 그리고 클래스가 가질 수 있는 상위 클래스는 parent라는 변수를 통해 가지고 있게 된다. 당연히 parent도 KlassType 객체이다. 그리고 실제 클래스는 속성(attribute)을 선언하게 된다. 이 속성에 대한 선언이 AttributeType이고, KlassType은 이를 소유하고 있다.

Klass는 실제 객체로 생성되었을 때 가지는 정보들을 선언한 클래스이다. Klass 클래스를 기반으로 객체가 생성될 때에는 무조건 한 개의 KlassType 객체를 인자로 받아야 한다. 일단 생성 이후에는 객체의 타입이 바뀌길 원하지는 않기 때문이다. 이는 마치 클래스를 기반으로 객체를 생성하는 과정과 같다. Klass 객체는 KlassType에 선언된 AttributeType을 기반으로 Attribute 객체를 생성한다.

Attribute와 AttributeType 간의 관계는 클래스의 속성 선언과 객체가 가진 속성의 관계와 같다. AttributeType이란 우리가 속성을 선언할 때 int data; 와 같이 선언하는 것처럼 우선 속성의 타입을 나타내는 typeClass 변수를 가져야 한다. 이 다이어그램에서는 이 typeClass를 Java의 클래스 객체로 선언하여 가지고 있다. 다음으로 속성의 이름에 해당하는 name 을 가지고 있어야 한다. description은 이 속성에 대한 설명을 담는 변수이다.

Attribute는 AttributeType을 기반으로 생성되는 객체이다. 실제 객체에서 데이터를 담을 수 있는 공간으로 이해할 수 있다. 따라서 자신의 타입 정보인 AttributeType 객체를 하나 가지고 있어야 하고, 데이터를 담을 value 객체를 가지고 있어야 한다. 이 Attribute는 범용적으로 쓰일 수 있어야 하기 때문에 value는 Object 타입이다.


이제 AOM 패턴의 구현을 살펴보자.


KlassType 클래스

class KlassType{

    // 클래스 정보에 해당한다.   

    KlassType parent;

   

    public KlassType(String name, KlassType parent){

        this.name = name;

        this.parent = parent;

        addParentAttributeTypes();

    }

   

    private void addParentAttributeTypes(){

        if(parent == null) return;

        Collection<AttributeType> parentAttributeTypes = parent.getAttributeTypes();

        for(AttributeType each : parentAttributeTypes){

            attributeTypes.put(each.getName(), each);

        }

    }

   

    Map<String, AttributeType> attributeTypes = new HashMap<String, AttributeType>();


        public void addAttributeType(AttributeType attributeType){   

            attributeTypes.put(attributeType.getName(), attributeType); 

        }


        public AttributeType getAttributeType(String typeName){ 

            return attributeTypes.get(typeName); 

        }


        public Collection<AttributeType> getAttributeTypes() {

            return attributeTypes.values();

        }

       

    private String name;

        public String getName(){ return name; }   

}

KlassType은 객체지향 언어에서 클래스를 선언하는 것과 대응되는 클래스이다. 클래스는 클래스 이름과 상속받은 부모 클래스 명, 내부에 선언된 속성들로 구성된다.(행위는 AOM 패턴과 연관된 다른 패턴들에 의해 구현된다. 이 예제에서는 일단 배제되어 있다.)

- parent : 부모 클래스

- name : 클래스명

- attributeTypes : 선언된 속성들


맨처음 KlassType이 객체로 선언되었을 때에는 아무런 속성타입(AttributeType)을 가지고 있지 않으므로 이를 추가해 줄 수 있도록 addAttributeType()이라는 메소드를 제공해야 한다. 또한 생성자에서 부모 클래스를 나타내는 객체를 받았다면 부모가 선언한 AttributeType들도 상속 받아야 한다. 따라서 addPrentAttributeTypes() 메소드를 생성자에서 호출하게 된다.


Klass 클래스

class Klass{

    // 객체에 해당한다.

    public Klass(KlassType type, String name, String id){

        this.type = type;

        this.name = name;

        this.id = id;

        initAttributes();

    }

   

    private void initAttributes(){

        for(AttributeType attributeType : type.getAttributeTypes()){

            attributes.put(attributeType.getName(), new Attribute(attributeType));

        }

    }

   

    private KlassType type;

        public KlassType getType(KlassType type){ return type; }

   

    String name;

        public String getName(){ return name; }

   

    String id;

        public String getId(){ return id; }   

    Map<String, Attribute> attributes = new HashMap<String, Attribute>();

        public Object get(String name){

            Attribute attr = attributes.get(name);

            if(attr != null) return attr.getValue();

            else throwNoSuchAttributeException(name);

            return null;

        }


        public void set(String name, Object value){

            Attribute attr = attributes.get(name);

            if(attr != null) attr.setValue(value);

            else throwNoSuchAttributeException(name);

        }


        public Attribute getAttribute(String name){

            Attribute attr = attributes.get(name);

            if(attr != null) return attr;

            else throwNoSuchAttributeException(name);

            return null;

        }

   

    public String toIndentString(String indent){

        StringBuffer buffer = new StringBuffer();

        buffer.append(indent + "Class " + type.getName() + " " + name);

        if(type.parent != null) buffer.append(" extends " + type.parent.getName());

        buffer.append("{\n");

        for(Attribute each : attributes.values()){

            if(each.getValue() instanceof Klass){

                Klass inner = (Klass)each.getValue();

                buffer.append(indent + "   " + each.getType().getTypeClassName() + " " + each.getType().getName() + " = " + inner.toIndentString(indent + "   ") + ";\n");

            }

            else{

                buffer.append(indent + "   " + each.getType().getTypeClassName() + " " + each.getType().getName() + " = " + each.getValue() + ";\n");

            }

        }

        buffer.append(indent + "}");

        return buffer.toString();

    }

   

    private void throwNoSuchAttributeException(String attributeName){

        try {

            throw new NoSuchAttributeException();

        } catch (NoSuchAttributeException e) {

            System.out.println("Class \"" + name + "\" has no such attribute : \"" + attributeName + "\n");

            e.printStackTrace();

        }

    }

} 

Klass 클래스는 생성 이후에 객체 역할을 수행하는 클래스이다. 객체는 객체로서의 이름과 아이덴티티, 그리고 타입에 대한 정보 및 속성 값들을 가지고 있어야 한다.

- type : 객체의 타입 정보(AttributeType)

- name : 객체의 이름(변수명이라고 이해하면 된다.)

- id : 객체와 객체를 유일하게 구분해주는 구분자 역할. 구현에서는 UUID를 문자열화 해서 사용한다.

- attributes : 속성들을 저장한 Map. 속성명(변수명)을 키로 하여 Attribute를 저장하는 Map이다.


각 Klass 객체는 별도의 속성을 가지고 있어야 한다. 따라서 Klass 객체가 생성되면 우선 KlassType을 인자로 받은 다음, 이 KlassType에 선언되어 있는 속성 타입 정보(attributeTypes)를 얻어온다. 그리고 그 정보를 기반으로 초기화 되지 않은 Attribute 객체를 만들고 이를 attributes 맵에 저장한다.

객체는 외부에서 자신에 접근하는 연산들을 제공해 주어야 한다. 따라서 저장된 속성값을 제공해주는 get() 메소드와 속상 값을 설정할 수 있게 해주는 set() 메소드를 제공해준다.

추후에 테스트를 통해서 의도한 대로 Klass 객체가 잘 만들어졌는지 확인하기 위해서 toIndentString() 메소드도 구현해 주었다. 이 메소드는 마치 우리가 클래스를 코딩했을 때와 유사하게 내부 속성들과 클래스명, 그리고 상속 받은 클래스의 정보들을 표시하도록 되어 있다.


AttributeType 클래스

class AttributeType{

    // 클래스에 속성(변수)을 선언하면 int data; 와 같이 선언한다. 이 정보를 이 클래스가 가지고 있어야 한다.

    Map<String, KlassType> klassTypes = new HashMap<String, KlassType>();


    @SuppressWarnings("rawtypes")

    private Class typeClass;

    @SuppressWarnings("rawtypes")

    public Class getTypeClass(){ return typeClass; }

    public String getTypeClassName(){

        String typeName = typeClass.getName();

        typeName = typeName.substring(typeName.lastIndexOf(".") + 1);

        return typeName;

    }

   

    @SuppressWarnings("rawtypes")

    public AttributeType(Class typeClass, String name, String description){

        this.typeClass = typeClass;

        this.name = name;

    }

   

    private String name;

    public String getName() { return name; }

   

    private String description;

    public String getDescription(){ return description; }

}

AttributeType 클래스는 속성의 선언에 해당하는 클래스이다.

- typeClass : 속성의 타입을 나타낸다. 이 구현에서는 클래스 객체를 이용하고 있다.

- name : 속성의 명칭을 나타내는 정보이다.

- description : 이 속성에 대한 설명을 넣는 변수이다.


이 클래스는 생성자를 통해서 위의 정보들을 받아 저장한다. 이 정보들은 이 후 Attribute 객체를 생성하기 위한 정보로 활용된다.


Attribute 클래스

class Attribute{

    // Attribute = 속성, 필드. attributType value를 가져야 한다.

   

    private AttributeType type;

   

    public Attribute(AttributeType type){

        this.type = type;

    }

   

    public AttributeType getType(){ return type; }

       

    private Object value;

    public Object getValue() { return value; }

    public void setValue(Object value) {

        if(isSettable == false){

            throwOperationNotSupportException();

            return;

        }

        this.value = value;

    }

   

    private void throwOperationNotSupportException(){

        try { throw new OperationNotSupportedException(); }

        catch (OperationNotSupportedException e) {

            System.err.println("Attribute " + type.getName() + " is immutable.");

            e.printStackTrace();

        }

    }

   

    private boolean isSettable = true;

    public boolean isSettable(){ return isSettable; }

    public void setSettable(boolean isSettable){ this.isSettable = isSettable; }

} 

Attribute 클래스는 실제 객체에서의 속성을 나타낸다. 따라서 Attribute는 자신이 저장할 속성 값에 대한 타입 정보를 가지고 있어야 하고, 속성 값을 함께 가지고 있어야 한다.

- type : AttributeType

- value : 속성 값을 저장하는 변수


이 속성은 외부에서 조회가 가능하고, 값이 변경되면 저장이 가능해야 한다. 따라서 getValue() 메소드와 setValue() 메소드를 통해 이런 연산들을 지원하고 있다.


이제 이 클래스들을 통해서 새로운 클래스를 선언하고 객체를 생성하는 과정을 살펴보도록 하자.


실행 함수

public static void main(String[] args) {

    KlassType site = new KlassType("Site", null);

    site.addAttributeType(new AttributeType(String.class, "position", "위치"));

    KlassType house = new KlassType("House", site);

    house.addAttributeType(new AttributeType(String.class, "owner", "소유주"));

    house.addAttributeType(new AttributeType(Integer.class, "area", "면적"));

    house.addAttributeType(new AttributeType(Klass.class, "car", "차량"));

    Klass myHouse = new Klass(house, "우리집", UUID.randomUUID().toString());

    myHouse.set("position", "사당동");

    myHouse.set("owner", "홍길동");

    myHouse.set("area", "30");

    KlassType car = new KlassType("Car", null);

    car.addAttributeType(new AttributeType(String.class, "model", "모델"));

    car.addAttributeType(new AttributeType(Integer.class, "hp", "마력"));

    car.addAttributeType(new AttributeType(String.class, "type", "종류"));

    Klass myCar = new Klass(car, "내차", UUID.randomUUID().toString());

    myCar.set("model", "아우디");

    myCar.set("hp", "500마력");

    myCar.set("type", "세단");

    myHouse.set("car", myCar);

    System.out.println(myHouse.toIndentString(""));

}


이 테스트 코드들이 의도하는 바는 다음과 같다.

우선 Site 클래스를 선언한다. Site 클래스는 각종 위치를 나타내기 위한 상위 클래스 역할이 된다.

House는 Site 클래스를 상속 받아 구현되는 클래스이다.

myHouse는 House의 객체(인스턴스)이다.

Car 클래스는 자동차를 나타내는 클래스이다.

myCar는 Car 클래스의 객체(인스턴스)이다.

myCar는 myHouse 내부에 선언된 속성으로 들어가게 된다. 즉 참조 객체가 된다.


설명이 좀 많긴 하지만 실행해보면 쉽게 알 수 있다. 출력은 다음과 같다.


출력

Class House 우리집 extends Site{

   String owner = 홍길동;

   Integer area = 30평;

   Klass car =    Class Car 내차{

      Integer hp = 500마력;

      String model = 아우디;

      String type = 세단;

   };

   String position = 사당동;

}

출력된 내용은 마치 소스코드와도 같다. 사실 AOM 패턴이 해결하고자 하는 문제가 딱 이런 것이다. 우리는 마치 새로운 클래스를 선언하고 이를 생성해 낸 것과 같은 효과를 얻었다. 이 과정에서 상속이나 참조 객체와 같이 객체지향 언어로 모델링을 할 경우 필요한 특성들까지도 역시 동일하게 확보할 수 있게 되었다.

이러한 특성은 또한 데이터베이스 설계에도 매우 도움이 된다. 실행 함수에서 선언한 정보들을 각각 테이블로 만들어주면 Klass / KlassType / Attribute / AttributeType과 매핑되는 4개의 테이블 만으로도 원하는 기능들을 모두 구현할 수 있다.

'5.디자인패턴' 카테고리의 다른 글

Actor Model 패턴의 구현(Java)  (0) 2016.09.30
Property List 패턴  (0) 2016.09.24
Mediator 패턴  (0) 2016.09.18
Facade 패턴  (0) 2016.09.18
Command 패턴  (0) 2016.09.18
Posted by 이세영2
,