Design pattern - Composite pattern

2023. 1. 24. 22:55Development

Design pattern

"객체 지향 프로그래밍 설계를 할 때 자주 발생하는 문제들을 피하기 위해 사용되는 패턴" 디자인 패턴이라는 정의에 대해서 누군가에게 설명 해본 적이 없어 구글링해서 나온 위키에 첫 문구를 발췌 해왔습니다.

 

실제 저도 처음 접한 개념은 객체 지향 프로그래밍에서의 좀 더 효과적이고 우아한 문제 해결을 솔루션세트 라는 느낌이었던 것 같았습니다. 하지만 현재는 패턴이라는 것은 객체 지향 뿐만 아니라 모든 영역에서 문제 해결 방법에 대한 탬플릿과 같은 개념으로 나에게는 정의되어 있는 느낌입니다.

 

사실 놀라운 발명이라고 하기에는 의외로 누구나 생각해낼 수 있는 아이디어에 가까운 것 같고, 소프트웨어 개발자들이 특정 유사한 상황에서 많이들 사용하고 좋은 방향이라고 판단된 구조들을 모아서 정리해서 나오게 된 게 디자인 패턴의 시작으로 알고 있습니다.

 

실제로 사용시도 조금씩 개인의 필요에 맞게 어느 정도 변형을 해서도 사용하기도 하며, 여러 변칙적인 디자인이 계속 생겨나고 있습니다. 현존하는 모든 디자인 패턴을 다 아는 것이 중요한 것이 아니라 특정 디자인이 추구하는 바를 이해하고, 당사자가 처한 상황에 맞게 응용할 수 있는 능력이 중요하지 않을까 생각합니다.

 

앞으로 뭔가를 만들면서 특정 Pattern을 응용하게 될 기회가 생기면 이를 하나씩 기록에 두려고 합니다.

Composite pattern

Composite pattern의 기본적인 디자인은 아래와 같습니다. (WIKI에서 발췌)

Component가 Leaf을 가지거나, 또 다른 Component를 가질 수 있습니다.
아주 간단한 개념이지만 이 자료구조로 굉장히 많은 것을 표현하는 데 유용합니다.

 

Android의 View도 이와 같은 구조로 만들어져 있습니다.

View는 child view를 가지기도 하며, ViewGroup을 가지기도 합니다. 그리고 ViewGroup 또한 View 입니다.

파일 시스템의 폴더 구조도 이런 형태로 표현할 수 있을 것 입니다.

 

폴더 안에는 파일이 있을 수 있고, 또 다른 폴더가 있을 수 있습니다.

 

흔히 말하는 Tree 구조로 나타낼 수 있는 모든 형태가 해당 디자인 패턴으로 표현하기에 용이합니다.

사용해보기

내가 만들고자 하는 프로그램은 핸드폰 메시지를 포워딩해주는 App입니다.
참고로 Android native app 이므로 코드는 java로 작성 되었습니다.

해당 App에서 정책을 저장하는 구조가 이 Composite pattern으로 나타내기에 적합하다고 판단되어 사용해보았습니다.

 

설명을 덧붙이자면 핸드폰에 SMS가 도착했을 때, 미리 정해진 정책을 보고 특정 조건에 부합되면 지정된 사람에게 해당 SMS를 전달해주는 App에서 정책을 저장하기 위한 자료구조입니다.

 

정의하고자 하는 룰에는 3가지 종류가 있습니다.

 

Msg : 특정 문구를 찾아주는 룰
Sender : 특정 발신인을 찾아주는 룰
Group : Msg, Sender를 And, Or 조건으로 묶어주기 위한 룰 set

 

기본적으로 하나의 룰에게 체크를 요청하면, 그 룰은 복수개의 룰을 And 조건이나 Or 조건으로 검사를 하게 되고 검사를 하게 되는 룰 중에는 Group 룰이 있을 수 있고 이때는 다시 재귀적으로 조건 검사를 해들어가게 되는 형태 입니다.

 

예를 들어 삼성카드 승인 신용카드 문자를 찾아내기 위한 룰을 정의해보겠습니다.
이럴 일은 잘 없지만;; 확인해보니 삼성카드 문자는

"삼성 카드 ----" , "삼성카드 ----" 두가지 시작 문구로 옵니다.

 

그럼 룰은 "삼성 카드", "삼성카드" or 조건으로 묶어서 하나의 그룹을 만들게 됩니다.
그리고 문자내용 중에 "승인"이 들어있는 경우에만 해당 함으로 마지막에 이를 And로 정의해야 합니다.

 

정리하면... ("삼성카드" or "삼성 카드") and "승인" 이 되어야하며, 이를 자료구조로 나타내면

Rule:
  - RuleGroup (and 묶음)
    - RuleGroup (or 묶음)
      - Msg "삼성카드" (포함)
      - Msg "상성 카드" (포함)
    - Msg "승인" (포함) 

