/**

 * 비밀번호 문자, 특수문자, 숫자 2가지 이상 조합

 * @param src - 비밀번호 문자 String src

 */

public boolean checkPassWordFrom(String src) {

    Pattern p = Pattern.compile("(?:[a-zA-Z]+[0-9]+$)|(?:[a-zA-Z]+[^a-zA-Z0-9\\n]+$)|(?:[0-9]+[a-zA-Z]+$)|(?:[0-9]+[^a-zA-Z0-9\\n]+$)|(?:[^a-zA-Z0-9\\n]+[a-zA-Z0-9]+$)");

    Matcher m = p.matcher(src);

    return m.find();

}

'Developer > Java' 카테고리의 다른 글

데드락(Dead Lock)이란?  (0) 2016.10.27
디자인패턴 - 옵저버 패턴(Observer Pattern)  (0) 2016.09.01
데드락을 이해하기 위해선 락(Lock)의 원리를 이해해야 합니다.
락을 거는 이유는 쉽게 설명하면 다음과 같습니다.

A와 B가 버스표를 예매합니다.
버스표 예매 과정은, 목적지 선택 -> 시간/등급 선택 -> 좌석 선택 -> 결제 입니다.
A와 B가 동시에 서울 10시 우등을 선택하고, 둘다 같은 좌석 20번으로 선택하고 결제합니다. 
A와 B가 버스를 탔는데... 헐!? 표가 같네요 -.-
이러면 안되죠...?

때문에 좌석선택 이후부터 결제가 종료될때까지 서울 10시 우등의 20번 좌석은 락을 겁니다.
A가 변심하여 20번 좌석을 택하지 않고 21번 좌석으로 변경한다면 B는 20번 좌석을 선택할 수 있고, 그렇지 않으면 B는 20번 좌석이 아닌 다른 좌석을 선택해야 하죠.
( 사실 이 예제는 DB의 트랜젝션에 더 어울리는 예제지만, 트랜젝션도 락의 일종이라고 보면 됩니다. 오히려 이 예제를 통해 DB 트랜젝션에 대한 이해도 동시에 될 수 있습니다 )

통칭 락이라고 부르는 종류에는 대표적으로 두가지가 있습니다.

뮤텍스(mutex) 그리고 세마포어(semaphore)인데요. ( 이외에도 객체락인 atomic, interlocked 등 여러가지 기술적인 부분이 더 존재하긴 합니다만 생략합니다 )

뮤텍스는 1:1 락, 세마포어는 1:N 락이라고 생각하시면 됩니다.
뮤텍스는 오직 하나만 접근이 가능, 세마포어는 지정된 수만큼 접근이 가능합니다.
뮤텍스는 위 예제로 이해하시면 되겠고, 세마포어는 성질이 다릅니다.

예를들어 명품백 매장이 있고, 매장안에는 5명의 직원이 있습니다. 저도 정확히는 모르지만, 아마 손님과 직원이 1:1로 제품 설명을 하고 고객관리를 하는듯 하는데, 이 경우가 세마포어의 예랑 밀접하다고 보시면 됩니다.
밖에 줄을 주르륵 서있는건 매장안에 출입가능한 손님수는 5명이죠.
이때 입구에서 입장을 안내하는 직원이 세마포어인겁니다.

이정도면 이해가 되셨으리라 믿고요,

데드락 현상은 락이 풀리지 않는 현상을 말합니다. ( 락이 풀리지 않으면 로직이 정상작동하지 않고, 로직이 정상작동하지 않으면 프로그램이 죽었음을 의미하기 때문에 dead-lock이라고 합니다 )

첫번째 예제의 버스 예매를 예로 들어보면, 
* 예 1
1. 좌석 선택후 결제 화면으로 넘어갈때 락이 걸립니다.
2. 결제화면이 뜨고, 결제를 진행합니다.
3-1. 결제가 성공하고 락을 풉니다. ( 정상 종료 )
3-2. 결제가 실패하여 안내 메세지를 뿌리고 처음 화면으로 돌아갑니다. ( 잘못된 프로그래밍 )
4. 3-2 이후 대기하고 있던 좌석선택 대기자는 데드락에 빠집니다.

