관리 메뉴

나만을 위한 블로그

팩토리 패턴이란? 본문

개인 공부/디자인 패턴

팩토리 패턴이란?

참깨빵위에참깨빵 2021. 10. 5. 21:51
728x90
반응형

뷰모델을 공부하면서 ViewModelProvider.Factory라는 인터페이스를 만들어 사용하는 예제를 같이 포스팅했었는데, 이 방식이 팩토리 패턴이라는 생소한 방식을 활용한 것이라 해서 팩토리 패턴에 대해 공부해봤다.

위키백과에서 말하는 팩토리 패턴의 '팩토리'는 아래와 같다.

 

https://en.wikipedia.org/wiki/Factory_(object-oriented_programming) 

 

Factory (object-oriented programming) - Wikipedia

In object-oriented programming (OOP), a factory is an object for creating other objects – formally a factory is a function or method that returns objects of a varying prototype or class[1] from some method call, which is assumed to be "new".[a] More broa

en.wikipedia.org

OOP에서 팩토리는 다른 객체를 만들기 위한 객체다. 공식적으로 팩토리는 "새로운" 것으로 가정되는 어떤 메서드 호출로부터 다양한 프로토타입이나 클래스의 객체를 반환하는 함수 or 메서드다. 보다 광범위하게 "새로운" 객체를 반환하는 서브루틴은 공장 방법 또는 공장 기능에서와 같이 "공장"이라고 할 수 있다. 이것은 OOP의 기본 개념이며, 많은 관련 소프트웨어 설계 패턴의 기초를 형성한다.

클래스 기반 프로그래밍에서 팩토리는 클래스 생성자의 추상화이고 프로토타입 기반 프로그래밍에서 팩토리는 프로토타입 객체의 추상화다. 생성자는 객체를 단일 클래스의 인스턴스로 생성하고 지정된 프로세스(클래스 인스턴스화)에 의해 객체를 생성한다는 점에서 구체적이며, 팩토리는 다양한 클래스를 인스턴스화하거나 객체 풀과 같은 다른 할당 체계를 사용하여 객체를 생성할 수 있다. 프로토타입 객체는 복제를 통해 객체를 생성하는 데 사용된다는 점에서 구체적이고, 팩토리는 다양한 프로토타입을 복제하거나 다른 할당 방식을 통해 객체를 생성할 수 있다. 팩토리는 다양한 방식으로 구현될 수 있지만 대부분 메서드로 구현되며 이 경우 팩토리 메서드라고 한다. 함수로 구현되면 팩토리 함수라고 한다. 일부 언어에선 생성자 자체가 팩토리지만 대부분 언어는 그렇지 않으며 생성자는 new 키워드를 쓰는 것과 같이 언어에 관용적인 방식으로 호출되는 반면, 팩토리는 특별한 상태가 없이 일반 메서드 호출 또는 함수 호출로 호출된다...(중략)...팩토리가 생성자를 일반화하는 언어에서 팩토리는 일반적으로 생성자가 있을 수 있는 모든 곳에서 사용할 수 있다. 일반적으로 객체 생성이 필요하다.

 

팩토리의 사전적 정의는 공장이란 뜻이고, 공장은 원료나 재료를 가공하여 물건을 만들어 내는 설비를 갖춘 곳이란 뜻이다.

즉 OOP에서도 위와 비슷하게 객체를 만들어내는 역할을 하는 함수 또는 메서드를 팩토리라고 부르는 것이고, 이걸 활용한 디자인 패턴을 팩토리 패턴이라고 하는 듯하다.

다른 블로그에선 어떻게 설명하는지 찾아봤다.

 

https://www.journaldev.com/1392/factory-design-pattern-in-java

 

Factory Design Pattern in Java - JournalDev

Factory Design Pattern in Java. Factory Pattern Java. Java Factory Method Design Pattern. Factory Design pattern advantages, JDK example, class diagram.

www.journaldev.com

팩토리 패턴은 여러 하위 클래스가 있는 슈퍼 클래스가 있고 입력을 기반으로 하위 클래스 중 하나를 반환해야 할 때 사용한다. 이 패턴은 클라이언트 프로그램에서 팩토리 클래스로 클래스를 인스턴스화하는 책임을 진다.

 

https://www.tutorialspoint.com/design_pattern/factory_pattern.htm

 

Design Pattern - Factory Pattern