이 됩니다. 위와 같은 구조를 담기 위해 Composite pattern을 이용하면 아래와 같습니다.

 

Class diagram 이고, 이를 Code로 옮겨보겠습니다.

 

IRule.java

public interface IRule {
    boolean check(String sender, String msg);
    String ruleType();

    String TYPE_SENDER = "type_sender";
    String TYPE_MSG = "type_msg";
    String TYPE_GROUP = "type_group";
}

RuleGroup.java

public class RuleGroup implements IRule {

    private final String type = IRule.TYPE_GROUP;
    private ArrayList<IRule> rules = new ArrayList<>();
    private boolean isAndConnection = false;

    public void addRule(IRule r) {
        rules.add(r);
    }

    public void removeRule(IRule r) {
        rules.remove(r);
    }

    public void setAndConnection(boolean isAndConnection) {
        this.isAndConnection = isAndConnection;
    }

    ArrayList<IRule> getRules() {
        return rules;
    }

    @Override
    public boolean check(String sender, String msg) {
        for(IRule r : rules) {
            if(r.check(sender, msg)) {
                if(!isAndConnection) {
                    return true;
                }
            } else {
                if(!isAndConnection) {
                    continue;
                }
                return false;
            }
        }
        return isAndConnection;
    }

    @Override
    public String ruleType() {
        return type;
    }
}

RuleMsg.java

public class RuleMsg implements IRule {
    enum CHECK_TYPE {
        CONTAIN,
        PREFIX,
        POSTFIX
    }
    private final String type = IRule.TYPE_MSG;
    private CHECK_TYPE check_type;
    private String text;

    public void setMsg(String text) {
        this.text = text;
    }

    public void setType(CHECK_TYPE type) {
        check_type = type;
    }

    String getMsg() {
        return text;
    }

    @Override
    public boolean check(String sender, String msg) {
        switch (check_type) {
            case CONTAIN:
                return msg.contains(text);
            case PREFIX:
                return msg.startsWith(text);
            case POSTFIX:
                return msg.endsWith(text);
        }
        return false;
    }

    @Override
    public String ruleType() {
        return type;
    }
}

RuleSender.java

public class RuleSender implements IRule {

    private final String type = IRule.TYPE_SENDER;
    private String match;

    public void setSender(String match) {
        this.match = match;
    }

    String getSender() {
        return match;
    }

    @Override
    public boolean check(String sender, String msg) {
        return sender.contains(match);
    }

    @Override
    public String ruleType() {
        return type;
    }
}

이제 만들어진 Class를 이용해서 테스트를 해보겠습니다.

Test code

RuleMsg msg1Rule = new RuleMsg();
msg1Rule.setMsg("삼성카드");
msg1Rule.setType(RuleMsg.CHECK_TYPE.CONTAIN);
RuleMsg msg2Rule = new RuleMsg();
msg2Rule.setType(RuleMsg.CHECK_TYPE.CONTAIN);
msg2Rule.setMsg("삼성 카드");

RuleGroup subRule = new RuleGroup();
subRule.addRule(msg1Rule);
subRule.addRule(msg2Rule);
subRule.setAndConnection(false);

RuleMsg msg3Rule = new RuleMsg();
msg3Rule.setMsg("승인");
msg3Rule.setType(RuleMsg.CHECK_TYPE.CONTAIN);

RuleGroup mainRule = new RuleGroup();
mainRule.addRule(subRule);
mainRule.addRule(msg3Rule);
mainRule.setAndConnection(true);

System.out.println(mainRule.check("", "삼성카드 승인"));
System.out.println(mainRule.check("", "삼성 카드 승인"));
System.out.println(mainRule.check("", "삼성 카드 수락"));
System.out.println(mainRule.check("", "삼성카드 취소"));
System.out.println(mainRule.check("", "삼성 카드 취소"));
System.out.println(mainRule.check("", "현대 카드 승인"));
System.out.println(mainRule.check("", "카드취소"));

의도에 맞게 아래와 같이 출력됨을 확인할 수 있습니다.

true
true
false
false
false
false
false

조합에 따라서는 자칫 굉장히 복잡 해질 수 있는 Rule을 판단하기 위한 코드이지만,


Composite 패턴으로 이를 담아 룰들을 관리해 줌으로써 의외로 true, false를 판단해주는 Logic부분은 아주 심플하게 처리가 됨을 확인할 수 있습니다.

'Development' 카테고리의 다른 글

Server test용 General Mock server 만들기  (0) 2023.01.24
GSON - Composite pattern class  (2) 2023.01.24
golang GO routine channel 테스트  (0) 2023.01.24
OIDC Login 구현해보기 Part-3  (1) 2023.01.24
OIDC Login 구현해보기 Part-2  (0) 2023.01.24