* 예 2
1. 좌석 선택 전체를 락을 겁니다. ( 잘못된 프로그래밍 )
2. 좌석 선택후 결제 화면으로 넘어갑니다.
3. 목적지, 시간, 등급, 좌석 정보를 사용자에게 다시 확인시켜주기 위해 다시한번 데이터를 조회합니다.
4. 좌석 정보를 조회하려는데 이미 락이 걸려있습니다. ( 대기탑니다 )
5. 좌석 선택을 대기타는 사용자도, 결제하려는 사용자도 데드락에 빠집니다.

대표적으로 많이 실수하는 예제 두가지를 들었습니다.
1번의 실수가 특히! 더 많기 때문에 lock guard를 사용하는 등의 기법들이 많이 등장했죠.
2번의 실수는 프로그래머 실수이고, 되도록 발생하지 말아야 하지만, 이 또한 recursive-lock이라는 개념(같은 쓰레드 같은 락 객체의 경우 대기타지 않도록 하는 lock)이 등장하여 프로그래머의 실수를 예방해주지만 성능에 영향이 있습니다.

마지막으로 기억에 남는 질문과 답변글중에, concurrent hash map을 사용하던걸 fast map으로 바꿨더니 데드락 현상이 사라졌다는 글이 있었습니다.
결론만 먼저 말씀드리면 매우 위험합니다.

예를들어 fast_map을 이용해 자판기(시장)를 이용한다고 해봅니다.
A 유저가 축데이를 1아덴에 올려놓습니다.
B, C 유저가 동시에 축데이를 구매합니다.
락이 없으므로 A유저는 1아덴, B, C유저는 축데이 각각 1장씩 얻습니다.

shop => fast_map

0. if( A.shop[ 축데이 ].갯수 > 0 ) - B, C 동시 통과
1. B.아데나 -= A.shop[ 축데이 ].가격;
1. C.아데나 -= A.shop[ 축데이 ].가격;
2. A.shop[ 축데이 ].갯수--; ( A.shop[ 축데이 ].갯수 는 0 이하로 됨 )
3. B.인벤토리.add( 축데이 )
3. C.인벤토리.add( 축데이 )
4. B, C 로직 종료

이런 현상이 발생하죠.

concurrent_hash_map 을 씁니다.
0. if( A.shop[ 축데이 ].갯수 > 0 ) - B통과 C 대기 ( 축데이 객체 락 )
1. B.아데나 -= A.shop[ 축데이 ].가격;
2. A.shop[ 축데이 ].갯수--; ( A.shop[ 축데이 ].갯수 는 0 이하로 됨 )
3. B.인벤토리.add( 축데이 )
4. B 로직 종료 - 축데이 객체 락 해제
5. 축데이 갯수 0개
6. C 로직 종료 

즉.. 락을 걸지 않았을때 나타나는 버그는 데드락보다 훨씬 위험하고 잡기 어렵습니다.
참고하시고 근본적인 원인을 해결하는 방법을 생각하는 것을 권유드립니다.

* 요약
1. 락은 데이터 무결성 위해 꼭 필요하다 ( 멀티쓰레드 환경 )
2. 락이 무기한 잠기어 프로그램이 정상작동하지 않는 상태를 데드락이라고 한다.
3. 데드락 상태에 빠지지 않도록 설계하고 코딩하는것이 제일 중요하다.


디자인 패턴 중 옵저버 패턴(Observer Pattern)을 알아보자.

객체지향 설계를 하다보면 객체들 사이에서 다양한 처리를 할 경우가 많다.

예를 들어 한 객체의 상태가 바뀔 경우 다른 객체들에게 변경됐다고 알려주는 경우를 들 수 있다.



상태를 가지고 있는 주체 객체와 상태의 변경을 알아야 하는 관찰 객체(Observer Object)가 존재하며 이들의 관계는 1:1이 될 수도 있고 1:N이 될 수가 있다.

서로의 정보를 넘기고 받는 과정에서 정보의 단위가 클 수록, 객체들의 규모다 클 수록, 각 객체들의 관계가 복잡할 수록 점점 구현하기 어려워지고 복잡성이 매우 증가할 것이다. 이러한 기능을 할 수 있도록 가이드라인을 제시해 주는 것이 바로 옵저버 패턴이다.



옵저버 패턴이라. 옵저버... 스타크래프트 프로토스에서 나오는 정찰기 유닛 이름이기도 하고, 단어 뜻 의미 자체를 봐도 "관찰자"를 의미한다. 이 패턴을 이용하면 마치 깜깜한 스타크래프트의 맵을 밝혀주는 것 처럼 주체 객체로부터 정보를 받아올 수 있는 것인가??????

