스트레티지 패턴(Strategy Pattern,전략패턴)은 알고리즘군을 정의하고 각각을 캡슐화하여 교환해서 사용할 수 있도록 만들어준다. 스트레티지 패턴을 활용하면 알고리즘을 사용하는 클라이언트와는 독립적으로 알고리즘을 변경할 수 있다.

 

바로 예제를 살펴보자.

 

Duck이라는 추상클래스가 있다고 생각해보자. 해당 추상클래스는 다양한 오리라는 서브클래스를 만들기 위해 여러가지 공통 메소드와 확장을 위한 추상 메소드를 가진 추상클래스이다. 만약 아래와 같은 요청이 있다고 생각해보자.

 

"모든 오리에게 날 수 있는 기능과 오리소리를 내는 기능을 추가해라. 하지만 구현체마다 해당 행동들은 모두 달라질 수 있다"

 

어떤 식으로 구현을 하면 좋을까? 두개의 행동이 모든 구현체마다 달라질 수 있다는데, 추상클래스에서 추상메소드로 만들어줘서 모든 서브 클래스에서 오버라이드 할 수 있게 할까? 그렇다면 어떠한 문제가 생길수 있을까 생각해보자. 문제는 이것이다. 서브 클래스에서 나는 행동과 오리소리를 내는 행동을 오버라이드 한다면 과연 이러한 코드는 재사용이 가능할까? 문제를 다시 한번보자. 

 

"구현체마다 행동들은 모두 달라질 수 있다"

 

달라질 수 있다지, 모든 구현체마다 다르다는 아니다. 이 말은 종류가 다른 오리지만 행동이 같은 교집합이 존재할 수 있다는 것이다. 그렇다면 서브클래스에서 슈퍼클래스의 나는 행동과 오리소리를 내는 행동을 오버라이드한다면 같은 행동을 갖는 다른 서브 클래스들이 동일한 코드가 중복되서 나타날 것이다. 그러면 다음 해결책을 보자.

 

"달라지는 행동들을 별도의 인터페이스로 분리하여 해당 기능이 필요한 오리 서브 클래스에 구현을 강제하자."

 

이 해결책 또한 재활용하기 힘들다. 서브 클래스에서 인터페이스의 구현을 강제하여 나는 행동과 오리소리 행동을 구현한다면 이 역시 재활용할 수 없는 코드가 될 것이다. 즉, 구현 클래스에서만 구현코드가 나타나기 때문에 행동이 같은 다른 서브 클래스에서 해당 코드를 재활용할 수 없다.

 

여기서 사용할 수 있는 디자인 패턴이 스트래티지 패턴(Strategy Pattern)이다. 이 패턴의 핵심은 달라지는 행동부분을 분리하고 캡슐화하여 클라이언트와는 독립적으로 행동을 주입할 수 있게 하는 것이다. 바로 구현 코드를 살펴보자.

 

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
public abstract class Duck {
    
    private FlyBehavior fly;
    private QuackBehavior quack;
    
    public void swim() {
        System.out.println("헤엄친다.");
    }
    
    public void fly() {
        fly.fly();
    }
    
    public void quack() {
        quack.quack();
    }
 
    public void setFly(FlyBehavior fly) {
        this.fly = fly;
    }
 
    public void setQuack(QuackBehavior quack) {
        this.quack = quack;
    }
    
}
cs

 

위의 추상 클래스를 살펴보면 이 추상클래스는 다양한 오리의 구현체의 공통 행동들을 담은 추상클래스이다. 그리고 오리 구현체들마다 달라질 수 있는 행동을 인터페이스로 분리하여 캡슐화하고 있다. 이 말은 상속이나 구현을 강제하는 것이 아니고 인스턴스 변수로 달라지는 행동을 구성(composition)하였다. 그리고 달라지는 행동들은 인터페이스를 구현하여 특정 클래스의 군(알고리즘군)으로 만들어지기 때문에 같은 행동을 하는 다른 오리들이 같은 클래스를 사용하므로 재사용성이 높아진다.

 

1
2
3
4
5
6
7
public interface FlyBehavior {
    public void fly();
}
 
