Skip to content

Latest commit

 

History

History
742 lines (570 loc) · 15.4 KB

slides.md

File metadata and controls

742 lines (570 loc) · 15.4 KB
theme title info class highlighter drawings transition mdc fonts
default
Python でファクトリメソッド?
## 「Python でファクトリメソッド?」 Slides for 東葛.dev LT (2024.08.03)
text-right
shiki
persist
slide-left
true
sans mono
Kosugi Maru
Fira Code

Python でファクトリメソッド?

ナイトウ
Press Space for next page
{{ nowStrRef }}
<script setup lang="ts"> import { ref, onMounted } from "vue"; const now = new Date(); const nowStr = now.toLocaleString(); const nowStrRef = ref(nowStr); function tick() { const newTime = new Date(); const newTimeStr = newTime.toLocaleString(); nowStrRef.value = newTimeStr; } onMounted(() => { setInterval(tick, 1000); tick(); }) </script> <style> h1 span { background: linear-gradient(315deg, #FFD343, #3776AB); background-clip: text; -webkit-background-clip: text; -webkit-text-fill-color: transparent; } </style>

title: slide-link level: 2

このスライドはこちらで

QR Code
<script setup> import { ref } from "vue"; import { useQRCode } from "@vueuse/integrations/useQRCode"; const text = ref("https://kosuke222naito.github.io/20240803-lt/"); const qrcode = useQRCode(text); </script>

layout: two-cols layoutClass: gap-16 title: Table of Contents transition: view-transition

::right::

Table of Contents

<style> h1 span { background: linear-gradient(315deg, #FFD343, #3776AB); background-clip: text; -webkit-background-clip: text; -webkit-text-fill-color: transparent; } </style>

transition: slide-left title: 自己紹介 layout: two-cols layoutClass: gap-16


::right::

I'm ナイトウ

  • Python で Web バックエンド
  • Web フロントもやりたい
  • Vue が好き
  • since: 2021 ({{ years }}年目)
  • 駆け出しエンジニア? (非 CS 専攻)
  • (東葛dev 運営?)
  • 自作キーボード
  • 新本格ミステリ
  • エヴァ、シュタゲ
  • 元ハロヲタ (until: 2022)
<script setup lang="ts"> import { ref } from "vue"; const now = new Date(); const START_DATE = new Date(2021, 1, 1); const years = now.getFullYear() - START_DATE.getFullYear() + 1; </script> <style> .text-gradation { background: linear-gradient(45deg, #52D422, #2266D4); background-clip: text; -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-size: 400% 400%; } h1 span.text-gradation { animation: GradientBackground 3s ease infinite; } ul li span.text-gradation { animation: GradientBackground 5s ease infinite; } .python-gradation { background: linear-gradient(45deg, #3776AB, #FFD43B); background-clip: text; -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-size: 400% 400%; animation: GradientBackground 5s ease infinite; } @keyframes GradientBackground { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } }; </style>

layout: cover class: text-center

ファクトリメソッド

<style> div { color: black; background-color: white; } </style>

transition: slide-up level: 2

ファクトリメソッド #とは

他のクラスのコンストラクタをサブクラスで上書き可能な自分のメソッドに置き換えることで、

アプリケーションに特化したオブジェクトの生成をサブクラスに追い出し、クラスの再利用性を高めることを目的とする


Factory Method パターン - Wikipedia

クラス図

classDiagram
    direction LR
    class Animal {
        +String name
        +String sound()
    }
    class Dog {
        +String name
        +String sound() "bark"
    }
    class Cat {
        +String name
        +String sound() "meow"
    }
    class AnimalFactory {
        +createAnimal(type: String) Animal
    }

    Animal <|-- Dog
    Animal <|-- Cat
    AnimalFactory o-- Animal

Loading


transition: slide-up level: 2

ファクトリメソッドのいいところ

  1. 再利用性の向上
  2. 変更の容易さ
  3. 可読性の向上


transition: slide-up layout: two-cols layoutClass: gap-4

Python での例

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def __init__(self, name):
        self.name = name

    @abstractmethod
    def speak(self):
        pass

class Dog(Animal):
    def __init__(self, name):
        super().__init__(name)

    def speak(self):
        return "Woof!"

class Cat(Animal):
    def __init__(self, name):
        super().__init__(name)

    def speak(self):
        return "Meow!"

::right::

class AnimalFactory:
    @staticmethod
    def create(animal_type, name):
        if animal_type == "dog":
            return Dog(name)
        elif animal_type == "cat":
            return Cat(name)
        else:
            raise ValueError("Unknown type")


dog = AnimalFactory.create("dog", "Buddy")
cat = AnimalFactory.create("cat", "Whiskers")

print(dog.speak())  # "Woof!"
print(cat.speak())  # "Meow!"

layout: image image: https://cover.sli.dev


transition: slide-left

ファクトリメソッドを採用した経緯

  • 現在チャットbotを利用したアプリを作成中(PoC)
  • 外部のAPIサービスを利用して実現する必要がある
  • しかしサービス選定の時間はない


そこで利用するサービスに依存せず
コードの変更を簡単にしたい


transition: view-transition

実際の使用例(チャットbot)


インターフェース(抽象基底クラス)の定義

from abc import ABC, abstractmethod
from enum import Enum

class ChatBot(ABC):
    @abstractmethod
    def genrerate_response(self, messages) -> str:
        pass


class ChatBotServices(Enum):
    OPENAI = "openai"

transition: view-transition level: 2

実際の使用例(チャットbot)


具象クラスの作成(OpenAI API)

from openai import OpenAI

class OpenAIChatBot(ChatBot):
    def __init__(self):
        self.chatgpt_client = OpenAI(api_key=OPEN_AI_API_KEY)

    def genrerate_response(self, messages) -> str:
        completion = self.chatgpt_client.chat.completions.create(
            messages=messages,
            model="gpt-4o",
        )
        content = completion.choices[0].message.content

        return content

transition: view-transition level: 2

実際の使用例(チャットbot)


ファクトリ

class ChatBotFactory:
    _class: dict[str, type[ChatBot]] = {}

    @classmethod
    def register(cls, chat_bot_service: ChatBotServices):
        def wrapper(cls_):
            cls._class[chat_bot_service.value] = cls_
            return cls._class[chat_bot_service.value]

        return wrapper

    @classmethod
    def get_chat_bot_class(cls, chat_bot_service: ChatBotServices):
        return cls._class[chat_bot_service.value]


ChatBotFactory.register(ChatBotServices.OPENAI)(OpenAIChatBot)

https://zenn.dev/miyaji26/articles/fe4a50319ed799


transition: view-transition level: 2

実際の使用例(チャットbot)


デコレータとして

```python
from openai import OpenAI

class OpenAIChatBot(ChatBot):
    def __init__(self):
        self.chatgpt_client = OpenAI(api_key=OPEN_AI_API_KEY)

    def genrerate_response(self, messages) -> str:
        completion = self.chatgpt_client.chat.completions.create(
            messages=messages,
            model="gpt-4o",
        )
        content = completion.choices[0].message.content

        return content
```

```python
from openai import OpenAI

@ChatBotFactory.register(ChatBotServices.OPENAI)
class OpenAIChatBot(ChatBot):
    def __init__(self):
        self.chatgpt_client = OpenAI(api_key=OPEN_AI_API_KEY)

    def genrerate_response(self, messages) -> str:
        completion = self.chatgpt_client.chat.completions.create(
            messages=messages,
            model="gpt-4o",
        )
        content = completion.choices[0].message.content

        return content
```

transition: slide-left level: 2

実際の使用例(チャットbot)


ファクトリでインスタンスを作成

ChatBotClass = ChatBotFactory.get_chat_bot_class(ChatBotServices.OPENAI)
chat_bot = ChatBotClass()

transition: slide-left level: 2

これがやりたかった

ChatBotFactory.register(ChatBotServices.OPENAI)(OpenAIChatBot)
ChatBotFactory.register(ChatBotServices.CLAUDE)(ClaudeChatBot)
ChatBotFactory.register(ChatBotServices.GOOGLE_GEMINI)(GoogleGeminiChatBot)
ChatBotFactory.register(ChatBotServices.MICROSOFT_BING)(MicrosoftBingChatBot)

柔軟に使うサービスを選択できる

```python{all}{lines:true}
ChatBotClass = ChatBotFactory.get_chat_bot_class(ChatBotServices.OPENAI)
chat_bot = ChatBotClass()
chat_bot_response = chat_bot.generate_response(messages)
```

```python{all}{lines:true}
ChatBotClass = ChatBotFactory.get_chat_bot_class(ChatBotServices.CLAUDE)
chat_bot = ChatBotClass()
chat_bot_response = chat_bot.generate_response(messages)
```

```python{all}{lines:true}
ChatBotClass = ChatBotFactory.get_chat_bot_class(ChatBotServices.GOOGLE_GEMINI)
chat_bot = ChatBotClass()
chat_bot_response = chat_bot.generate_response(messages)
```

```python{all}{lines:true}
ChatBotClass = ChatBotFactory.get_chat_bot_class(ChatBotServices.MICROSOFT_BING)
chat_bot = ChatBotClass()
chat_bot_response = chat_bot.generate_response(messages)
```

transition: slide-left level: 2

ファクトリメソッドのあんまりよくないところ

使わない場合に比べてコードが複雑(難解)になる


transition: slide-up level: 2 layout: two-cols layoutClass: gap-16

補うために

  • ドキュメントを作成
  • ペアプロ

ドキュメントレビューでメンバーを巻き込む

::right::

# chat-app

## 基本設計方針

利用する外部APIがまだ決まっていないのでファクトリメソッドによる設計。
抽象クラス(インタフェース)を各サービスの外部APIを用いて実装したファイルをファクトリに登録
呼び出し元でどのサービスを利用するかを決める。

## ディレクトリ構成

`tree.txt` を参照

### `services` ディレクトリ

ビジネスロジックなどを担う

- `chat`

  - AI チャットサービス(Open AIなど)を利用したチャット生成
  - ChatGPT が苦手な最新情報(天気情報など)の取得を別の外部サービスで補う

- `geocoding`

  - ジオコーディング処理を担う
  - あくまでも関数としてのふるまいまでにとどめる


layout: image image: https://cover.sli.dev


transition: slide-left

ファクトリメソッドを採用してみての感想

  • 😇 過剰だったかもしれない
  • 📕 ドキュメントの作成を丁寧に行う必要があった
  • 🙋‍♂️ この作りにしたおかげで他メンバーへの引き継ぎスムーズにできた
  • 🤔 コンストラクタで異なる引数を受け取る場合
  • 🤫 Python の理解も足りてない
  • 😵‍💫 果たしてこれは本当にファクトリメソッドだったのか

transition: slide-left

まとめ

  • ファクトリメソッド: オブジェクトの生成をサブクラスに追い出し、クラスの再利用性を高める
  • ファクトリメソッドを使うべき状況
    • オブジェクトの生成方法が複雑な場合
    • 生成するオブジェクトの種類が増減する可能性がある場合
    • 生成ロジックをカプセル化したい場合