본문 바로가기
📒 기술 또는 개념/🎨 Design Pattern

[Design Pattern] 팩토리(Factory) 패턴에 대해

by DEV_DAON 2023. 10. 25.

오늘은 디자인 패턴 중 생성 패턴의 팩토리 패턴에 대해 알아보자!

 

이 팩토리 패턴은 조금 더 구체적인 용어인 팩토리 메서드 패턴으로도 널리 알려져 있다.

 

팩토리 패턴이란?

팩토리 패턴은 객체를 생성하는 인터페이스는 미리 정의하되, 인스턴스를 만들 클래스의 결정은 서브 클래스 쪽에서 내리는 패턴이다.

다시 말해 여러 개의 서브 클래스를 가진 슈퍼 클래스가 있을 때 인풋에 따라 하나의 자식 클래스의 인스턴스를 리턴해주는 방식이다.

팩토리 패턴에서는 클래스의 인스턴스를 만드는 시점을 서브 클래스로 미룬다.

이 패턴은 인스턴스화에 대한 책임을 객체를 사용하는 클라이언트에서 팩토리 클래스로 가져온다.

 

활용성

- 어떤 클래스가 자신이 생성해야 하는 객체의 클래스를 예측할 수 없을 때

- 생성할 객체를 기술하는 책임을 자신의 서브클래스가 지정했으면 할 때

 

팩토리 패턴에 사용되는 슈퍼 클래스는 인터페이스나 추상 클래스, 혹은 그냥 평범한 자바 클래스여도 상관없다.

그러나 이번 예제에서는 추상 클래스로 만들어 toString()을 오버라이딩하여 코드를 작성해 보도록 하겠다!

 

Super Class

 

public abstract class Computer {

    public abstract String getRAM();
    public abstract String getSSD();
    public abstract String getCPU();
    
    @Override
    public String toString() {
    	return "RAM = "+this.getRAM()+", SSD = "+this.getSSD()+", CPU = "+this.getCPU();
        }
}

 

슈퍼 클래스를 만들었으니 이번에는 PC와 Server라는 이름의 두 서브 클래스를 만들어보자!

 

Sub Class - 1

 

public class PC extends Computer {

	private String ram;
    	private String ssd;
    	private String cpu;
    
    public PC(String ram, String ssd, String cpu){
    	this.ram = ram;
        this.ssd = ssd;
        this.cpu = cpu;
    }
    
    @Override
    public String getRAM(){
    	return this.ram;
    }
    
    @Override
    public String getSSD(){
    	return this.ssd;
    }
    
    @Override
    public String getCPU(){
    	return this.cpu;
    }
 
}

 

Sub Class - 2

 

public class Server extends Computer {

	private String ram;
    	private String ssd;
    	private String cpu;
    
    public Server(String ram, String ssd, String cpu) {
    	this.ram = ram;
        this.ssd = ssd;
    	this.cpu = cpu;
    }
    
    @Override
    public String getRAM() {
    	return this.ram;
    }
    
    @Override
    public String getSSD() {
    	return this.ssd;
    }
    
    @Override
    public String getCPU() {
    	return this.cpu;
    }
 
}

 

여기서 주의할 점은 PC 클래스와 Server 클래스 모두 Computer 클래스를 상속한다는 점이다.

 

이제 팩토리 클래스를 만들어보도록 하자!

 

Factory Class

 

public class ComputerFactory {

	public static Computer getComputer(String type, String ram, String sdd, String cpu) {
    	if("PC".equalsIgnoreCase(type))
        	return new PC(ram, ssd, cpu);
        else if("Server".equalsIgnoreCase(type))
        	return new Server(ram, ssd, cpu);
            
        return null;
    }

}

 

코드를 살펴보면, ComputerFactory 클래스의 getComputer 메서드가 Static 메서드로 구현되었다는 점을 알 수 있고, 메서드 내부 코드를 보면 type의 값이 "PC"일 경우 PC 클래스의 인스턴스를, "Server"일 경우 Server 클래스의 인스턴스를 리턴하는 것을 볼 수 있다.

 

이렇게 팩토리 메서드 패턴을 사용하게 되면 인스턴스를 필요로 하는 Application에서 Computer의 Sub 클래스에 대한 정보는 모른 채 인스턴스를 생성할 수 있게 된다.

이렇게 구현한다면 앞으로 Computer 클래스에 더 많은 Sub 클래스가 추가 된다 할지라도 getComputer()를 통해 인스턴스를 제공받던 Application의 코드는 수정할 필요가 없게 된다.

 

팩토리 메서드 패턴을 구현하는 데 중요한 점이 두 가지가 있다.

 

1. Factory class를 Singleton으로 구현해도 되고, 서브 클래스를 리턴하는 static 메서드로 구현해도 된다.

 

2. 팩토리 메서드는 위 예제의 getComputer()와 같이 입력된 파라미터에 따라 다른 서브 클래스의 인스턴스를 생성하고 리턴한다.

 

마지막으로, 위 예제에서 작성한 ComputerFactory 클래스를 사용하여 PC와 Server 클래스의 인스턴스를 생성해보겠다.

 

public class TestFactory {

	public static void main(String[] args) {
    	Computer pc = ComputerFactory.getComputer("pc", "8 GB", "512 GB", "4 GHz");
        Computer server = ComputerFactory.getComputer("server", "16 GB", "1 TB", "8 GHz");
        System.out.println("Factory PC Config::" +pc);
        System.out.println("Factory Server Config::" +server);
   }

 

Factory PC Config::RAM = 8 GB, SSD = 512 GB, CPU = 4 GHz
Factory Server Config::RAM = 16 GB, SSD = 1 TB, CPU = 8 GHZ

 

팩토리 패턴의 장점

 

1. 팩토리 패턴은 클라이언트 코드로부터 서브 클래스의 인스턴스화를 제거하여 서로 간의 종속성을 낮추고, 결합도를 느슨하게 하며, 확장을 쉽게 합니다. 예를 들어, 위 예제에서 작성한 클래스 중 PC 클래스에 대해 수정 혹은 삭제가 일어나더라도 클라이언트는 알 수 없기 때문에 코드를 변경할 필요도 없습니다.

 

2. 팩토리 패턴은 클라이언트와 구현 객체들 사이에 추상화를 제공합니다.

 

사용 예

 

1. java.util 패키지에 있는 Calendar, ResourceBundle 등의 클래스에서 정의된 getInstance() 메서드가 팩토리 패턴을 사용하고 있다.

 

2. Boolean, Integer, Long 등 Wrapper class 안에 정의된 valueOf() 메서드 또한 팩토리 패턴을 사용했다.