공부해봅시당
[디자인 패턴] 생성 패턴 - 팩토리 메소드 패턴 본문
의도
팩토리 메서드는 부모 클래스에서 객체들을 생성할 수 있는 인터페이스를 제공하지만, 자식 클래스들이 생성될 객체들의 유형을 변경할 수 있도록 하는 생성 패턴
문제
물류 관리 앱을 개발하고 있다고 가정
앱의 첫 번째 버전은 트럭 운송만 처리할 수 있어서 대부분의 코드가 Truck(트럭) 클래스에 있음
앱이 유명해지면서 매일 해상 물류 회사들로부터 해상 물류 기능을 앱에 추가해 달라는 요청을 수십 개씩 받기 시작했다고 가정
좋은 소식이지만 현재 대부분의 코드는 Truck 클래스에 결합되어 있음
앱에 Ship(선박) 클래스를 추가하려면 전체 코드 베이스를 변경해야 함
또한 차후 앱에 다른 유형의 교통수단을 추가하려면 아마도 다시 전체 코드 베이스를 변경해야 할 것
그러면 결과적으로 많은 조건문이 운송 수단 객체들의 클래스에 따라 앱의 행동을 바꾸는 매우 복잡한 코드가 작성될 것
해결책
팩토리 메소드 패턴은 (new 연산자를 사용한) 객체 생성 직접 호출들을 특별한 팩토리 메소드에 대한 호출들로 대체하라고 제안함
객체들은 여전히 new 연산자를 통해 생성되지만 팩토리 메소드 내에서 호출됨
참고로 팩토리 메소드에서 반환된 객체는 종종 제품이라고도 불림
얼핏 이러한 변경은 무의미해 보일 수도 있는데, 그 이유는 생성자 호출을 프로그램의 한 부분에서 다른 부분으로 옮겼을 뿐이기 때문임
하지만 위와 같은 변경 덕분에 이제 자식 클래스에서 팩토리 메소드를 오버라이딩하고 그 메소드에 의해 생성되는 제품들의 클래스를 변경할 수 있게 됨
BUT, 아직은 약간의 제한이 있음
자식 클래스들은 다른 유형의 제품들을 해당 제품들이 공통 기초 클래스 또는 공통 인터페이스가 있는 경우에만 반환할 수 있음
또 이전에 언급한 모든 제품들에 공통인 Transport 인터페이스로 Logistics 기초 클래스의 createTransport 팩토리 메소드의 반환 유형을 선언해야 함
예를 들어 Truck 과 Ship 클래스들은 모두 Transport 인터페이스를 구현해야 하며, 이 인터페이스는 deliver(배달)라는 메소드를 선언함
그러나 각 클래스는 이 메서드를 다르게 구현함
트럭은 육로로 화물을 배달하고 선박은 해상으로 화물을 배달함
RoadLogistics(도로 물류) 클래스에 포함된 팩토리 메서드는 Truck 객체들을 반환하는 반면, SeaLogistics(해운 물류) 클래스에 포함된 팩토리 메서드는 선박 객체들을 반환함
팩토리 메서드를 사용하는 코드를 종종 클라이언트 코드라고 부르며, 클라이언트 코드는 다양한 자식 클래스들에서 실제로 반환되는 여러 제품 간의 차이에 대해 알지 못함
클라이언트 코드는 모든 제품을 추상 Transport(운송체계)로 간주함
클라이언트는 모든 Transport 객체들이 deliver(배달) 메소드를 가져야 한다는 사실을 잘 알고 있지만, 이 메소드가 정확히 어떻게 작동하는지는 클라이언트에게 중요하지 않음
구조
위 사진의 번호와 대응되도록 아래 설명 작성함
- 제품: 인터페이스를 선언. 인터페이스는 생성자와 자식 클래스들이 생성할 수 있는 모든 객체의 공통.
- 구상 제품들: 제품 인터페이스의 다양한 구현들
- 크리에이터(Creator) 클래스: 새로운 제품 객체들을 반환하는 팩토리 메서드를 선언
- 팩토리 메서드를 abstract(추상)로 선언하여 모든 자식 클래스들이 각각 이 메서드의 자체 버전들을 구현하도록 강제할 수 있으며, 대안적으로 기초 팩토리 메서드가 디폴트(기본값) 제품 유형을 반환하도록 만들 수도 있음
- 중요한 점은 이 팩토리 메서드의 반환 유형이 제품 인터페이스와 일치해야 한다는 것
- 크리에이터라는 이름에도 불구하고 크리에이터의 주책임은 제품을 생성하는 것이 아님
- 일반적으로 크리에이터 클래스에는 이미 제품과 관련된 핵심 비즈니스 로직이 있으며, 팩토리 메서드는 이 로직을 구상 제품 클래스들로부터 디커플링(분리) 하는 데 도움을 줄 뿐임
- 대규모 소프트웨어 개발사에 비유해 보자면, 이 회사는 프로그래머들을 위한 교육 부서가 있을 수 있으나, 회사의 주 임무는 프로그래머를 교육하는 것이 아니라 코드를 작성하는 것
- 구상 크리에이터들: 기초 팩토리 메서드를 오버라이드(재정의)하여 다른 유형의 제품을 반환하게 하도록 함
참고로 팩토리 메서드는 항상 새로운 인스턴스들을 생성해야 할 필요가 없음
팩토리 메서드는 기존 객체들을 캐시, 객체 풀 또는 다른 소스로부터 반환할 수 있음
의사코드
어떻게 팩토리 메서드가 클라이언트 코드를 구상 UI 클래스들과 결합하지 않고도 크로스 플랫폼 UI 요소들을 생성할 수 있는지를 보여줌
기초 Dialog(대화 상자) 클래스는 여러 UI 요소들을 사용하여 대화 상자를 렌더링함
다양한 운영 체제에서 이러한 요소들은 약간씩 다르게 보일 수 있지만 여전히 일관되게 작동해야 함
예를 들어 윈도우에서의 버튼은 리눅스에서도 여전히 버튼이어야 함
팩토리 메서드가 적용되면, 대화 상자 로직을 각 운영 체제를 위하여 반복해서 재작성할 필요가 없음
기초 Dialog 클래스 내에서 버튼을 생성하는 팩토리 메서드를 선언하면, 나중에 팩토리 메서드에서 윈도우 유형의 버튼들을 반환하는 Dialog 자식 클래스를 생성할 수 있음
그 후 이 자식 클래스는 기초 클래스로부터 Dialog의 코드 대부분을 상속받으나, 팩토리 메서드 덕분에 윈도우 유형의 버튼들도 렌더링할 수 있음
이 패턴이 작동하려면 기초 Dialog 클래스가 추상 버튼들과 함께 작동해야 함
(참고로 추상 버튼은 모든 구상 버튼들이 따르는 인터페이스 또는 기초 클래스)
이렇게 해야 다이얼로그 코드가 버튼 유형에 관계없이 작동함
물론, 위 접근 방식을 다른 UI 요소들에도 적용할 수 있으나, 대화 상자에 새로운 팩토리 메서드를 추가할 때마다 이 프로그램은 추상 팩토리 패턴에 더 가까워짐
// 크리에이터 클래스는 제품 클래스의 객체를 반환해야 하는 팩토리 메서드를
// 선언합니다. 크리에이터의 자식 클래스들은 일반적으로 이 메서드의 구현을
// 제공합니다.
class Dialog is
// 크리에이터는 팩토리 메서드의 일부 디폴트 구현을 제공할 수도 있습니다.
abstract method createButton():Button
// 크리에이터의 주 업무는 제품을 생성하는 것이 아닙니다. 크리에이터는
// 일반적으로 팩토리 메서드에서 반환된 제품 객체에 의존하는 어떤 핵심
// 비즈니스 로직을 포함합니다. 자식 클래스들은 팩토리 메서드를 오버라이드 한
// 후 해당 메서드에서 다른 유형의 제품을 반환하여 해당 비즈니스 로직을
// 간접적으로 변경할 수 있습니다.
method render() is
// 팩토리 메서드를 호출하여 제품 객체를 생성하세요.
Button okButton = createButton()
// 이제 제품을 사용하세요.
okButton.onClick(closeDialog)
okButton.render()
// 구상 크리에이터들은 결과 제품들의 유형을 변경하기 위해 팩토리 메서드를
// 오버라이드합니다.
class WindowsDialog extends Dialog is
method createButton():Button is
return new WindowsButton()
class WebDialog extends Dialog is
method createButton():Button is
return new HTMLButton()
// 제품 인터페이스는 모든 구상 제품들이 구현해야 하는 작업들을 선언합니다.
interface Button is
method render()
method onClick(f)
// 구상 제품들은 제품 인터페이스의 다양한 구현을 제공합니다.
class WindowsButton implements Button is
method render(a, b) is
// 버튼을 윈도우 스타일로 렌더링하세요.
method onClick(f) is
// 네이티브 운영체제 클릭 이벤트를 바인딩하세요.
class HTMLButton implements Button is
method render(a, b) is
// 버튼의 HTML 표현을 반환하세요.
method onClick(f) is
// 웹 브라우저 클릭 이벤트를 바인딩하세요.
class Application is
field dialog: Dialog
// 앱은 현재 설정 또는 환경 설정에 따라 크리에이터의 유형을 선택합니다.
method initialize() is
config = readApplicationConfigFile()
if (config.OS == "Windows") then
dialog = new WindowsDialog()
else if (config.OS == "Web") then
dialog = new WebDialog()
else
throw new Exception("Error! Unknown operating system.")
// 클라이언트 코드는 비록 구상 크리에이터의 기초 인터페이스를 통하는 것이긴
// 하지만 구상 크리에이터의 인스턴스와 함께 작동합니다. 클라이언트가
// 크리에이터의 기초 인터페이스를 통해 크리에이터와 계속 작업하는 한 모든
// 크리에이터의 자식 클래스를 클라이언트에 전달할 수 있습니다.
method main() is
this.initialize()
dialog.render()
적용
- 팩토리 메서드는 코드가 함께 작동해야 하는 객체들의 정확한 유형들과 의존관계들을 미리 모르는 경우 사용하는 것 추천
- 팩토리 메서드는 제품 생성 코드를 제품을 실제로 사용하는 코드와 분리 > 제품 생성자 코드를 나머지 코드와는 독립적으로 확장하기 쉬워짐
- 예를 들어, 앱에 새로운 제품을 추가하려면 당신은 새로운 크리에이터 자식 클래스를 생성한 후 해당 클래스 내부의 팩토리 메서드를 오버라이딩(재정의)하기만 하면 됨
- 팩토리 메서드는 당신의 라이브러리 또는 프레임워크의 사용자들에게 내부 컴포넌트들을 확장하는 방법을 제공하고 싶을 때 추천
- 상속(inheritance)은 라이브러리나 프레임워크의 디폴트 행동을 확장하는 가장 쉬운 방법
- 그러나 프레임워크는 표준 컴포넌트 대신 당신의 자식 클래스를 사용해야 한다는 것을 어떻게 인식할까?
- 해결책은 일단 프레임워크 전체에서 컴포넌트들을 생성하는 코드를 단일 팩토리 메서드로 줄인 후, 누구나 이 팩토리 메서드를 오버라이드 할 수 있도록 하는 것
- 예시
- 오픈 소스 UI 프레임워크를 사용하여 앱을 작성하고 있고, 개발 중인 앱에는 둥근 버튼들이 필요한데 프레임워크는 사각형 버튼만 제공한다고 가정
- 또 표준 Button(버튼) 클래스는 RoundButton(둥근 버튼) 자식 클래스로 확장했지만, 이제 메인 UIFramework(사용자 인터페이스 프레임워크) 클래스에 디폴트 클래스 대신 새로운 RoundButton(둥근 버튼) 자식 클래스를 사용하라고 지시해야 한다고 가정
- 이를 달성하려면 기초 프레임워크 클래스에서 자식 클래스 UIWithRoundButtons를 만들어서 기초 프레임워크 클래스의 createButton 메서드를 오버라이딩(재정의)해야 함
- 이 메서드는 기초 클래스에 Button 객체들을 반환하지만, 당신은 당신의 자식 클래스가 RoundButton 객체들을 반환하도록 만듦
- 이제 UIFramework 클래스 대신 UIWithRoundButtons 클래스를 사용하면 됨
- 팩토리 메서드는 기존 객체들을 매번 재구축하는 대신 이들을 재사용하여 시스템 리소스를 절약하고 싶을 때 추천
- 이러한 요구 사항은 데이터베이스 연결, 파일 시스템 및 네트워크처럼 시스템 자원을 많이 사용하는 대규모 객체들을 처리할 때 자주 발생
- 기존 객체를 재사용하려면 무엇을 해야 할까?
- 필요한 단계
- 먼저 생성된 모든 객체를 추적하기 위해 일부 스토리지를 생성해야 함
- 누군가가 객체를 요청하면 프로그램은 해당 풀 내에서 유휴(free) 객체를 찾아야 함
- 이 객체를 클라이언트 코드에 반환해야 함
- 유휴(free) 객체가 없으면, 프로그램은 새로운 객체를 생성해야 함 (그리고 풀에 이 객체를 추가해야 함
- 문제
- 이것은 정말로 많은 양의 코드가 필요함
- 그리고 프로그램을 중복 코드로 오염시키지 않도록 이 많은 양의 코드를 모두 한곳에 넣어야 함
- 아마도 이 코드를 배치할 수 있는 가장 확실하고 편리한 위치는 우리가 재사용하려는 객체들의 클래스의 생성자일 것 > 그러나 생성자는 특성상 항상 새로운 객체들을 반환해야 하며, 기존 인스턴트를 반환할 수는 없음
- 해결
- 따라서 새 객체들을 생성하고 기존 객체를 재사용할 수 있는 일반적인 메서드가 필요 > 팩토리 메소드 사용 추천
- 필요한 단계
구현방법
- 모든 제품이 같은 인터페이스를 따르도록 설정. 이 인터페이스는 모든 제품에서 의미가 있는 메서드들을 선언해야 함
- 크리에이터 클래스 내부에 빈 팩토리 메서드 추가. 이 메서드의 반환 유형은 공통 제품 인터페이스와 일치해야 함
- 크리에이터의 코드에서 제품 생성자들에 대한 모든 참조 찾기.
- 이 참조들을 하나씩 팩토리 메소드에 대한 호출로 교체하면서 제품 생성 코드를 팩토리 메서드로 추출
- 이 시점에서 팩토리 메서드의 코드는 꽤 복잡할 수 있음
- 예를 들어 인스턴트화할 제품 클래스를 선택하는 큰 switch 문장이 있을 수 있음
- 하지만 걱정하지 않아도 됨. 곧 해결 가능.
- 반환된 제품의 유형을 제어하기 위해 팩토리 메서드에 임시 매개변수를 추가해야 할 수도 있음
- 팩토리 메서드에 나열된 각 제품 유형에 대한 크리에이터 자식 클래스들의 집합을 생성한 후, 자식 클래스들에서 팩토리 메서드를 오버라이딩하고 기초 메서드에서 생성자 코드의 적절한 부분들을 추출
- 제품 유형이 너무 많아 모든 제품에 대하여 자식 클래스들을 만드는 것이 합리적이지 않을 경우, 자식 클래스들의 기초 클래스의 제어 매개변수를 재사용할 수 있음
- 예를 들어, 다음과 같은 클래스 계층구조가 있다고 상상해보자
- Mail(우편) 기초 클래스의 자식 클래스들은 AirMail(항공우편)과 GroundMail(지상우편)
- Transport(운송수단) 클래스의 자식 클래스들은 Plane(비행기), Truck(트럭), 그리고 Train(기차)
- AirMail(항공우편) 클래스는 Plane(비행기) 객체만 사용하지만, GroundMail(지상우편)은 Truck 과 Train 객체들 모두 사용할 수 있음
- 이 두 가지 경우를 모두 처리하기 위해 새 자식 클래스(예: TrainMail(기차우편))를 만들 수도 있으나, 다른 방법도 있음. 클라이언트 코드가 받으려는 제품을 제어하기 위해 GroundMail 클래스의 팩토리 메서드에 전달인자(argument)를 전달하는 방법
- 예를 들어, 다음과 같은 클래스 계층구조가 있다고 상상해보자
- 추출이 모두 끝난 후 기초 팩토리 메서드가 비어 있으면, 해당 팩토리 메서드를 추상화할 수 있음. 팩토리 메서드가 비어 있지 않으면, 나머지를 그 메서드의 디폴트 행동으로 만들 수 있음
장단점
장점
- 크리에이터와 구상 제품들이 단단하게 결합되지 않도록 할 수 있음
- 단일 책임 원칙. 제품 생성 코드를 프로그램의 한 위치로 이동하여 코드를 더 쉽게 유지관리할 수 있음
- 개방/폐쇄 원칙. 기존 클라이언트 코드를 훼손하지 않고 새로운 유형의 제품들을 프로그램에 도입할 수 있음
단점
- 패턴을 구현하기 위해 많은 새로운 자식 클래스들을 도입해야 하므로 코드가 더 복잡해질 수 있음. 가장 좋은 방법은 크리에이터 클래스들의 기존 계층구조에 패턴을 도입하는 것
다른 패턴과의 관계
- 많은 디자인은 복잡성이 낮고 자식 클래스들을 통해 더 많은 커스터마이징이 가능한 팩토리 메서드로 시작해 더 유연하면서도 더 복잡한 추상 팩토리, 프로토타입 또는 빌더 패턴으로 발전해 나감
- 추상 팩토리 클래스들은 팩토리 메서드들의 집합을 기반으로 하는 경우가 많음. 하지만 프로토타입을 사용하여 추상 팩토리 구상 클래스들의 생성 메서드들을 구현할 수도 있음
- 팩토리 메서드를 반복자와 함께 사용하여 컬렉션 자식 클래스들이 해당 컬렉션들과 호환되는 다양한 유형의 반복자들을 반환하도록 할 수 있음
- 프로토타입은 상속을 기반으로 하지 않으므로 상속과 관련된 단점들이 없지만 복제된 객체의 복잡한 초기화가 필요. 팩토리 메서드는 상속을 기반으로 하지만 초기화 단계가 필요하지 않음
- 팩토리 메서드는 템플릿 메서드의 특수화라고 생각할 수 있음. 동시에 대규모 템플릿 메서드의 한 단계의 역할을 팩토리 메서드가 할 수 있음
코드 예시 - Java
팩토리 메서드 패턴은 자바 코드에서 널리 사용되며 코드에 높은 수준의 유연성을 제공해야 할 때 매우 유용
핵심 자바 라이브러리에 등장
- java.util.Calendar#getInstance()
- java.util.ResourceBundle#getBundle()
- java.text.NumberFormat#getInstance()
- java.nio.charset.Charset#forName()
- java.net.URLStreamHandlerFactory#createURLStreamHandler(String) (프로토콜에 따라 다른 싱글턴 객체를 반환)
- java.util.EnumSet#of()
- javax.xml.bind.JAXBContext#createMarshaller() 와 다른 유사한 메서드들.
팩토리 메서드는 구상 클래스들로부터 객체들을 생성하는 생성 메서드들로 인식될 수 있음
구상 클래스들은 객체 생성 중에 사용되지만 팩토리 메서드들의 반환 유형은 일반적으로 추상 클래스 또는 인터페이스로 선언됨
크로스 플랫폼 그래픽 사용자 요소들의 생성
버튼들은 제품의 역할, 다이얼로그들은 크리에이터의 역할
각 다른 다이얼로그 유형은 그의 고유한 요소 유형들이 필요
그러므로 각 다이얼로그 유형에 대한 자식 클래스를 만들고 해당 팩토리 메서드들을 오버라이드
각 다이얼로그 유형은 적절한 버튼 클래스들을 인스턴스화할 것
기초 다이얼로그는 공통 인터페이스를 사용하는 제품과 함께 작동하므로 모든 변경 후에도 해당 기초 다이얼로그의 코드가 계속 작동할 것
buttons
buttons/Button.java: 공통 제품 인터페이스
package refactoring_guru.factory_method.example.buttons;
/**
* Common interface for all buttons.
*/
public interface Button {
void render();
void onClick();
}
buttons/HtmlButton.java: 구상 제품
package refactoring_guru.factory_method.example.buttons;
/**
* HTML button implementation.
*/
public class HtmlButton implements Button {
public void render() {
System.out.println("<button>Test Button</button>");
onClick();
}
public void onClick() {
System.out.println("Click! Button says - 'Hello World!'");
}
}
buttons/WindowsButton.java: 또 하나의 구상 제품
package refactoring_guru.factory_method.example.buttons;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
* Windows button implementation.
*/
public class WindowsButton implements Button {
JPanel panel = new JPanel();
JFrame frame = new JFrame();
JButton button;
public void render() {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JLabel label = new JLabel("Hello World!");
label.setOpaque(true);
label.setBackground(new Color(235, 233, 126));
label.setFont(new Font("Dialog", Font.BOLD, 44));
label.setHorizontalAlignment(SwingConstants.CENTER);
panel.setLayout(new FlowLayout(FlowLayout.CENTER));
frame.getContentPane().add(panel);
panel.add(label);
onClick();
panel.add(button);
frame.setSize(320, 200);
frame.setVisible(true);
onClick();
}
public void onClick() {
button = new JButton("Exit");
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
frame.setVisible(false);
System.exit(0);
}
});
}
}
factory
factory/Dialog.java: 기초 크리에이터
package refactoring_guru.factory_method.example.factory;
import refactoring_guru.factory_method.example.buttons.Button;
/**
* Base factory class. Note that "factory" is merely a role for the class. It
* should have some core business logic which needs different products to be
* created.
*/
public abstract class Dialog {
public void renderWindow() {
// ... other code ...
Button okButton = createButton();
okButton.render();
}
/**
* Subclasses will override this method in order to create specific button
* objects.
*/
public abstract Button createButton();
}
factory/HtmlDialog.java: 구상 크리에이터
package refactoring_guru.factory_method.example.factory;
import refactoring_guru.factory_method.example.buttons.Button;
import refactoring_guru.factory_method.example.buttons.HtmlButton;
/**
* HTML Dialog will produce HTML buttons.
*/
public class HtmlDialog extends Dialog {
@Override
public Button createButton() {
return new HtmlButton();
}
}
factory/WindowsDialog.java: 또 하나의 구상 크리에이터
package refactoring_guru.factory_method.example.factory;
import refactoring_guru.factory_method.example.buttons.Button;
import refactoring_guru.factory_method.example.buttons.WindowsButton;
/**
* Windows Dialog will produce Windows buttons.
*/
public class WindowsDialog extends Dialog {
@Override
public Button createButton() {
return new WindowsButton();
}
}
Demo.java: 클라이언트 코드
package refactoring_guru.factory_method.example;
import refactoring_guru.factory_method.example.factory.Dialog;
import refactoring_guru.factory_method.example.factory.HtmlDialog;
import refactoring_guru.factory_method.example.factory.WindowsDialog;
/**
* Demo class. Everything comes together here.
*/
public class Demo {
private static Dialog dialog;
public static void main(String[] args) {
configure();
runBusinessLogic();
}
/**
* The concrete factory is usually chosen depending on configuration or
* environment options.
*/
static void configure() {
if (System.getProperty("os.name").equals("Windows 10")) {
dialog = new WindowsDialog();
} else {
dialog = new HtmlDialog();
}
}
/**
* All of the client code should work with factories and products through
* abstract interfaces. This way it does not care which factory it works
* with and what kind of product it returns.
*/
static void runBusinessLogic() {
dialog.renderWindow();
}
}
OutputDemo.txt: 실행 결과 (HtmlDialog)
<button>Test Button</button>
Click! Button says - 'Hello World!'
OutputDemo.png: 실행 결과 (WindowsDialog)
출처
https://refactoring.guru/ko/design-patterns/factory-method
팩토리 메서드 패턴
/ 디자인 패턴들 / 생성 패턴 팩토리 메서드 패턴 다음 이름으로도 불립니다: 가상 생성자, Factory Method 의도 팩토리 메서드는 부모 클래스에서 객체들을 생성할 수 있는 인터페이스를 제공하지
refactoring.guru
https://refactoring.guru/ko/design-patterns/factory-method/java/example
자바로 작성된 팩토리 메서드 / 디자인 패턴들
/ 디자인 패턴들 / 팩토리 메서드 / 자바 자바로 작성된 팩토리 메서드 팩토리 메서드는 제품 객체들의 구상 클래스들을 지정하지 않고 해당 제품 객체들을 생성할 수 있도록 하는 생성 디자인
refactoring.guru
'STUDY > 디자인 패턴' 카테고리의 다른 글
[디자인 패턴] MVC와 MVP 패턴 비교 (0) | 2023.06.09 |
---|---|
[디자인 패턴] MVC 패턴 (0) | 2023.06.09 |
[디자인패턴] 행동 패턴 - 전략 패턴 (0) | 2023.06.05 |
[디자인 패턴] 생성 패턴 - 싱글톤 패턴 (2) | 2023.06.05 |
[디자인패턴] 디자인 패턴이란? (0) | 2023.06.05 |