Java/Design Pattern

팩토리 메서드 패턴

haechan29 2023. 1. 16. 19:22

오늘은 팩토리 메서드 패턴에 대해 알아봐요.

팩토리 메서드 패턴은 이름만 봐도 어떤 패턴인지 감이 옵니다.

 

팩토리가 뭐죠? 공장, 즉 무언가를 만드는 곳이에요.

그럼 아마도 팩토리 메서드는 무언가를 만드는 역할을 하는 메서드겠죠?

 

결론부터 말하자면, new 연산자를 없애기 위해 사용하는 패턴이라고 생각하면 됩니다!

 

근데 new 연산자가 무슨 잘못이 있어요..?

 

아직은 잘 모르겠죠? 오늘도 침착맨과 함께 팩토리 패턴에 대해 알아봐요!

 

<상황>

2014년경 이병건은 웹툰 작가 이말년이자 다음팟 PD 침착맨으로 활동하고 있었다.

이말년은 작업실에서 태블릿으로 이말년 서유기를 그렸고, 침착맨은 스튜디오에서 하스스톤 게임 방송을 했다.

 

 

이 상황을 코드로 옮겨봐요!

이말년은 태블릿이, 침착맨은 방송용 카메라가 필요하고,

두 전자기기 클래스에는 전자기기를 사용하는(use) 기능을 구현해야 할 것 같아요.

 

그러면 이런 형식이 되겠죠?

 

[CalmDownMan.java]

public class CalmDownMan {
    private Camera camera;

    public CalmDownMan() {
        this.camera = new Camera();
    }

    public void playHearthstone() {
        camera.use();
    }
}

 

[LeeLaterYears.java]

public class LeeLaterYears { // 이말년
    private Tablet tablet;

    public LeeLaterYears() {
        this.tablet = new Tablet();
    }

    public void drawCartoon() {
        tablet.use();
    }
}

 

 

[Camera.java]

public class Camera {
    String deviceName = "방송용 카메라";

    public void use()  {
        System.out.printf("%s로 하스스톤 영상을 녹화합니다.\n", deviceName);
    }
}

 

[Tablet.java]

public class Tablet {
    String deviceName = "태블릿";

    public void use()  {
        System.out.printf("%s으로 이말년 서유기를 그립니다.\n", deviceName);
    }
}

 

그런데 만들고보니 비슷한 형식이 보이네요!

기왕이면 수퍼 클래스를 만들고 이를 상속하는 방식으로 만드는게 좋겠어요.

 

우선 카메라와 태블릿을 전자기기 클래스로 묶겠습니다.

 

[DigitalDevice.java]

public abstract class DigitalDevice {
    String deviceName;

    public abstract void use();
}

 

그러면 카메라와 태블릿도 이에 맞춰서 구현해야겠죠?

 

[Camera.java]

public class Camera extends DigitalDevice {
    public Camera() {
        deviceName = "방송용 카메라";
    }

    @Override
    public void use()  {
        System.out.printf("%s로 하스스톤 영상을 녹화합니다.\n", deviceName);
    }
}

 

[Tablet.java]

public class Tablet extends DigitalDevice {
    public Tablet() {
        deviceName = "태블릿";
    }

    @Override
    public void use()  {
        System.out.printf("%s으로 이말년 서유기를 그립니다.\n", deviceName);
    }
}

 

전자기기 클래스를 만들었으니, 그 다음으로 수퍼 클래스인 이병건 클래스를 만들어 봐요.

 

[LeeByeongGeon.java]

public abstract class LeeByeongGeon {
    DigitalDevice device;

    public void useDevice() {
        device.use();
    }
}

 

하위 클래스는 이렇게 바꿔주면 되겠네요.

 

[CalmDownMan.java]

public class CalmDownMan extends LeeByeongGeon {
    public CalmDownMan() {
        this.device = new Camera();
    }
}

 

[LeeLaterYears.java]

public class LeeLaterYears extends LeeByeongGeon {
    public LeeLaterYears() {
        this.device = new Tablet();
    }
}

 

침착맨과 이말년 클래스를 성공적으로 구현했어요!

 

그럼 잘 동작하는지 실행해볼까요?

 

LeeByeongGeon calmDownMan = new CalmDownMan();
calmDownMan.useDevice();

LeeByeongGeon leeLaterYears = new LeeLaterYears();
leeLaterYears.useDevice();

>> 방송용 카메라로 하스스톤 영상을 녹화합니다.
>> 태블릿으로 이말년 서유기를 그립니다.

 

예상한 대로 잘 동작하고 있습니다~

 

그럼 다음 상황으로 넘어가보죠!

 

<상황2>

그러던 어느날 침착맨이 아침에 차돌짬뽕을 먹고 기분 좋게 스튜디오로 출근했다.

 

"지식은... 우정을 대신할 수 없어... 후루룩"

 

스튜디오에 도착한 침착맨이 하스스톤 게임 방송을 시작하려고 카메라를 찾는 순간

 

