theme | title | info | class | highlighter | drawings | transition | mdc | fonts | |||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
default |
Python でファクトリメソッド? |
## 「Python でファクトリメソッド?」
Slides for 東葛.dev LT (2024.08.03)
|
text-right |
shiki |
|
slide-left |
true |
|
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>
<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>
::right::
<style> h1 span { background: linear-gradient(315deg, #FFD343, #3776AB); background-clip: text; -webkit-background-clip: text; -webkit-text-fill-color: transparent; } </style>::right::
- Python で Web バックエンド
- Web フロントもやりたい
- Vue が好き
- since: 2021 ({{ years }}年目)
- 駆け出しエンジニア? (非 CS 専攻)
- 柏 (東葛dev 運営?)
- 自作キーボード
- 新本格ミステリ
- エヴァ、シュタゲ
- 元ハロヲタ (until: 2022)
<style> div { color: black; background-color: white; } </style>
他のクラスのコンストラクタをサブクラスで上書き可能な自分のメソッドに置き換えることで、
アプリケーションに特化したオブジェクトの生成をサブクラスに追い出し、クラスの再利用性を高めることを目的とする
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
- 再利用性の向上
- 変更の容易さ
- 可読性の向上
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
- 現在チャットbotを利用したアプリを作成中(PoC)
- 外部のAPIサービスを利用して実現する必要がある
- しかしサービス選定の時間はない
そこで利用するサービスに依存せず
コードの変更を簡単にしたい
インターフェース(抽象基底クラス)の定義
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"
具象クラスの作成(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
ファクトリ
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
デコレータとして
```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
```
ファクトリでインスタンスを作成
ChatBotClass = ChatBotFactory.get_chat_bot_class(ChatBotServices.OPENAI)
chat_bot = ChatBotClass()
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)
```
使わない場合に比べてコードが複雑(難解)になる
- ドキュメントを作成
- ペアプロ
ドキュメントレビューでメンバーを巻き込む
::right::
# chat-app
## 基本設計方針
利用する外部APIがまだ決まっていないのでファクトリメソッドによる設計。
抽象クラス(インタフェース)を各サービスの外部APIを用いて実装したファイルをファクトリに登録
呼び出し元でどのサービスを利用するかを決める。
## ディレクトリ構成
`tree.txt` を参照
### `services` ディレクトリ
ビジネスロジックなどを担う
- `chat`
- AI チャットサービス(Open AIなど)を利用したチャット生成
- ChatGPT が苦手な最新情報(天気情報など)の取得を別の外部サービスで補う
- `geocoding`
- ジオコーディング処理を担う
- あくまでも関数としてのふるまいまでにとどめる
layout: image image: https://cover.sli.dev
- 😇 過剰だったかもしれない
- 📕 ドキュメントの作成を丁寧に行う必要があった
- 🙋♂️ この作りにしたおかげで他メンバーへの引き継ぎスムーズにできた
- 🤔 コンストラクタで異なる引数を受け取る場合
- 🤫 Python の理解も足りてない
- 😵💫 果たしてこれは本当にファクトリメソッドだったのか
- ファクトリメソッド: オブジェクトの生成をサブクラスに追い出し、クラスの再利用性を高める
- ファクトリメソッドを使うべき状況
- オブジェクトの生成方法が複雑な場合
- 生成するオブジェクトの種類が増減する可能性がある場合
- 生成ロジックをカプセル化したい場合