Design Pattern - Factory Pattern Factory pattern is one of the most used design patterns in Java. This type of design pattern comes under creational pattern as this pattern provides one of the best ways to create an object. In Factory pattern, we create ob

www.tutorialspoint.com

팩토리 패턴은 객체를 생성하는 방법 중 하나를 제공하는 생성 패턴 중 하나다. 객체 생성 로직을 클라이언트에 노출시키지 않고 객체를 생성하며 공통 인터페이스를 사용해 새로 생성된 객체를 참조한다.

 

https://velog.io/@lsj8367/%EC%9E%90%EB%B0%94-%ED%8C%A9%ED%86%A0%EB%A6%AC%ED%8C%A8%ED%84%B4

 

[Java] 디자인 패턴 - 팩토리 패턴(Factory pattern)

팩토리 패턴

velog.io

팩토리 패턴은 객체를 생성하는 인터페이스 또는 추상 클래스를 미리 정의하지만, 인스턴스를 만들 클래스의 결정은 서브 클래스 쪽에서 결정하는 패턴이다. 여러 서브 클래스를 가진 슈퍼 클래스가 있을 때 들어오는 인자에 따라 하나의 자식 클래스 인스턴스를 반환해주는 방식이다

 

이제 예시 코드로 확인해보자. 아래에 나열된 코드들은 팩토리 패턴을 추상 클래스와 인터페이스로 사용하는 방법 2가지로 나눈 것이다.

먼저 추상 클래스를 사용하는 방법부터 확인한다.

 

public abstract class Computer {

    public abstract String getRAM();
    public abstract String getHDD();
    public abstract String getCPU();

    @Override
    public String toString() {
        return "RAM = " + getRAM() + ", HDD = " + getHDD() + ", CPU = " + getCPU();
    }
}

 

램과 하드디스크, CPU 정보를 추상 메서드로 받는 추상 클래스다. toString()을 호출 시 각 메서드가 가져온 값들을 출력하도록 재정의한 걸 볼 수 있다.

그리고 이 추상 클래스를 정의한 클래스 2가지를 만든다.

 

public class PC extends Computer {

    private String ram;
    private String hdd;
    private String cpu;

    public PC(String ram, String hdd, String cpu) {
        this.ram = ram;
        this.hdd = hdd;
        this.cpu = cpu;
    }

    @Override
    public String getRAM() {
        return ram;
    }

    @Override
    public String getHDD() {
        return hdd;
    }

    @Override
    public String getCPU() {
        return cpu;
    }
}
public class Server extends Computer {

    private String ram;
    private String hdd;
    private String cpu;

    public Server(String ram, String hdd, String cpu) {
        this.ram = ram;
        this.hdd = hdd;
        this.cpu = cpu;
    }

    @Override
    public String getRAM() {
        return ram;
    }

    @Override
    public String getHDD() {
        return hdd;
    }

    @Override
    public String getCPU() {
        return cpu;
    }
}

 

PC와 Server라는 클래스에 추상 클래스(Computer)를 상속시켜서, 추상 클래스에 있던 3가지 메서드를 재정의했다.

이제 PC, Server 생성자에 넣은 값들을 각각 출력할 수 있게 됐다.

그리고 입력받은 값에 따라 각각 다른 컴퓨터 사양을 가진 인스턴스를 만들어 줄 팩토리 메서드가 있는 팩토리 클래스를 만든다.

 

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

 

대소문자 상관없이 PC라는 값이 들어오면 PC 클래스의 객체를 생성하고, Server라는 값이 들어오면 Server 클래스의 객체를 생성해 반환하는 메서드다. 때문에 메서드명 왼쪽의 타입을 보면 추상 클래스(Computer)인 걸 볼 수 있다.

이제 만들어진 팩토리 메서드를 메인 메서드에서 호출해 테스트할 차례다.

 

public class TestFactory {
    public static void main(String[] args) {
        Computer pc = ComputerFactory.getComputer("pc", "2GB", "2TB", "2.4GHz");
        Computer server = ComputerFactory.getComputer("server", "16GB", "4TB", "2.9GHz");
        System.out.println("팩토리 PC 구성 : " + pc);
        System.out.println("팩토리 Server 구성 : " + server);
        // >> 팩토리 PC 구성 : RAM = 2GB, HDD = 2TB, CPU = 2.4GHz
        // >> 팩토리 Server 구성 : RAM = 16GB, HDD = 4TB, CPU = 2.9GHz
    }
}

 