"아! 카메라 놓고 왔다."

 

결국 침착맨은 그날 하루 혼자서 게임을 했다.

 

이 날 이후로 침착맨은 카메라와 태블릿을 집에 두고 다니지 않고, 스튜디오와 작업실에 각각 두고 다니기로 했다.

 

 

이러한 변화에 맞춰서 코드도 변화시켜 볼까요?

 

이전에는 침착맨 클래스의 전자기기 멤버를 생성자에서 초기화했습니다.

마치 침착맨이 집에서 나올 때 카메라를 챙기는 것처럼요.

 

[CalmDownMan.java]

public class CalmDownMan extends LeeByeongGeon {
    public CalmDownMan() {
        this.device = new Camera(); // 집에서 나올 때 카메라를 챙긴다.
    }
}

 

침착맨 클래스같은 하위 클래스에서 전자기기 멤버를 초기화하는 것을 까먹으면 프로그램에는 오류가 발생합니다.

마치 침착맨이 까먹고 전자기기를 집에 놓고 간 것처럼요.

 

이제 침착맨은 매일 아침 집에서 전자기기를 챙겨나오는 대신,

일단 스튜디오나 작업실로 이동한 다음에 그 장소에서 전자기기를 찾습니다.

 

코드도 이와 비슷하게 변경하려고 해요.

이제는 생성자에서 전자기기 멤버를 초기화하지 않고,

전자기기를 사용하는 useDevice() 메소드 내부에서 첫 작업으로 전자기기 멤버를 초기화해보죠.

 

우선 코드를 어떻게 바꿀지 살펴보기 위해 기존의 이병건 클래스를 다시 한 번 볼까요?

 

[LeeByeongGeon.java]

public abstract class LeeByeongGeon {
    DigitalDevice device;

    public void useDevice() {
        device.use();
    }
}

 

현재는 전자기기 멤버를 초기화하는 부분이 하위 클래스의 생성자 안에 있는데, 

useDevice() 내부의 device.use(); 앞부분에서 전자기기 멤버를 초기화하도록 바꾸면 될 것 같네요!

 

근데 전자기기 멤버를 초기화하려고 보니 하위클래스마다 다른 클래스를 사용해야 하는 문제가 있어요.

 

어떡하면 좋을까요?

 

useDevice() 메소드를 추상 메소드로 바꾼 후 서브 클래스에서 상황에 맞게 구현하면 어떨까요?

 

꽤 좋은 방법 같아요!

아마도 이런 형식이 되겠죠?

 

[LeeByongGeon.java]

public abstract class LeeByeongGeon {
    DigitalDevice device;

    public abstract void useDevice();
}

 

[CalmDownMan.java]

public class CalmDownMan extends LeeByeongGeon {
    @Override
    public void useDevice() {
        device = new Camera();
        device.use();
    }
}

 

거의 완벽합니다!!...만 아직 조금 더 개선해볼 수 있을 것 같아요.

(이 부분이 오늘 배우는 팩토리 패턴을 사용하는 이유가 되겠죠? 😉)

 

어려우니까 질문 안 하고 답변부터 말씀드릴게요!

지금부터는 비유적으로 생각해봐요!

 

여태까지 스튜디오에서 방송용 카메라를 찾는 작업을 카메라 객체를 생성하는 코드처럼 생각했죠?

이 관점으로 침착맨 클래스를 한 번 더 살펴봅시다.

 

[CalmDownMan.java]

public class CalmDownMan extends LeeByeongGeon {
    @Override
    public void useDevice() {
        device = new Camera(); // 스튜디오에서 카메라를 찾고,
        device.use();          // 카메라로 하스스톤 영상을 녹화한다.
    }
}

 

useDevice() 메소드를 같이 해석해봐요. 전자기기를 사용한다는 게 어떤 일인가요?

첫 째로 스튜디오에서 카메라를 찾고, 그 다음에는 카메라를 사용해서 게임 영상을 촬영합니다.

 

현실 세계가 잘 반영되어 있네요!

 

그러면 이병건 클래스도 다시 한 번 살펴볼까요?

 

[LeeByongGeon.java]

public abstract class LeeByeongGeon {
    DigitalDevice device;

    public abstract void useDevice();
}

 

마찬가지로 useDevice() 메소드를 같이 해석해볼까요? 전자기기를 사용한다는 게 어떤 일인가요?

 

답은 "모른다"에요.

서브클래스에서 구현될테니까 좀 더 정확히 말하면 "작업실이나 스튜디오에 가봐야 알아요"겠죠?

 

뭔가 이상하네요...

어디 가는지 몰라도 전자기기를 사용하는 일이 어떤 작업을 포함하는 지는 너무 뻔하거든요.

그 장소의 어딘가에 있던 전자기기를 가져오고, 전자기기를 사용하는 일이지 않을까요?

 

