Thread (스레드) 설정

ThreadPoolExecutor는 작업자 스레드 개수와 풀의 생성과 종료뿐만이 아니라, 모든 스레드의 속성도

정의한다.  흔히 설정하는 응용프로그램 동작은 UI스레드와 경쟁하지않도록 스레드 우선순위를 낮추는 것이다.



작업자 스레드는 ThreadFactory 인터페이스의 구현을 통해 설정된다. 스레드 풀은 우선순위,이름,

예외 핸들러와 같은 작업자 스레드의 속성을 정의할 수 있다.



Thread 속성을 customize 한 고정 스레드풀



class LowPriorityThreadFactory implements ThreadFactory{
private static int count = 1;

public Thread newThread(Runnable r){
Thread t = new Thraed(r);
t.setPriority(4);
t.setUncaughtExceptionHandler(new Thraed.UncaughtExceptionHandler(){}
@Override
public void uncaugghtException(Thread t, Throwable e){
Log.d(TAG, "Thread = "+t.getName() + ", error="+e.getMessage());
});
return t;
}
}

Executors.newFixedThreadPool(10, new LowPriorityThreadFactory());




스레드 풀은 많은 스레드를 가지고 있고, 그 스레들은 실행시간을 두고 UI 스레드와 경쟁하기 때문에,

일반적으로 작업자 스레드에는 UI 스레드보다 낮은 우선순위를 할당하는 것이 좋다.


우선순위가 커스텀 ThreadFactory보다 낮지 않은 경우, 작업자 스레드는 기본적으로 UI 스레드와  같은 우선순위를 얻는다.



ThreadPoolExecutor 확장

ThreadPoolExecutor는 주로 독립적으로 사용되지만, 프로그램이 실행자 또는 실행자의 태스크를 추적할 수 있도록 확장될 수 있다. 응용프로그램은 스레드가 실핼될 때 마다 취하는 동작을 추가하기 위해 다음 메서드를 정의할 수 있다.



void beforeExecute(Thread t, Runnable r)

스레드를 실행하기전에 런타임 라이브러리에 의해 실행된다.



void afterExecute(Runnable r, Throwable t)

스레드가 정상적으로 또는 예외에 의해 종료된 후 런타임 라이브러리에의해 실행된다.


void terminated()

스레드 풀 종료 후 실행중이거나 실행을 위해 대기중인 태스크가 없을떄 런타임 라이브러리에 의해 실행된다.




처음 두메서드에는 Thread와 Runnable 객체가 전달된다. 전달되는 순서가 두 메서드에서 반대라는 점에 유의해라.

스레드 풀에서 얼마나 많은 태스크가 현재 실행되는지 추적하는 기초적인 커스텀 스레드 풀을 보여준다.



스레드 풀에서 진행중인 태스크의 수를 추적하는 예제 code


public class TaskTrackingThreadPool extends ThreadPoolExecutor{

private AtomicInteger mTaskCount = new AtomicInteger(0);

public TaskTrackingThreadPool(){
super(3, 3, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue());
}
@Override
protected void beforeExecute(Thread t, Runnalbe r){
super.beforeExecutor(t,r);
mTaskCount.getAndIncrement();
}

@Override
protected afterExecute(Runnable r, Throwable t){
super.afterExecute(r,t);
mTaskCount.getAndDecrement();
}

public int getNbrOfTasks(){
return mTaskCount.get();
}


}



beforeExecute는 태스크 실행전에 mTakseCoutner를 증가시키고 afterExecute는 실행후에

카운트를 감소시킨다. 


외부 관찰자는 언제든 getNbrOfTasks를 통해 현재 실행 태스크의 수를

요청할 수 있다. 


작업자 스레드와 외부 관찰자 스레드는 동시에 공유멤버변수를 접근할 수 있다.

따라서 스레드 안전을 보장하기 위해 AtomicInteger로 정의했다.



커스텀 스레드 풀


ThreadPoolExecutor 설정


ThreadPoolExecutor에 의해 스레드의 생성과 종료뿐만 아니라 Task의 큐잉에도 사용되며 다음과 같은 생성자에서 수행된다.



 ThreadPoolExecutor executor = new ThreadPoolExecutor(

   int corePoolSize, //핵심풀크기

   int maximumPoolSie,  //최대풀크기

   long keepAliveTime,  //생존 유지시간

   TimeUnit unit,  //생존 유지 시간의 단위

   BlockingQueue workQueue; //태스크 큐 유형

);