public interface QuackBehavior {
    public void quack();
}
cs

 

달라지는 행동들은 인터페이스로 분리하였다.

 

1
2
3
4
5
6
7
public class ADuck extends Duck{
 
}
 
public class BDuck extends Duck{
 
}
cs

 

추상클래스를 상속받은 오리서브 클래스들은 만들었다. 특징은 ADuck은 헤엄은 칠 수 있지만 날지 못하고 오리소리를 내지 못한다. BDuck은 수영도 가능하고 날수도 있고 울수도 있다. 과연 이러한 달라진 행동들을 실제로 어떻게 사용할까?

 

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
public class NotFlyBehavior implements FlyBehavior {
 
    @Override
    public void fly() {
        System.out.println("못 날아요");
    }
 
}
 
public class DoFlyBehavior implements FlyBehavior {
 
    @Override
    public void fly() {
        System.out.println("난다.");
    }
 
}
 
public class NotQuackBehavior implements QuackBehavior {
 
    @Override
    public void quack() {
        System.out.println("못 운다.");
    }
 
}
 
public class DoQuackBehavior implements QuackBehavior {
 
    @Override
    public void quack() {
        System.out.println("운다.");
    }
 
}
 
 
cs

 

구체적인 행동들을 구현한 클래스 집합군들이다.(나는 행동 집합, 오리소리는 내는 행동 집합)

 

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
42
43
44
45
/*
 * ADuck은 날지못하고 울지도 못한다.
 * BDuck은 날수 있고 울 수도 있다.
 */
public class DuckMain {
 
    public static void main(String[] args) {
        
        Duck a = new ADuck();
        a.setFly(new NotFlyBehavior());
        a.setQuack(new NotQuackBehavior());
        
        
        Duck b = new BDuck();
        b.setFly(new DoFlyBehavior());
        b.setQuack(new DoQuackBehavior());
        
        System.out.println("===========ADuck===========");
        
        a.swim();
        a.fly();
        a.quack();
        a.setFly(new DoFlyBehavior());
        a.fly();
        
        System.out.println("===========BDuck===========");
        
        b.swim();
        b.fly();
        b.quack();
    }
 
}
 
=>결과
===========ADuck===========
헤엄친다.
못 날아요
못 운다.
난다.
===========BDuck===========
헤엄친다.
난다.
운다.
 
cs

 

이렇게 사용 시점에 알고리즘을 set 메소드로 주입하여 사용한다. 만약 추후에 ADuck이 나는 기능이 추가된다면 위 코드와 같이 날수 있는 구현 클래스를 주입해주기만 하면 ADuck은 날 수 있게 된다. 즉, ADuck은 행동을 인터페이스로 의존만 하고 있고(구성,composition) 구체적인 행동을 몰라도 되는 것이다.

posted by 여성게
:

싱글톤(singleton)은 '단독 개체', '독신자'라는 뜻 말고도 '정확히 하나의 요소만 갖는 집합' 등의 의미가 있다. singleton 패턴은 객체의 생성과 관련된 패턴으로서 특정 클래스의 객체가 오직 한 개만 존재하도록 보장한다. 즉 클래스의 객체를 하나로 제한한다. 프로그램에서 이런 개념이 필요할 때는 언제일까? 프린터 드라이버의 예를 들어보자.

 

여러 컴퓨터에서 프린터 한 대를 공유하는 경우, 한 대의 컴퓨터에서 프린트하고 있을 때 다른 컴퓨터가 프린트 명령을 내려도 현재 프린트하는 작업을 마치고 그다음 프린트를 해야지 두 작업이 섞여 나오면 문제가 될 것이다. 즉 여러 클라이언트(컴퓨터)가 동일 객체(공유 프린터)를 사용하지만 한 개의 객체(프린트 명령을 받은 출력물)가 유일하도록 상위 객체가 보장하지 못한다면 singleton 패턴을 적용해야 한다. 이처럼 동일한 자원이나 데이터를 처리하는 객체가 불필요하게 여러 개 만들어질 필요가 없는 경우에 주로 사용한다.

 