이 패턴은 과연 개발을 하면서 밀접하게 사용되는가 궁금해졌다. Head First Design Pattern 서적을 참고하면서 나름대로 정리해보겠다. 도대체 어디에 쓰이는 패턴이며 어떻게 구현하는가?



일단, 위에서 언급한 주체 객체, 관찰 객체라는 의미부터 파악해보자. 이러한 관계들을 우리는 일상생활에서 흔히 발견할 수 있다.

잡지를 발행하는 잡지사와 그 잡지를 매달 구독하는 독자들과의 관계, 이와 비슷하게 우유를 제공하는 우유배달업체와 고객들, 신문사와 정기구독자들,

어떤 인터넷 까페에 속한 회원과 매달 공지사항을 날려주는 운영진들과의 관계......무수히 많은 케이스가 있다.

어떤 정보를 알고자 혹은 얻고자 하는 고객들은 Publishing 해주는 존재와의 관계를 맺고(계약 등) 꾸준히 정보를 얻어간다. 이렇게 지속되다가 더 이상 정보를 원하지 않을 경우 관계를 끊을 수 있다.(계약 해지)

객체와의 관계를 맺고 끊으며 객체의 상태가 변경되면 그 정보를 Observer(Subscribe)에게 알려주는 방법을 알아보자.





예시


위의 수많은 예들 중, 뉴스를 예로 들어보자. 매일매일 새로운 뉴스를 제공하는 뉴스머신(?)이 있다고 가정하자. 이 머신은 새로운 뉴스를 취합하여 정보를 가지고 있다. 그리고 이 뉴스머신에서 나온 뉴스를 구독하고 싶어하는 Observer를 정의해보자. 이들은 뉴스머신이 새로운 뉴스를 생성할 때마다 이 소식을 바로 받을 수 있다.

그렇다면 일단 NewsMachine 이라는 객체와 Observer라는 객체가 필요하겠지?? 이들을 클래스로 만들면 되는것인가?