핵심풀 크기

스레드 풀에 포함되는 스레드의 하한, 실제로 스레드풀은0개 스레드로부터

시작하지만, 핵심 풀 크기에 도달하면 스레드 개수는 하한 이하로 떨어지지않는다.

풀에서 작업자 스레드의 개수가 핵심 풀크기보다 적을때, 태스크가 큐에 추가되면, 작업을 기다리는 유휴스레드가 있는경우에도 새로운 스레드가 생성될 것이다. 일단 작업자 스레드의 개수가 핵심풀크기보다 같거나 많아지면,

큐가 가득찬경우 새로운 작업자 스레드가 생성된다. 즉 큐는 스레드 생성에 대한 우선권을 얻는다.



최대 풀 크기

동시에 실행할 수 있는 스레드의 최대개수.

최대 풀 크기에 도달한 후, 큐에 더해진 태스크는 태스크를 처리할  유휴 스레드가 사용가능해질 떄까지 큐에서 기다린다.



최대 유휴시간(생존 유지시간)

유휴 스레드는 처리하기 위해 들어오는 태스크를 준비하기 위해 활성 상태를 유지하지만, 생존시간이 설정된 경우 시스템은 비핵심 풀 스레드를 회수할 수 있다.

생존시간은 TimeUnit에 의해 설정된 시간 단위로 측정된다.



태스크 큐 유형

Task가 작업자 스레드에 의해 처리될 수 있을때까지 소비자에 의해 더지핸 Task를 보유하는 BlockingQueue의 구현.



- 스레드 풀 설계

스레드 풀은 동시에 백그라운드 Task를 실행해야 하는 스레드를 관리하는데 

도움을 주지만, 사용자는 여전히 제한된 자원 소비와 높은 처리량을 얻기 위해 현명하게

설정해야 한다. 기본적인 목표는 필요한 것보다 더 많은 메모리를 사용하지 않고

하드웨어에 의해 허용되는 가장높은 속도로 작업을 처리하는 스레드 풀을 생성하는 것이다.




크기나스레드풀의 최대크기를 정한다. 스레드의 최대 개수가 너무작으면

충분한 속도로 큐에서 태스크를 꺼내지않아 성능저하될수있따.


예를들어 모든 스레드가 긴 I/O연산을 실행하는 경우, I/O 동작이 완료될떄까지 실행시간을 얻지 못하고 

기다리는 짧은 수명의 태스크가 있을 수 있다.


반면 너무 많은 스레드도 CPU가 실행 대신 스레드의 전환에 많은 시간을 사용해야하므로 성능에 부정적인 영향을 미칠 수 있다.


스레드 풀의 크기는 하부의 하드웨어, 좀더 정확히말해 사용가능한 CPU 개수를 기준으로 하는것이 좋다. 안드로이드는 Runtime 클래스에서 CPU개수를 알아낼 수 있다.


int N  = Runtime.getRuntume().availableProcessors();


N은 실제로 동시에 실행될 수 있는 태스크의 최대 개수다.


스레드 풀의크기 N은 독립적이고 비차단적인 태스크들의 운용하기에 충분할수있다.

(고도의 연산을 요하는등)



역동성



제한 또는 무제한 태스크 큐

스레드풀은 일반적으로 제한 또는 무제한 태스크 큐와 함꼐 사용한다.

무제한 큐는 무한증가할 수 있어서 메모리가 고갈할 수 있는 반면, 제한 큐의 자원소비는

더 잘관리될 수 있다 한편 제한 큐는 그 크기와 포화정책을 모두 준비해야하낟.

포화정책이란 거부된 태스크를 생산자가 어떻게 처리할지를 뜻한다.



제한 또는 무제한의 큐를 구현한 것이 LinkedBlockingQueue, PriorityBlockingQueue, ArrayBlockingQueue다.

뒤의 두가지 큐는 제한 큐고, 첫번째 큐는 기본적으로 무제한 큐지만 제한 큐로 구성할 수 있다.


스레드 풀


Thread pool은 task 큐와, 작업자 스레드 집합의 조합.


작업자 스레드는 생산자-소비자 구조를 형성한다.


생산자는 큐에 Task를 추가하고, 작업자 스레드는 새로운 백그라운드 실행을

수행할 준비가 된 유휴 스레드가 있을 때마다 Task를 소비한다.




