{ "cells": [ { "cell_type": "raw", "id": "eccfe49f-de35-4241-90e3-a7095940b61a", "metadata": {}, "source": [ "设计模式提供高频重复出现的需求的最佳解决方案。以下介绍适合词频统计案例的设计模式:策略模式、观察者模式、工厂模式。" ] }, { "cell_type": "markdown", "id": "c186171f-d1f2-433e-a3eb-b266e2909a2c", "metadata": {}, "source": [ "## 策略模式(动态选择分词策略)\n", "\n", "策略模式允许动态切换算法(如分词器),比元编程简单。" ] }, { "cell_type": "code", "execution_count": null, "id": "97c865cb-0b5a-4fa1-aa74-5ba2e65e7436", "metadata": {}, "outputs": [], "source": [ "from abc import ABC, abstractmethod\n", "\n", "class Tokenizer(ABC):\n", " \"\"\"分词器接口\"\"\"\n", " @abstractmethod\n", " def tokenize(self, text: str, stop_words: set) -> List[str]:\n", " pass\n", "\n", "class JiebaTokenizer(Tokenizer):\n", " \"\"\"jieba 分词器\"\"\"\n", " def tokenize(self, text: str, stop_words: set) -> List[str]:\n", " return [w for w in jieba.lcut(text) if w not in stop_words]\n", "\n", "class SimpleTokenizer(Tokenizer):\n", " \"\"\"简单分词器\"\"\"\n", " def tokenize(self, text: str, stop_words: set) -> List[str]:\n", " return [w for w in text.split() if w not in stop_words]\n", "\n", "class TextAnalyzer:\n", " def __init__(self, config_path='config.yaml'):\n", " with open(config_path, 'r', encoding='utf-8') as f:\n", " config = yaml.safe_load(f)\n", " self.data_dir = config['data_dir']\n", " self.top_n = config['top_n']\n", " self.stop_words_file = config['stop_words_file']\n", " self.output_file = config['output_file']\n", " self.stop_words = self.load_stop_words()\n", " self.word_count = Counter()\n", " # 动态选择分词器\n", " tokenizer_name = config.get('tokenizer', 'jieba')\n", " self.tokenizer = {'jieba': JiebaTokenizer(), 'simple': SimpleTokenizer()}[tokenizer_name]\n", "\n", " def tokenize(self, text: str) -> List[str]:\n", " \"\"\"使用策略分词\"\"\"\n", " return self.tokenizer.tokenize(text, self.stop_words)\n", "\n", " # 其余方法同上" ] }, { "cell_type": "markdown", "id": "5435ebc3-d3b0-4475-8bd5-cb45fb51638c", "metadata": {}, "source": [ "工程质量提升:\n", "- 可扩展性:添加新分词器只需实现 Tokenizer 接口。\n", "- 可维护性:分词逻辑与主类分离,修改更独立。\n", "\n", "适用场景:适合需要动态切换算法的场景。" ] }, { "cell_type": "markdown", "id": "fbf53455-558c-40fb-8718-446dec989b5d", "metadata": {}, "source": [ "## 观察者模式(结果输出解耦)\n", "\n", "观察者模式可用于解耦结果输出逻辑(如打印、保存文件、发送通知)。" ] }, { "cell_type": "code", "execution_count": null, "id": "d7a2bd4c-df73-4800-b45b-9b6c73d28d7b", "metadata": {}, "outputs": [], "source": [ "class OutputObserver(ABC):\n", " \"\"\"输出观察者接口\"\"\"\n", " @abstractmethod\n", " def update(self, top_words: List[Tuple[str, int]]):\n", " pass\n", "\n", "class ConsoleOutput(OutputObserver):\n", " \"\"\"控制台输出\"\"\"\n", " def update(self, top_words: List[Tuple[str, int]]):\n", " for word, count in top_words:\n", " print(f\"{word}: {count}\")\n", "\n", "class FileOutput(OutputObserver):\n", " \"\"\"文件输出\"\"\"\n", " def __init__(self, output_file: str):\n", " self.output_file = output_file\n", "\n", " def update(self, top_words: List[Tuple[str, int]]):\n", " with open(self.output_file, 'w', encoding='utf-8') as f:\n", " for word, count in top_words:\n", " f.write(f\"{word}: {count}\\n\")\n", "\n", "class TextAnalyzer:\n", " def __init__(self, config_path='config.yaml'):\n", " with open(config_path, 'r', encoding='utf-8') as f:\n", " config = yaml.safe_load(f)\n", " self.data_dir = config['data_dir']\n", " self.top_n = config['top_n']\n", " self.stop_words_file = config['stop_words_file']\n", " self.output_file = config['output_file']\n", " self.stop_words = self.load_stop_words()\n", " self.word_count = Counter()\n", " self.observers = [ConsoleOutput(), FileOutput(self.output_file)]\n", "\n", " def add_observer(self, observer: OutputObserver):\n", " \"\"\"添加观察者\"\"\"\n", " self.observers.append(observer)\n", "\n", " def notify_observers(self, top_words: List[Tuple[str, int]]):\n", " \"\"\"通知所有观察者\"\"\"\n", " for observer in self.observers:\n", " observer.update(top_words)\n", "\n", " def run(self):\n", " \"\"\"执行词频统计并通知观察者\"\"\"\n", " self.process_directory()\n", " top_words = self.get_top_words()\n", " self.notify_observers(top_words)\n", "\n", " # 其余方法同上" ] }, { "cell_type": "markdown", "id": "02b5cfba-431c-4a01-a454-099e4f41922c", "metadata": {}, "source": [ "### 分析\n", "\n", "工程质量提升:\n", " - 可扩展性:添加新输出方式只需实现 OutputObserver 接口。\n", " - 解耦性:输出逻辑与统计逻辑分离,修改输出不影响核心功能。\n", "\n", "适用场景:适合需要多种输出或通知的场景。\n", "\n", "局限性:观察者模式增加代码复杂性,适合复杂输出需求。" ] }, { "cell_type": "markdown", "id": "11669305-8cd5-4317-afd5-e85c3f0a5a81", "metadata": {}, "source": [ "## 工厂模式(动态创建分词器)\n", "\n", "工厂模式可用于动态创建分词器,简化策略模式中的初始化逻辑。" ] }, { "cell_type": "code", "execution_count": null, "id": "2fa50633-de22-40c8-912d-3ded5ebcedfc", "metadata": {}, "outputs": [], "source": [ "class TokenizerFactory:\n", " \"\"\"分词器工厂\"\"\"\n", " @staticmethod\n", " def create_tokenizer(name: str) -> Tokenizer:\n", " tokenizers = {\n", " 'jieba': JiebaTokenizer(),\n", " 'simple': SimpleTokenizer()\n", " }\n", " return tokenizers.get(name, JiebaTokenizer())\n", "\n", "class TextAnalyzer:\n", " def __init__(self, config_path='config.yaml'):\n", " with open(config_path, 'r', encoding='utf-8') as f:\n", " config = yaml.safe_load(f)\n", " self.data_dir = config['data_dir']\n", " self.top_n = config['top_n']\n", " self.stop_words_file = config['stop_words_file']\n", " self.output_file = config['output_file']\n", " self.stop_words = self.load_stop_words()\n", " self.word_count = Counter()\n", " self.tokenizer = TokenizerFactory.create_tokenizer(config.get('tokenizer', 'jieba'))\n", "\n", " # 其余方法同上" ] }, { "cell_type": "markdown", "id": "a4db7046-dfe2-4bd8-81d1-49a42e2eeb5c", "metadata": {}, "source": [ "### 分析\n", "\n", "工程质量提升:\n", " - 可维护性:分词器创建逻辑集中于工厂,易于修改。\n", " - 可扩展性:添加新分词器只需更新工厂方法。\n", "\n", "适用场景:适合需要动态创建对象的场景。\n", "\n", "局限性:对于简单场景,工厂模式可能略显冗余。" ] }, { "cell_type": "markdown", "id": "07158f09-703e-4abb-ac1a-881ba1b3b26d", "metadata": {}, "source": [ "## 附:元编程\n", "\n", "元编程允许动态修改类或函数行为,可用于动态配置分词器或输出格式。案例中,可通过元编程动态注册分词器。" ] }, { "cell_type": "code", "execution_count": null, "id": "4394008c-88da-44bd-aa0d-f1b7a6dbc7d6", "metadata": {}, "outputs": [], "source": [ "class TokenizerRegistry(type):\n", " \"\"\"元类:动态注册分词器\"\"\"\n", " tokenizers = {}\n", "\n", " def register_tokenizer(cls, name):\n", " def decorator(func):\n", " cls.tokenizers[name] = func\n", " return func\n", " return decorator\n", "\n", "class TextAnalyzer(metaclass=TokenizerRegistry):\n", " def __init__(self, config_path='config.yaml'):\n", " with open(config_path, 'r', encoding='utf-8') as f:\n", " config = yaml.safe_load(f)\n", " self.data_dir = config['data_dir']\n", " self.top_n = config['top_n']\n", " self.stop_words_file = config['stop_words_file']\n", " self.output_file = config['output_file']\n", " self.stop_words = self.load_stop_words()\n", " self.word_count = Counter()\n", " self.tokenizer_name = config.get('tokenizer', 'jieba')\n", "\n", " @classmethod\n", " def register_tokenizer(cls, name):\n", " return cls.__class__.register_tokenizer(name)\n", "\n", " def tokenize(self, text: str) -> List[str]:\n", " \"\"\"动态调用分词器\"\"\"\n", " tokenizer = self.__class__.tokenizers.get(self.tokenizer_name)\n", " return tokenizer(self, text)\n", "\n", " @register_tokenizer('jieba')\n", " def jieba_tokenizer(self, text: str) -> List[str]:\n", " \"\"\"jieba 分词\"\"\"\n", " return [w for w in jieba.lcut(text) if w not in self.stop_words]\n", "\n", " @register_tokenizer('simple')\n", " def simple_tokenizer(self, text: str) -> List[str]:\n", " \"\"\"简单分词(按空格)\"\"\"\n", " return [w for w in text.split() if w not in self.stop_words]\n", "\n", " # 其余方法(load_stop_words, process_file, etc.)同上" ] }, { "cell_type": "markdown", "id": "30ba75ea-f769-4f90-9075-27670db9ada4", "metadata": {}, "source": [ "### 分析\n", "\n", "工程质量提升:\n", "- 可扩展性:新分词器只需添加新方法并注册,无需修改核心部分。\n", "- 灵活性:通过配置文件动态选择分词器。\n", "\n", "适用场景:适合需要动态配置或插件化系统的场景。\n", "\n", "局限性:元编程增加代码复杂性,需要团队整体技术能力支持 。" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.7" } }, "nbformat": 4, "nbformat_minor": 5 }