객체지향설계를 하기전에 저번에 배웠던 스트레티지 패턴을 다시 상기시켜보자. (스트레티지 패턴 : http://flowarc.tistory.com/entry/1-Strategy-Pattern)

인터페이스를 이용하여 객체간의 느슨한 결합(Loose Coupling)을 권장했다. 즉 상속을 통한 구현이 아닌 구성(Composition)을 이용한 것이다. 구성이란 A에 B가 있다 라는 것을 의미하며 한 객체에 객체를 포함시키는 것이 아닌 인터페이스를 포함하는 방식으로 많이 사용된다.

우리가 사용하려는 옵저버 패턴에서도 이러한 방식처럼 인터페이스를 활용한다. 여기서는 크게 두 가지의 역할을 한다. 하나는 Publisher의 역할, 또 하나는 Observer의 역할이다. 일단 이들의 인터페이스를 정의한 후 이를 implement한 클래스들을 이용한다. 


Publisher라는 인터페이스는 Observer들을 관리하는 Method를 가지고 있을 것이다. 구독을 원하는 Observer를 받아 등록시키고(add), 명단에서 제외하고(delete), 등록된 Observer들에게 정보를 알려주는(notifyObserver) 이렇게 3가지의 Method를 정의할 수 있다.

Observer 인터페이스는 정보를 업데이트 해주는 Update method를 가지고 있을 것이다. 이를 토대로 클래스 다이어그램을 그린다면 아래와 같이 나온다.








Publisher를 구현한 NewsMachine 클래스는 정보를 제공하는 Publisher가 되며 Observer객체들을 가지고 있다. 그리고 Observer 인터페이스를 구현한 AnnualSubscriber(매년 정기구독자), EventSubscriber(이벤트 고객) 클래스는 NewsMachine이 새로운 뉴스를 notifyObserver() 를 호출하면서 알려줄 때마다 Update가 호출된다. 이를 토대로 JAVA로 코딩해보자.




1. Observer 인터페이스 정의


1
2
3
public interface Observer {
    public void update(String title, String news);
}



2. Publisher 인터페이스 정의

1
2
3
4
5
public interface Publisher {
    public void add(Observer observer);
    public void delete(Observer observer);
    public void notifyObserver();
}




3. NewsMachine 클래스


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class NewsMachine implements Publisher {
    private ArrayList<Observer> observers;
    private String title;
    private String news;
     
    public NewsMachine() {
        observers = new ArrayList<>();
    }
 
    @Override
    public void add(Observer observer) {
        observers.add(observer);
    }
 
    @Override
    public void delete(Observer observer) {
        int index = observers.indexOf(observer);
        observers.remove(index);
    }
 
    @Override
    public void notifyObserver() {
        for(Observer observer : observers) {
            observer.update(title, news);
        }
    }
     
    public void setNewsInfo(String title, String news) {
        this.title = title;
        this.news = news;
        notifyObserver();
    }
 
    public String getTitle() {
        return title;
    }
 
    public String getNews() {
        return news;
    }
}




4. AnnualSubscriber 클래스


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class AnnualSubscriber implements Observer {
    private String newsString;
    private Publisher publisher;
     
    public AnnualSubscriber(Publisher publisher) {
        this.publisher = publisher;
        publisher.add(this);
    }
     
    @Override
    public void update(String title, String news) {
        this.newsString = title + " \n -------- \n " + news;
        display();
    }
 
    private void display() {
        System.out.println("\n\n오늘의 뉴스\n============================\n\n" + newsString);
    }
}




5. EventSubscriber 클래스


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class EventSubscriber implements Observer {
    private String newsString;
    private Publisher publisher;
     
    public EventSubscriber(Publisher publisher) {
        this.publisher = publisher;
        publisher.add(this);
    }
     
    @Override
    public void update(String title, String news) {
        newsString = title + "\n------------------------------------\n" + news;
        display();
    }
     
    public void display() {
        System.out.println("\n\n=== 이벤트 유저 ===");
        System.out.println("\n\n" + newsString);
    }
}




위의 클래스 다이어그램을 구현했다. 이제 Main에서 한번 활용해보자.


6. void main()

1
2
3
4
5
6
7
8
9
10
public class MainClass {
    public static void main(String[] args) {
        NewsMachine newsMachine = new NewsMachine();
        AnnualSubscriber as = new AnnualSubscriber(newsMachine);
        EventSubscriber es = new EventSubscriber(newsMachine);
         
        newsMachine.setNewsInfo("오늘 한파", "전국 영하 18도 입니다.");
        newsMachine.setNewsInfo("벛꽃 축제합니다", "다같이 벚꽃보러~");
    }
}


뉴스머신을 선언하고 한파와 벚꽃 축제라는 2가지의 뉴스가 업데이트 했다. 과연 어떻게 출력 될 것이가?






위와 같이 2가지의 뉴스가 각각 AnnualSubscribe와 EventSubscribe 객체에 잘 전달이 됐다. 이 두 객체가 생성되는 순간 Publisher가 가지고 있는 Observer 목록에 등록이 된 것이다.

자, 여기서 AnnualSubscribe와 EventSubscribe 객체에서 꼭 Publisher 객체를 멤버변수로 선언할 필요가 있을까 궁금증이 생긴다.
생성자에서 add 역할만 하는데 왜 필요할까?

바로 Observer 목록에서 탈퇴할 때 이 Publisher 레퍼런스를 이용하면 편하기 때문이다.
위의 소스에서 EventSubscribe 객체인 es 인스턴스를 탈퇴시킨다면



void main()

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MainClass {
    public static void main(String[] args) {
        NewsMachine newsMachine = new NewsMachine();
        AnnualSubscriber as = new AnnualSubscriber(newsMachine);
        EventSubscriber es = new EventSubscriber(newsMachine);
         
        newsMachine.setNewsInfo("오늘 한파", "전국 영하 18도 입니다.");
         
        // es인스턴스 Observer 목록에서 탈퇴
        newsMachine.delete(es);
        newsMachine.setNewsInfo("벛꽃 축제합니다", "다같이 벚꽃보러~");
    }
}



위와 같이 newsMachine의 delete 를 이용하여 탈퇴해야 한다.

아니 내가 탈퇴하겠다는데!!!!! newsMachine을 이용하는 것은 뭔가 기분이 영 나쁘다. 내 스스로 탈퇴하도록 만들어보자.



EventSubscriber 클래스


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class EventSubscriber implements Observer {
    private String newsString;
    private Publisher publisher;
     
    public EventSubscriber(Publisher publisher) {
        this.publisher = publisher;
        publisher.add(this);
    }
     
    @Override
    public void update(String title, String news) {
        newsString = title + "\n------------------------------------\n" + news;
        display();
    }
     
    // publisher를 이용하여 탈퇴
    public void withdraw() {
        publisher.delete(this);
    }
     
    public void display() {
        System.out.println("\n\n=== 이벤트 유저 ===");
        System.out.println("\n\n" + newsString);
    }
}



이렇게 withdraw 메서드를 호출하면 바로 탈퇴할 수 있다.



void main()


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MainClass {
    public static void main(String[] args) {
        NewsMachine newsMachine = new NewsMachine();
        AnnualSubscriber as = new AnnualSubscriber(newsMachine);
        EventSubscriber es = new EventSubscriber(newsMachine);
         
        newsMachine.setNewsInfo("오늘 한파", "전국 영하 18도 입니다.");
         
        // es인스턴스 Observer 목록에서 탈퇴
        es.withdraw();
//      newsMachine.delete(es);
         
        newsMachine.setNewsInfo("벛꽃 축제합니다", "다같이 벚꽃보러~");
    }
}







위와같이 1번째 소식 이후 탈퇴한 결과 2번째 뉴스부터는 EventSubscriber 객체는 제외되었다.



Publisher에서 정보나 상태가 변경될 때마다 Observer에게 보내는 방식을 우리가 흔히 알고 있는 푸시(Push)라고 부른다.

반면에 Observer에서 정보가 필요할 때마다 Publisher에게 요청하는 방식을 풀(Pull)이라고 한다.

지금까지 예를 들어가며 살펴본건 푸시 방식이었다. 물론 옵저버 패턴으로 풀 방식을 구현할 수 있다. 이는 추후에 다음 포스팅에서 살펴보자.


JAVA에서는 이 옵저버 패턴을 적용한 것들을 기본적으로 제공해준다.

바로 Observable 클래스와 Observer 인터페이스다. 이를 이용하면 직접 인터페이스를 구현하지 않아도 쉽게 옵저버 패턴을 적용할 수 있다. 하지만 장점이 있다면 단점도 있듯이 여기에는 하나의 문제점이 있다. 바로 Observable이 인터페이스가 아닌 클래스로 구현되어있다는 점이다. 결국 상속을 받을 수 밖에 없는데 다른 클래스에서 반드시 상속을 받아야하는 경우에는 사용할 수 없다.(JAVA는 다중상속을 지원하지 않기 때문) 

때문에 적절히 상황을 봐서 사용해야 할 것이다.


이 옵저버 패턴이 쓰이는 곳이 또 어디일까?? 

JAVA의 Swing이나 Android의 View나 Button 등의 위젯의 각종 이벤트를 받을 때 쓰인다. Android 에서 흔히 사용되는 버튼을 살펴보자.

버튼은 항상 클릭이라는 이벤트가 있으며 이 이벤트는 OnClickListener 라는 인터페이스로 구성되어있다. 즉 버튼이라는 객체가 Publisher가 되고 OnClickListener가 Observer가 된다고 볼 수 있다. 버튼에서 상태가 변경(클릭 될 경우)된다면 OnClickListener로 알려준다. 



Button button = (Button) findViewById(xx);

button.setOnClickListener( new OnClickListener() {

@Override

public void onClick(...){

// Action..

}

});



위와 같이 버튼에 OnClickListener라는 Observer를 등록하는 경우가 대표적인 옵저버 패턴을 적용한 것이다. 이를 응용하여 커스텀 리스너를 선언할 수도 있다.

이제 옵저버 패턴을 배웠으니 Android에서 한번 사용해 보자. 

Drawer에서 버튼을 클릭할 경우 Activity로 어떠한 동작을 해야할 경우 커스텀 리스너를 인터페이스로 정의하고 활용하면 될 것이다.




정리


  • 옵저버 패턴은 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들에게 연락이 가고 자동으로 정보가 갱신되는 1:N 의 관계를 정의한다.
  • 연결은 인터페이스를 이용하여 느슨한 결합성을 유지한다. 주체, 옵저버 인터페이스를 적용한다.
  • 옵저버 패턴은 푸시 방식과 풀 방식으로 언제든지 구현할 수 있다.
  • JAVA에서 기본으로 Observable 클래스와 Observer 인터페이스를 제공한다.
  • Swing, Android 등에서 UI관련된 곳에서 이 옵저버 패턴이 많이 사용된다. (물론 이보다 더 많다)


'Developer > Java' 카테고리의 다른 글

비밀번호 문자, 특수문자, 숫자 2가지 이상 조합  (0) 2016.11.02
데드락(Dead Lock)이란?  (0) 2016.10.27

+ Recent posts