작업자 스레드 풀은, 태스크를 실행하는 활성 스레드와 실행을 기다리는 유휴 스레드를 모두포함할 수 있다.



스레드 풀의 장점

- 작업자 스레드는 실행할 다음 태스크를 기다리기 위해서, 살아 있을 수 있음. 이는

스레드가 매 태스크를 위해 생성과 파괴(오버헤드 증가)될 필요 없다는 것을 의미한다.


- 스레드 풀은 스레드의 최대개수로 정의된다. 이는 응용프로그램 메모리를 소비하는

백그라운드 스레드 수가 많아져서 스레드 풀에 과부하가 걸리는것을 막기 위해서다.


-모든 작업자 스레드의 생명주기는 스레드 풀 생명주기에 의해 제어된다.



미리 정의된 스레드 풀

Executor 프레임워크는 Executors 팩토리 클래스에서 만들어진 미리 정의된 스레드 풀 유형을 포함한다.


- 고정 크기

고정크기 스레드 풀은, 사용자가 정의한 개수의 작업자 스레드를 유지한다.

종료된 스레드는 작업자 스레드의 일정 스레드 수를 유지하기 위해 새로운 스레드로 대체딘다.

고정된 풀 유형은 Executors.nexFixedThreadPool(n) 으로 생성되며 n은 스레드 수다.


이 스레드 풀 유형은 무한한 태스크 큐를 사용한다. 즉 새로운 태스크가 추가로 더해지면 큐가 자유롭게 증가할수있다.

그러므로 생산자는 태스크를 삽입하는데 실패하지 않는다.



- 동적 크기

동적 크기의 스레드 풀, 즉 캐시된 스레드 풀은 처리할 태스크가 있을 때 새로운

스레드를 만든다. 유휴 스레드는 실행할 새로운 태스크를 60초간 기다리고, 태스크 큐가 비어있는

경우 종료된다. 따라서 스레드 풀은 실행할 태스크 수와 함꼐 늘어나고 줄어든다.

Executors.newCachedThreadPool()로 생성한다.




- 싱글스레드 실행자

이 풀은 큐에서 task를 처리하기위해 하나의 작업자 스레드를 가진다.

태스크는 차례대로 실행되고 스레드 안전이 침해될수없다.

Executors.newSingleThreadExecutor()로 생성한다.

들어가기 앞서

안드로이드 앱은, 자바의 멀티스레드 프로그래밍 모델을 준수해야합니다. 자바가 편하긴하지만 동시프로그램 처리, 데이터 일관성 유지, Task 시퀀스 설정등 복잡성이 증가하는 단점이 있습니다. 이는 개발자가 고려하여 최대한 복잡성은 줄이고 성능을 높일 수 있도록 구현해야할 것입니다.



스레드 기본사항

보통 안드로이드에서는, 하나의 스레드에서 실행하는 작업의 단위를 TASK라고 지칭합니다. 

스레드는 순차적으로 하나 또는 다수의 태스크를 실행할 수 있습니다.



실행

자바에서는 run method 안에서 태스크를 정의함으로 구현할 수 있다.


private class MyThreadTask implements Runnable{

   public void run(){

int j = 0; // 스레드에서의 지역스택에 저장됨.

   }

}


run() 메서드 안에서 호출되는 지역변수는 스레드의 지역 메모리 스택에 저장됩니다. 태스크의 실행은 Thread 객체생성과 start() 메서드 호출로 

실핼할 수 있습니다.


Thread myThread = new Thread(new MyThreadTask ());

myThread.start();


스레드의 지역데이터를 저장하는 전용메모리가 있습니다. 


CPU는 한번에 하나의 스레드 명령어를 처리할 수 있습니다.


스레드의 변경을 문맥교환, Context Switch라고 합니다.

하나의 프로세서에 동시에 실행되는 두개의 스레드를 코드와 그림으로 보시겠습니다.

Thread T1 = new Thread(new MythreadTask());

T1.start();

Thread T2 = new Thread(new MythreadTask());

T2.start();



스레드 T1 ------                   ------------

                        ||||(문맥교환시간)

스레드 T2                  ------                  -----


      시간


T1과 T2사이에는 CPU가 사용하는 문맥교환시간이 존재해서 T1이 끝나고 바로 T2가 실행되는게 아니라 약간의 문맥교환 오버헤드가 드는것을 확인하실 수 있습니다.(위에 |||| ) 