객체를 생성하는 자세한 로직은 숨긴 채 메인 메서드에서 각 인자 부분에 원하는 값을 넣고 객체를 print 하면 Computer에서 재정의했던 toString()의 양식대로 컴퓨터 사양들이 출력되는 걸 볼 수 있다.

 


 

이번엔 인터페이스를 사용해 팩토리 패턴을 사용한 예제를 확인해보자. 먼저 인터페이스부터 만들어준다.

 

public interface Shape {
    void draw();
}

 

이제 이 인터페이스를 구현한 Rectangle, Circle, Square 클래스들을 만들고 인터페이스 안의 draw() 메서드를 재정의해서, 어떤 클래스의 draw()가 호출됐는지 알 수 있도록 한다.

 

public class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Rectangle : draw() 호출");
    }
}
public class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("Circle : draw() 호출");
    }
}
public class Square implements Shape {
    @Override
    public void draw() {
        System.out.println("Square : draw() 호출");
    }
}

 

그리고 팩토리 클래스를 만들어 입력받은 값에 따라 각각 다른 인스턴스를 리턴해주는 팩토리 메서드를 정의한다.

 

public class ShapeFactory {
    public Shape getShape(String shapeType) {
        if (shapeType.equalsIgnoreCase("CIRCLE"))
            return new Circle();
        else if (shapeType.equalsIgnoreCase("RECTANGLE"))
            return new Rectangle();
        else if (shapeType.equalsIgnoreCase("SQUARE"))
            return new Square();
        else
            return null;
    }
}

 

역시 대소문자 상관없이 circle, rectangle, square라는 값이 들어오면 그것에 맞는 객체를 생성해 각각 리턴해준다.

마지막으로 위에서 만든 getShape()를 테스트할 차례다.

 

public class ShapeTest {
    public static void main(String[] args) {
        ShapeFactory factory = new ShapeFactory();
        Shape circle = factory.getShape("circle");
        circle.draw();
        // >> Circle : draw() 호출

        Shape rectangle = factory.getShape("rectangle");
        rectangle.draw();
        // >> Rectangle : draw() 호출

        Shape square = factory.getShape("square");
        square.draw();
        // >> Square : draw() 호출
    }
}

 

팩토리 패턴은 객체 지향이 제공하는 성질 중 다형성을 이용하는 패턴이기 때문에, 새로운 도형을 정의해야 하는 상황이 생기더라도 그 도형의 클래스를 만들고 Shape 인터페이스를 구현한 뒤, 팩토리 클래스에 정의해서 필요한 곳에 호출해주면 곧바로 사용할 수 있다. 만들어진 도형을 삭제해야 하는 경우가 생기더라도 마찬가지로 간편하게 삭제할 수 있다.

 

이런 특징을 가진 팩토리 패턴은 아래의 경우에 활용을 고려해볼 수 있다.

 

  • 어떤 클래스가 자신이 생성해야 하는 객체의 클래스를 예측할 수 없을 때
  • 생성할 객체를 기술하는 책임을 자신의 서브클래스가 지정해야 할 때

 

또한 팩토리 패턴의 장단점은 아래와 같다. 장단점을 요약하면 객체 지향 원칙인 SOLID 원칙 중 "의존관계 역전 원칙"이 성립된다는 것이다.

 

장점

 

  • 클라이언트 코드로부터 서브 클래스의 인스턴스화를 제거해 서로 간의 종속성을 낮추고, 결합도를 느슨하게 하며, 확장을 쉽게 한다
  • 상속을 통해 클라이언트와 구현 객체들 사이에 추상화를 제공한다
  • 구상 클래스(Concrete Class)에 의존하지 않고 추상화된 것에 의존한다

 

단점

 

  • 새로 생성할 객체가 늘어날 때마다 팩토리 클래스에 추가해야 하기 때문에 클래스가 많아진다

 

의존관계 역전 원칙에 대해선 위키백과 또는 다른 블로그를 참고하자.

https://en.wikipedia.org/wiki/Dependency_inversion_principle

 

Dependency inversion principle - Wikipedia

In object-oriented design, the dependency inversion principle is a specific form of loosely coupling software modules. When following this principle, the conventional dependency relationships established from high-level, policy-setting modules to low-level

en.wikipedia.org

 

반응형
Comments