기타/Miscellaneous

Factory Design Pattern

bellmake 2025. 3. 27. 15:15
🔍 1단계: 추상 인터페이스 정의
코드
python
복사
편집
from abc import ABC, abstractmethod

class Coffee(ABC):
    @abstractmethod
    def prepare(self):
        pass
설명:
**추상 클래스(인터페이스)**인 Coffee를 정의한다.

Coffee 클래스는 직접 인스턴스를 생성할 수 없으며, 반드시 하위 클래스가 prepare() 메서드를 구현하도록 강제한다.

이를 통해 커피 종류가 달라져도 항상 같은 메서드(prepare)로 준비 과정을 처리할 수 있다.

☕️ 2단계: Concrete Product(구체적인 제품 클래스) 구현
코드
python
복사
편집
class Espresso(Coffee):
    def prepare(self):
        return "Preparing a rich and strong Espresso."


class Latte(Coffee):
    def prepare(self):
        return "Preparing a smooth and creamy Latte."


class Cappuccino(Coffee):
    def prepare(self):
        return "Preparing a frothy Cappuccino."
설명:
Concrete Product 클래스들(Espresso, Latte, Cappuccino)이 추상 클래스 Coffee를 상속하여 각기 다른 prepare 메서드를 구체적으로 구현한다.

각각의 클래스가 구체적인 제품의 제조법(메시지)을 정의한다.

🏭 3단계: Factory 클래스 구현 (CoffeeMachine)
코드
python
복사
편집
class CoffeeMachine:
    def make_coffee(self, coffee_type):
        if coffee_type == "Espresso":
            return Espresso().prepare()
        elif coffee_type == "Latte":
            return Latte().prepare()
        elif coffee_type == "Cappuccino":
            return Cappuccino().prepare()
        else:
            return "Unknown coffee type!"
설명:
Factory 역할을 하는 클래스(CoffeeMachine)를 통해 구체적인 커피 객체를 생성하고 준비 과정(prepare())을 수행한다.

사용자는 커피 타입(coffee_type)만 지정하면, Factory에서 조건을 판단하여 적절한 Concrete Product를 생성 및 준비한다.

이렇게 되면 사용자는 직접 커피 객체를 생성하지 않아도 된다. 즉, 커피 제작 방식의 변화나 새로운 커피 종류 추가 시 Factory만 수정하면 되어 유지보수성이 높아진다.

🎯 4단계: Factory 사용 (메인 프로그램)
코드
python
복사
편집
if __name__ == "__main__":
    machine = CoffeeMachine()

    coffee = machine.make_coffee("Espresso")
    print(coffee)  # Preparing a rich and strong Espresso.

    coffee = machine.make_coffee("Latte")
    print(coffee)  # Preparing a smooth and creamy Latte.

    coffee = machine.make_coffee("Cappuccino")
    print(coffee)  # Preparing a frothy Cappuccino.
설명:
메인 프로그램에서 CoffeeMachine 인스턴스를 만들고 make_coffee() 메서드를 호출하여 원하는 커피를 주문한다.

이 방식은 클라이언트가 복잡한 객체 생성 과정을 알지 않아도 간단한 인터페이스로 원하는 객체를 얻을 수 있게 한다.

📝 패턴 요약 (Factory Method Pattern)
Coffee (추상 클래스) → 추상 제품 (Product)

Espresso, Latte, Cappuccino → 구체적인 제품 (Concrete Product)

CoffeeMachine → Factory 클래스: 제품의 인스턴스를 생성하는 공장 역할

이렇게 하면 제품 객체 생성 과정을 분리하여, 코드의 확장성과 유지보수성을 크게 높일 수 있다.

즉, 새로운 커피 타입(예: Americano, Mocha)이 추가될 경우:

Concrete Product를 새로 구현하고,

Factory 메서드에 조건문만 추가해주면 된다.

이로써 코드가 더욱 깔끔하고 유연하게 관리될 수 있다.

 

 

활용 예시 - 학습에 사용할 방대한 양의 데이터가 다양한 형식을 가지고 있을 때에 대한 처리

import os
import zipfile
from abc import ABC, abstractmethod

import pandas as pd


# Define an abstract class for Data Ingestor
class DataIngestor(ABC):
    @abstractmethod
    def ingest(self, file_path: str) -> pd.DataFrame:
        """Abstract method to ingest data from a given file."""
        pass


# Implement a concrete class for ZIP Ingestion
class ZipDataIngestor(DataIngestor):
    def ingest(self, file_path: str) -> pd.DataFrame:
        """Extracts a .zip file and returns the content as a pandas DataFrame."""
        # Ensure the file is a .zip
        if not file_path.endswith(".zip"):
            raise ValueError("The provided file is not a .zip file.")

        # Extract the zip file
        with zipfile.ZipFile(file_path, "r") as zip_ref:
            zip_ref.extractall("extracted_data")

        # Find the extracted CSV file (assuming there is one CSV file inside the zip)
        extracted_files = os.listdir("extracted_data")
        csv_files = [f for f in extracted_files if f.endswith(".csv")]

        if len(csv_files) == 0:
            raise FileNotFoundError("No CSV file found in the extracted data.")
        if len(csv_files) > 1:
            raise ValueError("Multiple CSV files found. Please specify which one to use.")

        # Read the CSV into a DataFrame
        csv_file_path = os.path.join("extracted_data", csv_files[0])
        df = pd.read_csv(csv_file_path)

        # Return the DataFrame
        return df


# Implement a Factory to create DataIngestors
class DataIngestorFactory:
    @staticmethod
    def get_data_ingestor(file_extension: str) -> DataIngestor:
        """Returns the appropriate DataIngestor based on file extension."""
        if file_extension == ".zip":
            return ZipDataIngestor()
        else:
            raise ValueError(f"No ingestor available for file extension: {file_extension}")


# Example usage:
if __name__ == "__main__":
    # # Specify the file path
    # file_path = "/Users/ayushsingh/Desktop/end-to-end-production-grade-projects/prices-predictor-system/data/archive.zip"

    # # Determine the file extension
    # file_extension = os.path.splitext(file_path)[1]

    # # Get the appropriate DataIngestor
    # data_ingestor = DataIngestorFactory.get_data_ingestor(file_extension)

    # # Ingest the data and load it into a DataFrame
    # df = data_ingestor.ingest(file_path)

    # # Now df contains the DataFrame from the extracted CSV
    # print(df.head())  # Display the first few rows of the DataFrame
    pass