싱글 스레드 애플리케이션

각 앱은, 하나이상의 스레드를 가집니다. 또한, 사용자의 반응성을 고려해서 멀티스레드 환경으로 구현해야하는 경우도 존재합니다.



멀티스레드 애플리케이션

동작이 동시에 실행되는것으로 인식되도록 앱 코드를 여러코드 경로로 분할하는 애플리케이션입니다. 실행 스레드 수가 프로세서의 수를 초과하면 완벽한 동시성이 될수는 없습니다. 

멀티스레드는 필수적이긴하나 그에따른 복잡성 증가는 개발자가 풀어야 할 과제입니다.



자원소비증가

각 스레드는 지역변수등을 저장하는 전용 메모리영역을 할당받습니다. 프로세서 측면에서는 스레드 설정 및 해제, 문맥 교환에서 스레드를 저장하고 복원시, 오버해드가 생기게됩니다. 스레드가 많아질수록, 더많은 문맥교환이 일어나고 성능이 저하되게 됩니다.



복잡성 증가

코드를 분석하기 어려워집니다. 또한 실행에 불확실성을 가져다 줍니다.



데이터 불일치

두개의 스레드가 공유자원을 사용할 경우, 어떤 순서로 데이터를 접근하는지 알수없게됩니다. 하나의 공유변수를 두개의 스레드가 접근할때 경쟁조건에 노출되게 됩니다. 

그래서 공유변수에접근할때 다른스레드가 접근할수없도록, 원자영역을 만드는것이 필수적입니다. 자바에서 가장 동기화 메커니즘은 synchronized 키워드를 사용하면 되겠습니다.


ex_)

synchronized(this){

  sharedVal++;

}


안녕하세요. 살빼고싶은 개발자 진쓰입니다. 어댑터 패턴에 대해서 포스팅 시작하겠습니다.



- 의도

이미 제공되는 기능이 형태가 달라, 필요로하는 형태로 변경하고자 할 때 사용합니다.



- 활용

기존의 클래스를 사용해야 하나 인터페이스가 수정되어야 하는경우 사용합니다.  (인터페이스 수정은 지양해야됩니다.)


기존 라이브러리 모양을 달리하여 재사용하고자 할 경우 사용합니다. (라이브러리를 직접 수정할수는 없죠.)



역시 예제상황과 코드리뷰로 배워보도록 하겠습니다.



- 예제 상황

-LED Framework를 제공되어졌다. 

LED를 끄고 RGB 색상으로 켜고 하는 메소드들을 제공받았다.(Framework)

하지만, 우리하드웨어는 켜있는 상태에서 다른 색상을 켜면 색이 섞이는 현상이 생겨서, 무조건 한번 끄고 색상을 켜는 기능을 추가로 하고싶다.


Framework자체를 수정해버리면, 하드웨어가 바뀌면 또다시 Framework를 되돌려야 하는 부담감이 있다. 




- 코드리뷰


소스코드 구성




 ILedFramework.java

개발자가 제공받을 LedFramework.java 의 api를 추출하여 인터페이스를 생성했습니다. 

 public interface ILedFramework {

    //기존메소드, 기존의기능에서 수정할 메소드 구현, 완전 새로운 메소드 구현
    public void ledOff();
    public void ledOn(String color);    
    public void ledBlink(String color1);
}



 LedFramework.java

개발자가 받은 API입니다. 문제가 있죠?(색이 섞이는 현상코드로 구현되어있음)

 //adatee

public class LedFramework {
    public int state = 0;
    public String curColor;
    
    public void ledOn(String color){
       if(state==0){
           System.out.println("LED "+color+" ON");
           curColor=color;
           state=1;
       }
       else{
           if(curColor.equals(color)){
               System.out.println("LED "+color+" ON");
               state=1;
               return;
           }
           //색이 섞인 경우발생.
           
           if(color.equals("RED")){
               curColor=curColor+" RED";
               System.out.println("LED "+curColor+" ON");        
           }
           else if(color.equals("BLUE")){
               curColor=curColor+" BLUE";
               System.out.println("LED "+curColor+" ON");
           }
           else if(color.equals("GREEN")){
               curColor=curColor+" GREEN";
               System.out.println("LED "+curColor+" ON");    
           }   
           
       }
    }
    public void ledOff(){
       System.out.println("Led Off");   
       state = 0;
       curColor="";
    }
    
}



 LedFrameworkAdpater.java

1. LedframeWork는 implements 하였습니다. 

2. LedFramework(색이 섞이는 문제가 있는부분) 를 has a 관계로 가지고있습니다. 

3. ledOn을 보시면 기존의 ledOn을 호출하기전 ledOff를 먼저 호출하도록 수정하였습니다. 

4. ledBlink라는 새로운 기능을 추가하기도 했습니다.


 public class LedFrameworkAdpater implements ILedFramework{

    //1. 확장하려는 클래스를 멤버변수로 선언.
    public LedFramework led;
    //2. 생성자에서 생성.
    public LedFrameworkAdpater(){
       led = new LedFramework();
    }
    
    //3. 기존메소드, 기존의기능에서 수정할 메소드 구현, 완전 새로운 메소드 구현
    @Override
    public void ledOff() {
       led.ledOff();
    }
    @Override
    public void ledOn(String color) {
       ledOff();
       led.ledOn(color);
    }
    @Override
    public void ledBlink(String color1) {
       for(int i=0;i<10;i++){
           ledOff();
           led.ledOn(color1);
       }
    }
    
    
}



 Main.java

led프레임워크를 그대로 사용하는 경우와, 어댑터 패턴을 적용한 객체를 생성해서 사용하는 예제를 볼 수 있다. 

 public class Main {

    public static void main(String[] args) {
       // TODO Auto-generated method stub
       LedFramework led = new LedFramework();
       led.ledOn("RED");
       led.ledOff();
       led.ledOn("RED");
       led.ledOn("BLUE");
       led.ledOn("GREEN");
       led.ledOff();
       led.ledOn("GREEN");
       
       System.out.println("LED프레임워크 -> 어댑터 패턴 적용\n\n\n\n");
    
       ILedFramework newLed = new LedFrameworkAdpater();
       newLed.ledOff();
       newLed.ledOn("RED");
       newLed.ledOn("BLUE");
       newLed.ledOn("GREEN");
       newLed.ledBlink("GREEN");
    }
}



결과화면


Adpater패턴 적용 전 


 Red를 켜고 Blue를 켜버리면, Red와 Blue가 섞이게 된다.

 Adpater패턴 적용 후


색이 안섞이는것을 볼 수있다.

 


 



.


- UML

> UML 설명

ILedFramework 인터페이스로, 기존에 제공받은 LedFrameWork api와 더불어 유저가 추가하고싶은 ledBlink()메소드도 선언하였습니다.



LedFrameworkAdpater.java 는 ILedFramework를 implements하였으며 프레임워크(LedFramework)를 has a 관계로 가지고있습니다.

ledOn은 기존의 기능에 새로 추가된 내용을 덧 붙혔으며, ledBlink는 기존 LedFramework가 없는 기능입니다.


어댑터 패턴의 장점을 보실 수 있습니다.(플랫폼에 알맞게 수정, 확장)




- 정리

지금 포스팅된 어댑터 패턴은 Object Adapter 패턴입니다. 일반적으로 많이 사용됩니다.


오브젝트 어댑터 패턴을 사용하면, Adaptee 클래스의 하위 클래스에 대해서도 소스코드 변경없이, 이를 활용하여 새로운 기능 추가가 편리해집니다.




정독해주셔서 감사합니다.

항상 정성을 다해서 포스팅 하도록 하겠습니다 :)

풀스택개발자를 지향하는 진쓰의 디자인패턴 - 상태패턴 블로그 포스팅 시작하겠습니다.


의도

 - 객체 자신의 내부상태에 따라 행위를 변경하는 효과를 주고자 할 때 사용합니다.



활용

- 객체 행위는 상태에 따라 달라질 수 있다. 객체의 상태에 따라서 프로그램이 실행 중 행위가 바뀌어야할 때 활용할 수 있습니다.


- 객체 상태에 따라 수많은 if를 갖도록 구현해야하는데 이를 피해보고자 할 때 사용합니다.



역시 개발자는 코드를 보면서 공부를 해야제맛이죠. 

예제상황 살펴보시겠습니다.


 

- Edit 프로그램을 구현하려 한다.


하나의 문서를 열 수 있으며, 

문서가 수정상태일 경우, =>          문서의 저장, 열기, 종료 기능을 처리하는 방법은,

문서가 수정상태가 아닌 경우의  => 처리 방법을 다르게 동작한다고 한다.