사실 싱글톤을 구현하는 방법은 몇 가지 존재한다. 하지만 오늘 소개할 싱글톤 패턴 코드는 가장 많이 사용하고 안전한 싱글톤 객체를 만드는 코드 한가지만 다루어 볼것이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton {
    
    private Singleton() {}
    
    private static class LazyHolder{
        public static final Singleton SINGLETON=new Singleton();
    }
    
    public static Singleton getInstance() {
        return LazyHolder.SINGLETON;
    }
    
}
cs

 

위 코드는 싱글톤으로 생성될 객체를 Lazy Loading 하는 코드이다. 클래스가 로딩될때 LazyHolder라는 내부 클래스는 로딩되지 않는다. 이유는 LazyHolder라는 객체를 참조하여 호출하는 코드가 존재하지 않기 때문이다. 그 말은 진짜 Singleton 객체가 필요하여 getInstance()를 호출할때 LazyHolder 클래스가 로딩되는 것이다. 바로 필요할때 객체를 생성한다는 장점이 있다.

 

또 하나의 장점이 있다. 해당 코드는 다른 싱글톤 생성 코드와 달리 싱글톤 인스턴스가 NULL인지 체크하는 것과 synchronized 블럭을 사용하여 동기화하는 부분도 존재하지 않는다. 왜 일까? 그러면 과연 멀티 스레드 환경에서 안전할까? 안전하다! JVM에서 클래스를 로딩하는 과정에서는 멀티 스레드 환경에서도 안전한 동기화 환경을 제공하기 때문이다! 즉, LazyHolder 클래스가 로딩되는 과정에 static 한 SINGLETON 인스턴스를 생성하기 때문에 싱글톤 객체 생성과정에서 별도의 동기화 처리를 하지 않아도 클래스 로딩되는 과정 자체가 JVM에서 동기화 환경을 제공하기 때문에 멀티 스레드 환경에서도 안전한 것이다!

 

LazyHolder를 이용한 싱글톤 객체 생성 시 장점

  1. 싱글톤 객체가 진짜 필요할 때까지 초기화를 늦춘다.(Lazy Loading)
  2. JVM 클래스 로딩 타임때 싱글톤 객체를 생성하므로 멀티 스레드 환경에 안전한 동기화 이슈를 JVM이 직접 해결해준다.

싱글톤 객체를 생성할 때는 위의 방법을 이용하자!

 

 

기타 싱글톤 객체 생성 코드

나머지 방법들은 멀티 스레드에 안전하지 않으면서도 불필요한 Lock을 잡고 있는 방법들이기 때문에 사용을 권장하지 않는다.

 

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
42
43
44
public class Singleton_methodSync {
    
    private static Singleton_methodSync instance;
 
    private Singleton_methodSync() {}
    
    public static synchronized Singleton_methodSync getInstance() {
        if(instance == null) {
            instance = new Singleton_methodSync();
        }
        return instance;
    }
    
}
 
//Double Checked Lock
public class Singleton_methodSync2 {
    
    private static Singleton_methodSync2 instance;
    
    private Singleton_methodSync2() {}
    
    public static Singleton_methodSync2 getInstance() {
        if(instance == null) {
            synchronized (Singleton_methodSync2.class) {
                if(instance==null) instance = new Singleton_methodSync2();
            }
        }
        return instance;
    }
}
 
//이른 초기화
public class Singleton_precreate {
    
    private static Singleton_precreate instance = new Singleton_precreate();
    
    private Singleton_precreate() {    }
    
    public static Singleton_precreate getInstance() {
        return instance;
    }
    
}
 

public class Singleton_notsafety {        

private static Singleton_methodSync instance;

    private Singleton_methodSync() {}    

    public static Singleton_methodSync getInstance() {

        if(instance == null) {

            instance = new Singleton_methodSync();

        }

        return instance;

    }    

}

 cs
posted by 여성게
:

https://plposer.tistory.com/29

위의 포스트된 글 일부에 컴포지트 패턴이 설명되어있으며, 예제코드도 나와있음.

posted by 여성게
: