해당 포스트는 "자바 객체지향 디자인 패턴", "JAVA 언어로 배우는 디자인 패턴 입문" 책의 내용을 요약한 것이다.



※ 팩토리 메서드(Factory Method) 패턴

: 객체를 생성하는 코드를 별도의 클래스/메서드로 분리함으로써 객체 생성 방식의 변화에 대비하는 패턴

 

ex1)JDBC를 이용한 db 프로그램

 

public class Database{
   private Connection con;
   private Statement stmt;
   private ResultSet rset;
  
   public Database(){ 
      String server = "192.168.0.12";
      try{
            Class.forName("org.gjt.mm.mysql.Driver");
       }catch(ClassNotFoundException e){
             e.printStackTrace();
       }

       try{
            String url = "jdbc:mysql://" + "localhost:3306/test";
            con=DriverManager.getConnection(url);
            stmt = con.createStatement();
       }catch(SQLException e){
            e.printStackTrace();
       }

       public Connection getConnection(){
             return con;
       }
}
public class Business{
   Database db = new Database();
   Connection con = db.getConnection();
   
              .......//db 관련 비즈니스 로직

} 

Business 클래스에서 Database 객체를 직접 생성해서 Connection 객체를 참조한다. Business 클래스 뿐만 아니라 BusinessA, BusinessB, BusinessC 클래스에서도 Database 객체를 직접 생성한다고 가정하자. 만약 Database의 생성자가 기본 생성자가 아닌 Database(String str)로 바뀐다면 Database를 직접 생성하는 모든 클래스에 가서 일일이 수정을 해야하는 유지보수의 문제점이 생긴다. 이런 문제점을 해결하려면 객체의 생성을 다른 클래스에 위임을 시키는 팩토리 메서드 패턴을 사용하면 된다. 위 예제에서는 Database 객체를 직접 생성했는 데 Database 객체를 생성해주는 별도의 클래스를 만들어서 Database 클래스와 Business 클래스 사이의 의존성을 제거해 코드의 수정이 서로의 클래스에 영향을 미치지 않도록 할 수 있다.

 

public interface Database{
    public Connection getConnection();
}

public class DatabaseImpl implements Database{
   private Connection con;
   private Statement stmt;
   private ResultSet rset;
  
   public DatabaseImpl(){ 
      String server = "192.168.0.12";
      try{
            Class.forName("org.gjt.mm.mysql.Driver");
       }catch(ClassNotFoundException e){
             e.printStackTrace();
       }

       try{
            String url = "jdbc:mysql://" + "localhost:3306/test";
            con=DriverManager.getConnection(url);
            stmt = con.createStatement();
       }catch(SQLException e){
            e.printStackTrace();
       }

       public Connection getConnection(){
             return con;
       }
}  
public interface DatabaseFactory{
    public Database getDatabase();
}

public class DatabaseFactoryImpl implements DatabaseFactory{
   private Database db;
   public Database getDatabase(){
        db = new DatabaseImpl();
        return db;
   }
}
public class Business{
   DatabaseFactory df = new DatabaseFactoryImpl();
   Database db = df.getDatabase();
   Connection con = db.getConnection();
 
         .....//db 관련 비즈니스 로직
 }

위 코드는 팩토리 메서드 패턴을 적용한 예이다. 전 예제와 달리 Database 객체를 new 연산자를 통해서 직접 생성하지 않고 객체 생성 위임 클래스인 DatabaseFactory의 getDatabase 메서드를 통해서 객체를 얻고 있다. 만약 Database의 생성자가 바뀔 시 getDatabase 메서드를 호출하는 모든 클래스의 수정 없이 단순히 DatabaseFactoryImpl 함수의 getDatabase 메서드를 수정하기만 하면 된다.

 

 

 

ex2) 여러가지 방식의 엘리베이터 스케줄링 방법 지원

 

ElevatorManager 클래스는 여러 대의 엘리베이터를 관리하는 클래스이다. ElevatorController 클래스는 각각의 엘리베이터 이동을 책임진다. ThroughtputScheduler 클래스는 엘리베이터를 스케줄링하기 위한 클래스로 하나의 엘리베이터에 대해서 최대한 많은 사람이 탈 수 있도록 처리량을 최대화 할 수 있도록 엘리베이터를 스케쥴링하는 클래스다. 

public class ElevatorManager{
   private List controllers;
   private ThoughputScheduler scheduler;

   public ElevatorManager(int controllerCount){
       controllers = new ArrayList(controllerCount);
       for(int i=0; i< controllerCount; i++){
            ElevatorController controller = new ElevatorController(i);
            controllers.add(controller);
       }
       scheduler = new ThroughputScheduler();
   }

   void requestElevator(int destination, Direrction direction){
       int selectedElevator = scheduler.selectElevator(this, destination, direction);
       controllers.get(selectedElevator).gotoFloor(destination); 
   }
}

public class ElevatorController{
    private int id;
    private int curFloor;

   public ElevatorController(int id){
      this.id = id;
      curFloor = 1;
   }
   public void gotoFloor(int destination){
         System.out.println("Elevator+ " id : Floor  "+curFloor);
         curFloor = destination;
         System.out.println("==>> " + curFloor);
  }
}

public class ThroughputScheduler {
   public int selectElevator(ElevatorManager manager, int destination, Direction direction){
                 //로직 수행
           return 0;
   }
} 

- 문제점

: 위 예제에서 엘리베이터는 처리량을 최대화할 수 있도록 ThroughputScheduler 클래스를 통해 스케쥴링된다. 만약에 사용자의 대기시간을 최소화하는 엘리베이터 선택 전략을 사용해야 한다면 어떻게 해야할까? 그리고 오전에는 처리량 최대화 스케쥴링을 사용하고 오후에는 대기시간 최소화 스케쥴링을 사용하여 스케쥴링을 변경해야 하는 동적 스케쥴링을 지원해야 한다면 어떻게 해야 할까?

 

우선은 사용자의 대기시간을 최소화하는 스케쥴링 클래스 ResponseTimeScheduler 클래스를 별도로 작성해야한다. 또한 ResponseTimeScheduler 클래스와 ThroughputScheduler 클래스는 ElevatorManager 클래스 입장에서는 엘리베이터 스케줄링 전략이기 때문에 스트래티지 패턴을 활용하여 AbstractStrategy 라는 부모클래스를 만들고 두 개의 스케쥴링 클래스에서 상속받도록 하였다. 그리고 동적 스케쥴링 지원을 위해서 ElevatorManager 클래스의 requestElevator 메서드를 수정해야 한다.

void requestElevator(int destination, Direction direction){ ElevatorScheduler scheduler; int hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY); if(hour<12) scheduler = new ResponseTimeScheduler(); else scheduler = new ThroughputScheduler(); int selectedElevator = scheduler.selectElevator(this,destination, direction); controllers.get(selectedElevator).gotoFloor(destination); }

이와 같이 엘리베이터 스케줄링 전략이 추가되거나 동적 스케줄링 방식을 선택할 경우 객체 생성에 관해서 requestElevator 메서드를 계속 수정할 수 밖에 없다. 그런데 requestElevator 메서드는 객체 생성에 목적이 있지 않고 엘리베이터 선택과 선택된 엘리베이터를 이동시키는 것이 근본 목적이다. 그래서 주어진 기능을 실제로 제공하는 적절한 클래스 생성 작업을 별도의 클래스로 분리하는 팩토리 메서드 패턴을 구현한다.

public enum SchedulingStrategyID { RESPONSE_TIME, THROUGHPUT, DYNAMIC }

public class SchedulerFactory{
   public static ElevatorScheduler getScheduler(SchedulingStrategyID strategyID){
         ElevatorScheduler scheduler = null;
         switch(strategyID){
             case RESPONSE_TIME:
                scheduler = new ResponseTimeScheduler();
                break;
             case THROUGHPUT:
                scheduler = new ThroughputScheduler();
                break;
             case DYNAMIC:
                int hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
                if(hour<12)
                  scheduler = new ResponseTimeScheduler();
               else 
                  scheduler = new ThroughputScheduler();
               break;
          }
          return scheduler;
    }
}
vodi requestElevator(int destination, Direction direction){
       ElevatorScheduler scheduler = SchedulerFactory.getScheduler(strategyID);
       int selectedElevator = scheduler.selectElevator(this, destination, direction);
       controllers.get(selectedElevator).gotoFloor(destination); 
}

위와 같이 객체를 생성하는 별도의 클래스를 만들면 새로운 엘리베이터 스케쥴링 클래스가 생기거나 동적 스케줄링을 할 시 SchedulerFactory 클래스를 수정하고 requestElevator 메서드에서는 단지 strategyID 값만 변경해 주면 된다.

'자바 > 디자인패턴' 카테고리의 다른 글

추상 팩토리 패턴  (0) 2017.07.05
템플릿 메서드 패턴  (0) 2017.07.04
중재자(Mediator) 패턴  (0) 2017.07.04
커맨드(Command) 패턴  (0) 2017.07.03
상태(State) 패턴  (0) 2017.07.03

+ Recent posts