코드리뷰


- 구조





 State.java   // 작명을 잘못한것같애요. UML부분에 이유를 설명드리겠습니다.

 public interface State {

    public void open();
    public void close();
    public void save();
    public void edit(String text);  
}




 InitState.java


State를 implements 하여 구현하였습니다. 앞서배웠던 싱글톤 패턴으로 작성되어있는것도 살펴보실수 있습니다.


 public class InitState implements State {

    private InitState(){}
    private static InitState init;
    public static InitState getInstance(){
       if(init ==null)
           init = new InitState();
       return init;
    };
    
    @Override
    public void open() {
       //System.out.println("새로 문서가 열렸습니다.");
    }
    @Override
    public void close() {
       System.out.println("문서 수정없이 문서가 닫혔습니다.");
    }
    @Override
    public void save() {
       System.out.println("저장할 것이 없습니다.");
    }
    @Override
    public void edit(String text) {
       System.out.println("init 상태 -> modify상태 ");
        EditEngine.setState(ModifyState.getInstance());
    }
}




 ModifyState.java

 public class ModifyState implements State {

    private ModifyState(){}
    private static ModifyState modify;
    public static ModifyState getInstance(){
       if(modify ==null)
           modify = new ModifyState();
       return modify;
    };
    
    @Override
    public void open() {
       System.out.println("이미 열려있는 파일이 있습니다.파일을 저장하고 엽니다.");
       save();
    }
    @Override
    public void close() {
       System.out.println("수정된 사항이있습니다. 파일을 저장하고 종료합니다.");
       save();
    }
    @Override
    public void save() {
       System.out.println("현재내용을 저장합니다.");
        EditEngine.setState(InitState.getInstance());
    }
    @Override
    public void edit(String text) {
       System.out.println("편집 상태 -> 편집상태 (상태변화없음)");
       
    }
}




 EditEngine.java


여기서 EditEngine은 state를 가지고 있는데, public static으로 선언되어있어서, State 인터페이스를 implements한 클래스들이 접근할 수 있도록 만들어 놓았습니다.

 public class EditEngine {

    
    private String text ="";
    private static State myState = InitState.getInstance();
    
    public void setText(String text){
       this.text = text;
    }
    public String getText(){
           return text;
    }
    public static void setState(State state){
       myState = state;
    }
    
    public void open(){
       myState.open();
       System.out.println("문서가 open되었습니다.");
    }
    public void close(){
       myState.close();
       System.out.println("문서가 종료 되었습니다.");
    }
    public void edit(String text){
       myState.edit(text);
       System.out.println("문서가 편집되었습니다");
    }
    public void save(){
       myState.save();
    }
}



.


 Main.java

 public class Main {

    public static void main(String[] args) {
       
       EditEngine edit = new EditEngine();
       
       edit.open();
       edit.edit("안녕");
       edit.save();
       edit.save();
       
       System.out.println("\n\n");
       edit.open();
       edit.edit("Jinss Programming Lecture");
       edit.open();
       edit.close();
       
       System.out.println("\n\n");
       edit.open();
       edit.edit("끝내자");
       edit.close();
       
    }
}


결과화면





UML


UML 설명


- State에 문서가 가질수있는 상태 혹은 동작 들을 인터페이스로 구현하였습니다.  음... 동작이라는 표현이 더 와닿는것 같기도합니다.

어쨌든 State 인터페이스를 상속받아 InitState와 ModifyState 를 각각 두웠습니다.

덕분에 InitState일때와, ModifyState일때 open, close, save, edit 라는 행위를 할때는 State에 따라 다르게 동작하게 됩니다.


그러고보니 인터페이스인 State는 막상 State라는 의미이기보단 Behavior? 라는게 더 알맞는 작명일것 같습니다.

뭐 개발자 나름이니깐 저는 그렇게 판단이 되네요.



어쩄든 EditEngine은 State를 가지고있는 has a 관계를 형성하고있습니다.

Main에서 EditEngine을 생성하고 단지 open, close, save, edit만 하면 상태에 따라서 알아서 착착착 다르게 호출되며 돌아가게 됩니다.



그리고 EditEnngine는, setState(state: State): void 를 static method로 선언해서, InitState와 ModfityState에서 접근하여 상태를 바꿀 수 있도록 해놓았습니다.  