분명히 "객체 지향은 현실을 모델링한다"고 했는데 어딘가 어긋난 것 같아요.

 

그럼 우리가 왜 모른다고 대답할 수 밖에 없었는지,

즉 우리가 왜 이병건 클래스에서 useDevice() 메소드를 추상 메소드로 선언할 수 밖에 없었는지 다시 한 번 생각해봐요!

(힘드시죠? 거의 다 왔어요!)

 

처음에는 useDevice() 메소드 내부에서  전자기기 클래스 멤버를 초기화하고 싶었는데,

이병건 클래스의 하위클래스마다 다른 클래스의 인스턴스를 생성해야 하는 문제에 부딪혔었죠.

 

즉 전자기기를 가져온다는 말을 하면 되는데,

카메라 또는 태블릿을 가져온다는 말 밖에 할 수가 없어서 "모른다"고 대답을 할 수 밖에 없었던 거에요.

 

이것이 바로 글의 처음에 말했던 new 연산자의 문제점이에요!!

new 연산자는 구상 클래스의 인스턴스밖에 생성할 수 없어요.

 

DigitalDevice device = new Camera();

또는

DigitalDevice device = new Tablet();

와 같이 카메라 객체나 태블릿 객체만 생성할 수 있어요.

 

하지만 팩토리 메서드 패턴을 이용하면 전자기기 객체를 생성할 수 있어요.

그리고 이 객체의 타입은 하위 클래스가 생성될 때 필요에 따라 카메라 클래스 또는 태블릿 클래스로 결정돼요.

 

많이 이상하죠?

 

어떤 방법인지 보고나면 느낌이 올 거에요!

 

[LeeByongGeon.java]

public abstract class LeeByeongGeon {
    DigitalDevice device;

    public void useDevice() {
    	device = setUpDevice(); // 전자기기를 가져오고,
        device.use();           // 전자기기를 사용한다.
    };
    
    public abstract DigitalDevice setUpDevice();
}

 

우리가 new 연산자만 가지고는 하지 못했던 "전자기기를 가져온다"는 말을 추상 메소드 setUpDevice()를 통해 표현했어요.

 

정확하게 얘기하자면, 추상 메소드 setUpDevice()를 통해 전자기기의 하위 클래스 타입 객체를 생성했어요.

이 객체는 이말년 클래스에서는 카메라 타입으로, 침착맨 클래스에서는 태블릿 타입으로 생성돼요.

 

조금 어렵죠? 읽고 읽고 또 읽다 보면 언젠가는 이해될... 지도... 😂

 

아무튼 이제야 useDevice() 메소드가 현실 세계를 잘 반영한다는 느낌이 들어요!

 

그럼 나머지 침착맨, 이말년 클래스도 이병건 클래스에 맞춰서 구현해볼까요?

 

[CalmDownMan.java]

public class CalmDownMan extends LeeByeongGeon {
    @Override
    public DigitalDevice setUpDevice() { // 스튜디오에서 가져오는 전자기기는
        return new Camera();             // 카메라
    }
}

 

[LeeLaterYears.java]

public class LeeLaterYears extends LeeByeongGeon {
    @Override
    public DigitalDevice setUpDevice() { // 작업실에서 가져오는 전자기기는
        return new Tablet();             // 태블릿
    }
}

 

각각 추상 메서드 setUpDevice()가 카메라와 태블릿을 반환하도록 구현했어요.

이로써 코드를 성공적으로 구현했습니다~~

 

<결론>

 

지금까지 사용한 패턴이 바로 팩토리 메서드 패턴이에요.

 

팩토리 메서드 패턴이 뭔지 정의를 살펴 볼까요?

 

팩토리 메서드 패턴객체를 생성하는 인터페이스를 만듭니다.

어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정합니다.

 

분명히 이해했다고 생각했는데... 무슨 소린지 모르겠죠? 😂

 

한 줄 한 줄 살펴봐요!

 

팩토리 메서드라는 말은 공장 메소드, 즉 무언가를 만드는 메서드라는 뜻이겠죠?

메서드가 만드는 것이 바로 객체라는 뜻이에요.

여기서 인터페이스는 꼭 인터페이스 타입뿐만 아니라 추상 클래스나 추상 메서드도 포함하는 개념이에요.

 

이번 글에서는 setUpDevice() 메서드전자기기 클래스의 하위 클래스 객체를 생성하는 팩토리 메서드였어요.

 

어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정한다는 말은 어느 정도 이해가 가시나요?

이번 글에서는 침착맨과 이말년이 setUpDevice() 메서드를 오버라이드해서 각각 카메라, 태블릿 객체를 생성하도록 했었죠!

 

팩토리 메서드 패턴에 대한 소개는 여기까지에요.

저도 글을 쓰면서 실시간으로 새로운 내용을 알게 되는 놀라운 경험을 하고 있습니다... 하하...

 

여러분도 복습 열심히 하셔서 패턴을 잘 익히시길 바래요! 파이팅~