EditEngine은 State인터페이스 변수를 두고있고, 현재 상태를 InitState혹은 ModifyState 로 갱신해야되니 뚫어놓아야겠죠?



정독해주셔서 감사합니다 :)




안녕하세요. 풀스택개발자를 지향하는 진쓰입니다.

디자인패턴 팩토리메소드 포스팅 시작하겠습니다.




목적

- 개발자는 객체를 생성해서 사용해야하는데, 어떤 객체를 생성할지, 어떤 객체가 생성가능할지 없을 때 사용할 수 있습니다.




활용

- 생성할 객체 타입을 예측할 수 없을 때 활용합니다.


- 객체 생성의 책임을 하위 클래스에게 위임시키고, 어느 하위 클래스에게 위임했는지에 대한 정보를 은닉하고자 할 때 활용합니다.




목적과 활용으로 봐서는 감이 안오실수있습니다. 예제를 통해 살펴보고 설명하도록 하겠습니다.

예제

- 교통수단이 되는 것을 만들어내는 회사가 있다. 이회사는 택시와 자전거를 만들어내는 회사다.





코드리뷰


 - 구성




 TransProduct.java

 public abstract class TransProduct {

    abstract void useTrans();
}




 TransProduct_Taxi.java

 public class TransProduct_Taxi extends TransProduct {

    @Override
    void useTrans() {
       System.out.println("Using Taxi");    
    }
}



 TransProduct_Bicycle.java

 public class TransProduct_Bicycle extends TransProduct {

    @Override
    void useTrans() {
       System.out.println("Using Bicycle");       
    }
}



.



 TransCreator.java

 //추상 클래스.

//Caller는 Interface혹은 abstract 클래스를 통해, Callee와 연결해야된다.
public abstract class TransCreator {
    //TransProduct 생성하는 팩토리 메소드(자전거, 택시)
    public abstract TransProduct transCreate(String trans);
}




 RealTransCreator_TransCompany.java

 public class RealTransCreator_TransCompany extends TransCreator {

    @Override
    public TransProduct transCreate(String trans) {
       
       if(trans.equals("taxi")){
           return new TransProduct_Taxi();
       }
       else{
           return new TransProduct_Bicycle();
       }   
       
       
    }
}



Main.java 

 public class Main {

    public static void main(String[] args) {
       
       //교통수단을 만들어내는 생산자 선언.
       TransCreator tc = new RealTransCreator_TransCompany();
       
       //Caller(Main)은 Callee를 직접변수를 선언하지않고 생성.
       //항상 인터페이스(중간매체, 추상클래스포함)를 통해 생성.
       
       TransProduct tp;
       
       //교통수단 - 자전거 생성
       tp = tc.transCreate("bicycle");
       
       //자전거 기능 이용.
       tp.useTrans();
       
       //교통수단 - 택시 생성
       tp = tc.transCreate("taxi");
       
       //택시 기능 이용.
       tp.useTrans();
    }
}



결과화면





UML




- UML 설명

TransCreator 클래스가 부모 클래스 입니다.

RealTransCreator가 진짜 객체를 만드는 클래스 입니다

여기선 RealTransCreator가 Bicycle과 Taxi를 생성하여(TransProduct)반환한다.



*) 여기서 회사가 만들어내는 제품이 늘어난다면?

(1) TransProduct 상속받는 클래스 추가합니다.

(2) RealTransCreator_TransCompany(Product를 생성하는 클래스)에서도 transCreate 수정합니다.

(Product가 늘어났으니!,dependency 관계니깐)



 RealTransCreator_TransCompany는 TransProduct_Bicycle, TransProduct_Taxi와 dependency 관계라서 Product가 늘어났다는것은 RealTransCreator_TransCompany역시 수정되어야 한다는 의미이다.



점선화살표는 dependency 를 의미합니다.





객체지향 원칙

Caller는 Callee를 직접 만들어 호출하지 않는다.


=> 여기선, TransCreator에서 TransProdcut_Taxi와 TransProduct_Bicycle을 만들 수 있지만, 

RealTransCreator_TransComapny 라는 sub 클래스를 하나 둬서 , 실제 TransProduct를 생성하여 반환하도록 하였다.


이처럼 객체지향언어에서는 Caller는 Callee를 직접만들어 호출하지않고 한단계를 더 둔다=>결합도를 떨어트리기 위해서이다.







정독해주셔서 감사합니다 :)

+ Recent posts