史华乐 发表于 2025-6-2 00:35:11

为什么构造函数需要尽可能的简单

\n    report = UserReport(user_id=1001)# 调用者只是想创建一个报告对象\nFile \"user_report.py\", line 5, in __init__\n    self.user = database.fetch_user(user_id)# 数据库查询可能失败\nFile \"database.py\", line 78, in fetch_user\n    user_data = self._execute_query(f\"SELECT * FROM users WHERE id = {user_id}\")\nFile \"database.py\", line 31, in _execute_query\n    connection = self._get_connection()\nFile \"database.py\", line 15, in _get_connection\n    return pymysql.connect(host=self.host, user=self.user, password=self.password, db=self.db_name)\nFile \"/usr/local/lib/python3.8/site-packages/pymysql/__init__.py\", line 94, in Connect\n    return Connection(*args, **kwargs)\nFile \"/usr/local/lib/python3.8/site-packages/pymysql/connections.py\", line 327, in __init__\n    self.connect()\nFile \"/usr/local/lib/python3.8/site-packages/pymysql/connections.py\", line 629, in connect\n    raise exc\npymysql.err.OperationalError: (2003, \"Can't connect to MySQL server on 'db.example.com' (timed out)\")"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"Traceback (most recent call last):\nFile \"main.py\", line 42, in \n    report = UserReport(user_id=1001)# 调用者只是想创建一个报告对象\nFile \"user_report.py\", line 5, in __init__\n    self.user = database.fetch_user(user_id)# 数据库查询可能失败\nFile \"database.py\", line 78, in fetch_user\n    user_data = self._execute_query(f\"SELECT * FROM users WHERE id = {user_id}\")\nFile \"database.py\", line 31, in _execute_query\n    connection = self._get_connection()\nFile \"database.py\", line 15, in _get_connection\n    return pymysql.connect(host=self.host, user=self.user, password=self.password, db=self.db_name)\nFile \"/usr/local/lib/python3.8/site-packages/pymysql/__init__.py\", line 94, in Connect\n    return Connection(*args, **kwargs)\nFile \"/usr/local/lib/python3.8/site-packages/pymysql/connections.py\", line 327, in __init__\n    self.connect()\nFile \"/usr/local/lib/python3.8/site-packages/pymysql/connections.py\", line 629, in connect\n    raise exc\npymysql.err.OperationalError: (2003, \"Can't connect to MySQL server on 'db.example.com' (timed out)\")"]]],["p",{"uuid":"m8s7xnhip38ukw1k0oo","ind":{}},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"而将计算逻辑提取到专门函数,访问外部依赖的逻辑通过注入进行,就不会存在该问题:"]]],["code",{"syntax":"python","theme":"default","wrap":true,"id":"m6a0bo","title":"","fold":false,"hideHeader":false,"codeFolding":false,"showLineNumber":true,"font":{"fontFamily":"defaultFont","ligatures":false},"enableMacHeader":false,"uuid":"m8s81eef3y3wmjgkcnm","code":"class UserReport:\n    def __init__(self, user, statistics=None):\n      \"\"\"构造函数只负责初始化,无副作用\"\"\"\n      self.user = user\n      self.statistics = statistics if statistics is not None else {}\n    \n    def calculate_statistics(self, activity_source):\n      \"\"\"将计算逻辑分离到专门的方法,并接受依赖注入\"\"\"\n      activities = activity_source.get_activities(self.user.id)\n      self.statistics = {\n            \"login_count\": len(activities),\n            \"active_days\": len(set(a.date for a in activities))\n      }\n      return self.statistics\n\nclass UserActivity:\n    def __init__(self, user_id, date, action):\n      self.user_id = user_id\n      self.date = date\n      self.action = action\n\nclass DatabaseActivity:\n    def get_activities(self, user_id):\n      # 实际应用中会查询数据库\n      return database.fetch_user_activities(user_id)"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"class UserReport:\n    def __init__(self, user, statistics=None):\n      \"\"\"构造函数只负责初始化,无副作用\"\"\"\n      self.user = user\n      self.statistics = statistics if statistics is not None else {}\n    \n    def calculate_statistics(self, activity_source):\n      \"\"\"将计算逻辑分离到专门的方法,并接受依赖注入\"\"\"\n      activities = activity_source.get_activities(self.user.id)\n      self.statistics = {\n            \"login_count\": len(activities),\n            \"active_days\": len(set(a.date for a in activities))\n      }\n      return self.statistics\n\nclass UserActivity:\n    def __init__(self, user_id, date, action):\n      self.user_id = user_id\n      self.date = date\n      self.action = action\n\nclass DatabaseActivity:\n    def get_activities(self, user_id):\n      # 实际应用中会查询数据库\n      return database.fetch_user_activities(user_id)"]]],["p",{"uuid":"m8s88a1kfn4g86cn49"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},""]]],["h3",{"ind":{},"uuid":"m8rhmg694l3wztwylnb","spacing":{"before":18.666666666666664,"after":8,"line":0.8529411764705882}},["span",{"data-type":"text"},["span",{"bold":true,"sz":14,"szUnit":"pt","data-type":"leaf"},"方便调试和演进"]]],["p",{"uuid":"m8s84rfxl3an6r5k5ee","ind":{"firstLine":32}},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"构造函数仅负责简单的初始化时,代码变得更加易于调试和演进。相比之下,包含复杂逻辑的构造函数会使问题定位和系统扩展变得困难。比如下面例子"]]],["code",{"syntax":"python","theme":"default","wrap":true,"id":"ogt9ug","title":"","fold":false,"hideHeader":false,"codeFolding":false,"showLineNumber":true,"font":{"fontFamily":"defaultFont","ligatures":false},"enableMacHeader":false,"uuid":"m8s85i2jptdlitwg29","code":"class UserReport:\n    def __init__(self, user_id):\n      self.user_id = user_id\n      self.user = database.fetch_user(user_id)\n      self.activities = database.fetch_user_activities(user_id)\n      self.statistics = self._calculate_statistics()\n      self.recommendations = self._generate_recommendations()\n      # 更多复杂逻辑..."},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"class UserReport:\n    def __init__(self, user_id):\n      self.user_id = user_id\n      self.user = database.fetch_user(user_id)\n      self.activities = database.fetch_user_activities(user_id)\n      self.statistics = self._calculate_statistics()\n      self.recommendations = self._generate_recommendations()\n      # 更多复杂逻辑..."]]],["p",{"uuid":"m8s85g8lbmcvyrmke9w","ind":{"firstLine":32}},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"可以看到构造函数包括了太多可能失败的点,调试时也不容易找到具体哪一行除了问题。而下面方式调试容易很多:"]]],["code",{"syntax":"python","theme":"default","wrap":true,"id":"2fasur","title":"","fold":false,"hideHeader":false,"codeFolding":false,"showLineNumber":true,"font":{"fontFamily":"defaultFont","ligatures":false},"enableMacHeader":false,"uuid":"m8s880n93rke98mjkyo","code":"class UserReport:\n    def __init__(self, user, activities=None, statistics=None, recommendations=None):\n      self.user = user\n      self.activities = activities or []\n      self.statistics = statistics or {}\n      self.recommendations = recommendations or []"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"class UserReport:\n    def __init__(self, user, activities=None, statistics=None, recommendations=None):\n      self.user = user\n      self.activities = activities or []\n      self.statistics = statistics or {}\n      self.recommendations = recommendations or []"]]],["p",{"uuid":"m8s87ys9yud6uesw4mf","ind":{"firstLine":32}},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"而演进时,复杂的构造函数有很大风险,例如:"]]],["code",{"syntax":"python","theme":"default","wrap":true,"id":"gctvxf","title":"","fold":false,"hideHeader":false,"codeFolding":false,"showLineNumber":true,"font":{"fontFamily":"defaultFont","ligatures":false},"enableMacHeader":false,"uuid":"m8s89mzk28gcd4dypum","code":"# 需要修改原有构造函数,风险很高\nclass UserReport:\n    def __init__(self, user_id, month=None):# 添加新参数\n      self.user_id = user_id\n      self.user = database.fetch_user(user_id)\n      # 修改现有逻辑\n      if month:\n            self.activities = database.fetch_user_activities_by_month(user_id, month)\n      else:\n            self.activities = database.fetch_user_activities(user_id)\n      # 以下计算可能需要调整\n      self.statistics = self._calculate_statistics()\n      self.recommendations = self._generate_recommendations()"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"# 需要修改原有构造函数,风险很高\nclass UserReport:\n    def __init__(self, user_id, month=None):# 添加新参数\n      self.user_id = user_id\n      self.user = database.fetch_user(user_id)\n      # 修改现有逻辑\n      if month:\n            self.activities = database.fetch_user_activities_by_month(user_id, month)\n      else:\n            self.activities = database.fetch_user_activities(user_id)\n      # 以下计算可能需要调整\n      self.statistics = self._calculate_statistics()\n      self.recommendations = self._generate_recommendations()"]]],["p",{"uuid":"m8s89l9jw16bx6zdi2","ind":{"firstLine":32}},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"我们需要添加按月筛选活动数据,增加一个参数,这种情况也是实际代码维护中经常出现的,想到哪写到哪,导致构造函数变的非常复杂难以理解,同时增加出错可能性,而更好的方式如下:"]]],["code",{"syntax":"python","theme":"default","wrap":true,"id":"kb3ezh","title":"","fold":false,"hideHeader":false,"codeFolding":false,"showLineNumber":true,"font":{"fontFamily":"defaultFont","ligatures":false},"enableMacHeader":false,"uuid":"m8s8bzt8mwzfikn6iyq","code":"class UserReport:\n    def __init__(self, user, activities=None, statistics=None, recommendations=None):\n      self.user = user\n      self.activities = activities or []\n      self.statistics = statistics or {}\n      self.recommendations = recommendations or []\n      \n    def filter_by_month(self, month):\n      \"\"\"添加新功能作为单独的方法\"\"\"\n      filtered_activities = \n      return UserReport(\n            self.user,\n            activities=filtered_activities,\n            # 可根据需要重新计算或保留原有数据\n      )"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"class UserReport:\n    def __init__(self, user, activities=None, statistics=None, recommendations=None):\n      self.user = user\n      self.activities = activities or []\n      self.statistics = statistics or {}\n      self.recommendations = recommendations or []\n      \n    def filter_by_month(self, month):\n      \"\"\"添加新功能作为单独的方法\"\"\"\n      filtered_activities = \n      return UserReport(\n            self.user,\n            activities=filtered_activities,\n            # 可根据需要重新计算或保留原有数据\n      )"]]],["p",{"uuid":"m8s8bs5oe0rxbyt1js9","ind":{"firstLine":32}},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"新功能可以独立添加,不影响现有功能,同时也避免修改这种核心逻辑时测试不全面带来的上线提心吊胆。"]]],["p",{"uuid":"m8s82ajjaclp30vpw5","ind":{"firstLine":32}},["span",{"data-type":"text"},["span",{"data-type":"leaf"},""]]],["h2",{"ind":{},"uuid":"m8rhlox699ej7aa04f","spacing":{"before":21.333333333333332,"after":9,"line":0.8529411764705882}},["span",{"data-type":"text"},["span",{"bold":true,"sz":16,"szUnit":"pt","data-type":"leaf"},"可测试性"]]],["p",{"uuid":"m8s8fu8d79usf2b04y","ind":{"firstLine":32}},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"良好的构造函数设计对代码的可测试性有着决定性的影响。当构造函数简单且只负责基本初始化时,测试变得更加容易、更加可靠,且不依赖于特定环境。这也是为什么我写本篇文章的原因,就是在写单元测试时发现很多类几乎不可测试(部分引用的第三方类库中的类,类本身属于其他组件,我无权修改,-.-)。"]]],["h3",{"uuid":"m8s8fu8ebyp3iwyvybb"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"依赖注入与可测试性"]]],["p",{"uuid":"m8s8fu8eia2b3avzft","ind":{"firstLine":32}},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"如果构造函数有较多逻辑,例如:"]]],["code",{"syntax":"python","theme":"default","wrap":true,"id":"pi9yqg","title":"","fold":false,"hideHeader":false,"codeFolding":false,"showLineNumber":true,"font":{"fontFamily":"defaultFont","ligatures":false},"enableMacHeader":false,"uuid":"m8s8gljzydw22ks4iya","code":"class UserReport:\n    def __init__(self, user_id):\n      self.user_id = user_id\n      self.user = database.fetch_user(user_id)\n      self.activities = database.fetch_user_activities(user_id)\n      self.statistics = self._calculate_statistics()"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"class UserReport:\n    def __init__(self, user_id):\n      self.user_id = user_id\n      self.user = database.fetch_user(user_id)\n      self.activities = database.fetch_user_activities(user_id)\n      self.statistics = self._calculate_statistics()"]]],["p",{"uuid":"m8s8gjnpdie8iee6vqg","ind":{"firstLine":32}},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"那么我们的单元测试会变的成本非常高昂,每一个外部依赖都需要mock,就算只需要测试一个非常简单的Case,也需要模拟所有外部依赖,比如"]]],["code",{"syntax":"python","theme":"default","wrap":true,"id":"nrqgsg","title":"","fold":false,"hideHeader":false,"codeFolding":false,"showLineNumber":true,"font":{"fontFamily":"defaultFont","ligatures":false},"enableMacHeader":false,"uuid":"m8s8i94vdoylipcnyhl","code":"def test_user_report():\n    # 需要大量的模拟设置\n    with patch('module.database.fetch_user') as mock_fetch_user:\n      with patch('module.database.fetch_user_activities') as mock_fetch_activities:\n            # 配置模拟返回值\n            mock_fetch_user.return_value = User(1, \"Test User\", \"test@example.com\")\n            mock_fetch_activities.return_value = [\n                Activity(1, datetime(2023, 1, 1), \"login\"),\n                Activity(1, datetime(2023, 1, 2), \"login\")\n            ]\n            \n            # 创建对象 - 即使只是测试一小部分功能也需要模拟所有依赖\n            report = UserReport(1)\n            \n            # 验证结果\n            assert report.statistics[\"login_count\"] == 2\n            assert report.statistics[\"active_days\"] == 2\n            \n            # 验证调用\n            mock_fetch_user.assert_called_once_with(1)\n            mock_fetch_activities.assert_called_once_with(1)"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"def test_user_report():\n    # 需要大量的模拟设置\n    with patch('module.database.fetch_user') as mock_fetch_user:\n      with patch('module.database.fetch_user_activities') as mock_fetch_activities:\n            # 配置模拟返回值\n            mock_fetch_user.return_value = User(1, \"Test User\", \"test@example.com\")\n            mock_fetch_activities.return_value = [\n                Activity(1, datetime(2023, 1, 1), \"login\"),\n                Activity(1, datetime(2023, 1, 2), \"login\")\n            ]\n            \n            # 创建对象 - 即使只是测试一小部分功能也需要模拟所有依赖\n            report = UserReport(1)\n            \n            # 验证结果\n            assert report.statistics[\"login_count\"] == 2\n            assert report.statistics[\"active_days\"] == 2\n            \n            # 验证调用\n            mock_fetch_user.assert_called_once_with(1)\n            mock_fetch_activities.assert_called_once_with(1)"]]],["p",{"uuid":"m8s8i7mt6rz36vrsxha","ind":{"firstLine":32}},["span",{"data-type":"text"},["span",{"data-type":"leaf"},""]]],["p",{"uuid":"m8s8il1lv8boy6r267l","ind":{"firstLine":32}},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"而构造函数简单,我们的单元测试也会变得非常简单,比如针对下面代码进行测试:"]]],["code",{"syntax":"python","theme":"default","wrap":true,"id":"hcgndm","title":"","fold":false,"hideHeader":false,"codeFolding":false,"showLineNumber":true,"font":{"fontFamily":"defaultFont","ligatures":false},"enableMacHeader":false,"uuid":"m8s8j5zipgvs7rznue","code":"class UserReport:\n    def __init__(self, user, activities=None):\n      self.user = user\n      self.activities = activities or []\n      self.statistics = {}\n    \n    def calculate_statistics(self):\n      \"\"\"计算统计数据\"\"\"\n      login_count = len(self.activities)\n      active_days = len(set(a.date for a in self.activities))\n      self.statistics = {\n            \"login_count\": login_count,\n            \"active_days\": active_days\n      }\n      return self.statistics"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"class UserReport:\n    def __init__(self, user, activities=None):\n      self.user = user\n      self.activities = activities or []\n      self.statistics = {}\n    \n    def calculate_statistics(self):\n      \"\"\"计算统计数据\"\"\"\n      login_count = len(self.activities)\n      active_days = len(set(a.date for a in self.activities))\n      self.statistics = {\n            \"login_count\": login_count,\n            \"active_days\": active_days\n      }\n      return self.statistics"]]],["p",{"uuid":"m8s8is0hg0ngzzudxjt","ind":{"firstLine":32}},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"可以看到单元测试不再需要复杂的Mock"]]],["code",{"syntax":"python","theme":"default","wrap":true,"id":"u7pkhn","title":"","fold":false,"hideHeader":false,"codeFolding":false,"showLineNumber":true,"font":{"fontFamily":"defaultFont","ligatures":false},"enableMacHeader":false,"uuid":"m8s8kakdgpmik61zhfm","code":"def test_report_should_calculate_correct_statistics_when_activities_provided():\n    # 直接创建测试对象,无需模拟外部依赖\n    user = User(1, \"Test User\", \"test@example.com\")\n    activities = [\n      UserActivity(1, datetime(2023, 1, 1), \"login\"),\n      UserActivity(1, datetime(2023, 1, 2), \"login\"),\n      UserActivity(1, datetime(2023, 1, 2), \"logout\")# 同一天的另一个活动\n    ]\n    \n    # 创建对象非常简单\n    report = UserReport(user, activities)\n    \n    # 测试特定方法\n    stats = report.calculate_statistics()\n    \n    # 验证结果\n    assert stats[\"login_count\"] == 3\n    assert stats[\"active_days\"] == 2"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"def test_report_should_calculate_correct_statistics_when_activities_provided():\n    # 直接创建测试对象,无需模拟外部依赖\n    user = User(1, \"Test User\", \"test@example.com\")\n    activities = [\n      UserActivity(1, datetime(2023, 1, 1), \"login\"),\n      UserActivity(1, datetime(2023, 1, 2), \"login\"),\n      UserActivity(1, datetime(2023, 1, 2), \"logout\")# 同一天的另一个活动\n    ]\n    \n    # 创建对象非常简单\n    report = UserReport(user, activities)\n    \n    # 测试特定方法\n    stats = report.calculate_statistics()\n    \n    # 验证结果\n    assert stats[\"login_count\"] == 3\n    assert stats[\"active_days\"] == 2"]]],["p",{"uuid":"m8s8k8qa8odh69udtp5","ind":{"firstLine":32}},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"同时测试时,Mock对象注入也变得非常简单,如下:"]]],["code",{"syntax":"python","theme":"default","wrap":true,"id":"6dszfc","title":"","fold":false,"hideHeader":false,"codeFolding":false,"showLineNumber":true,"font":{"fontFamily":"defaultFont","ligatures":false},"enableMacHeader":false,"uuid":"m8s8lb67dmxk9spatrj","code":"def test_report_should_use_activity_source_when_calculating_statistics():\n    # 准备测试数据\n    user = User(42, \"Test User\", \"test@example.com\")\n    mock_activities = [\n      UserActivity(42, datetime(2023, 1, 1), \"login\"),\n      UserActivity(42, datetime(2023, 1, 2), \"login\")\n    ]\n    \n    # 创建模拟数据源\n    activity_source = MockActivity(mock_activities)\n    \n    # 使用依赖注入\n    report = UserReport(user)\n    report.calculate_statistics(activity_source)\n    \n    # 验证结果\n    assert report.statistics[\"login_count\"] == 2\n    assert report.statistics[\"active_days\"] == 2"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"def test_report_should_use_activity_source_when_calculating_statistics():\n    # 准备测试数据\n    user = User(42, \"Test User\", \"test@example.com\")\n    mock_activities = [\n      UserActivity(42, datetime(2023, 1, 1), \"login\"),\n      UserActivity(42, datetime(2023, 1, 2), \"login\")\n    ]\n    \n    # 创建模拟数据源\n    activity_source = MockActivity(mock_activities)\n    \n    # 使用依赖注入\n    report = UserReport(user)\n    report.calculate_statistics(activity_source)\n    \n    # 验证结果\n    assert report.statistics[\"login_count\"] == 2\n    assert report.statistics[\"active_days\"] == 2"]]],["p",{"uuid":"m8s8l9seo3cygwfgpuk","ind":{"firstLine":32}},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"而做边界值测试时更为简单:"]]],["code",{"syntax":"python","theme":"default","wrap":true,"id":"ta20sh","title":"","fold":false,"hideHeader":false,"codeFolding":false,"showLineNumber":true,"font":{"fontFamily":"defaultFont","ligatures":false},"enableMacHeader":false,"uuid":"m8s8m9350vahc67owmwn","code":"def test_statistics_should_be_empty_when_activities_list_is_empty():\n    user = User(1, \"Test User\", \"test@example.com\")\n    report = UserReport(user, [])# 空活动列表\n    \n    stats = report.calculate_statistics()\n    assert stats[\"login_count\"] == 0\n    assert stats[\"active_days\"] == 0\n\ndef test_constructor_should_throw_exception_when_user_is_null():\n    # 测试无效用户情况\n    with pytest.raises(ValueError):\n      report = UserReport(None)# 假设我们在构造函数中验证用户不为空"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"def test_statistics_should_be_empty_when_activities_list_is_empty():\n    user = User(1, \"Test User\", \"test@example.com\")\n    report = UserReport(user, [])# 空活动列表\n    \n    stats = report.calculate_statistics()\n    assert stats[\"login_count\"] == 0\n    assert stats[\"active_days\"] == 0\n\ndef test_constructor_should_throw_exception_when_user_is_null():\n    # 测试无效用户情况\n    with pytest.raises(ValueError):\n      report = UserReport(None)# 假设我们在构造函数中验证用户不为空"]]],["p",{"uuid":"m8s8m73kkwfor2bklu","ind":{"firstLine":32}},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"因此整个代码逻辑通过单元测试将变得更为健壮,而不是需要大量复杂的Mock,复杂的Mock会导致单元测试非常脆弱(也就是修改一点逻辑,导致现有的单元测试无效)"]]],["p",{"ind":{},"uuid":"m8rhndygu807zck8f0q"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},""]]],["h2",{"ind":{},"uuid":"m8rhsrukfnanatcttte","spacing":{"before":21.333333333333332,"after":9,"line":0.8529411764705882}},["span",{"data-type":"text"},["span",{"bold":true,"sz":16,"szUnit":"pt","data-type":"leaf"},"架构相关影响"]]],["h3",{"uuid":"m8se8bneaxma46kgxlb","spacing":{"before":18.666666666666664,"after":8,"line":0.8529411764705882}},["span",{"data-type":"text"},["span",{"bold":true,"sz":14,"szUnit":"pt","data-type":"leaf"},"更容易依赖注入"]]],["p",{"uuid":"m8se8e710o7coonllyog","ind":{"firstLine":32}},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"依赖注入的核心理念是高层模块不应该依赖于低层模块的实现细节,而应该依赖于抽象。好比我们需要打车去公司上班,我们只要打开滴滴输入目的地,我们更高层次的需求是从A到B,而具体的实现细节是打车过程是哪款车,或者司机是谁,这也不是我们关心的。具体由哪辆车,哪位司机提供服务可以随时切换。"]]],["p",{"uuid":"m8se8e720cdxbbaq1bgd","ind":{"firstLine":32}},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"依赖注入是现代软件架构的核心实践之一,而简单的构造函数设计是实现有效依赖注入的基础。通过构造函数注入依赖,我们可以构建松耦合、高内聚的系统,显著提高代码的可维护性和可扩展性。"]]],["code",{"syntax":"python","theme":"default","wrap":true,"id":"my30nd","title":"","fold":false,"hideHeader":false,"codeFolding":false,"showLineNumber":true,"font":{"fontFamily":"defaultFont","ligatures":false},"enableMacHeader":false,"uuid":"m8se9lrdrdzwcegjoyh","code":"# 直接在类内部创建依赖\nclass UserReport:\n    def __init__(self, user_id):\n      self.user_id = user_id\n      # 直接依赖具体实现\n      self.database = MySQLDatabase()\n      self.user = self.database.fetch_user(user_id)"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"# 直接在类内部创建依赖\nclass UserReport:\n    def __init__(self, user_id):\n      self.user_id = user_id\n      # 直接依赖具体实现\n      self.database = MySQLDatabase()\n      self.user = self.database.fetch_user(user_id)"]]],["code",{"syntax":"python","theme":"default","wrap":true,"id":"x19fsm","title":"","fold":false,"hideHeader":false,"codeFolding":false,"showLineNumber":true,"font":{"fontFamily":"defaultFont","ligatures":false},"enableMacHeader":false,"uuid":"m8sea2fe8k9gl1uzoc2","code":"# 通过构造函数注入依赖\nclass UserReport:\n    def __init__(self, user, activity_source):\n      self.user = user\n      self.activity_source = activity_source\n      self.statistics = {}\n    \n    def calculate_statistics(self):\n      activities = self.activity_source.get_activities(self.user.id)\n      # 计算逻辑..."},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"# 通过构造函数注入依赖\nclass UserReport:\n    def __init__(self, user, activity_source):\n      self.user = user\n      self.activity_source = activity_source\n      self.statistics = {}\n    \n    def calculate_statistics(self):\n      activities = self.activity_source.get_activities(self.user.id)\n      # 计算逻辑..."]]],["p",{"uuid":"m8se8e73z4fhnilb058","ind":{"firstLine":32}},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"通过第二段代码可以看到更容易实现依赖注入,通常实际使用中还结合依赖注入容器(IoC)自动化依赖的创建和注入,但这超出本篇的篇幅了。"]]],["p",{"uuid":"m8seb8ntvs43b33ww","ind":{"firstLine":32}},["span",{"data-type":"text"},["span",{"data-type":"leaf"},""]]],["h3",{"ind":{},"uuid":"m8rht5bhe6hhtmeldv","spacing":{"before":18.666666666666664,"after":8,"line":0.8529411764705882}},["span",{"data-type":"text"},["span",{"bold":true,"sz":14,"szUnit":"pt","data-type":"leaf"},""]]],["h3",{"ind":{},"uuid":"m8rhtxbl1gkz0a6ur0l","spacing":{"before":18.666666666666664,"after":8,"line":0.8529411764705882}},["span",{"data-type":"text"},["span",{"bold":true,"sz":14,"szUnit":"pt","data-type":"leaf"},"更容易暴露设计问题"]]],["p",{"ind":{"firstLine":32},"uuid":"m8rhk1mlaajoghkmt7h"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"构造函数仅做赋值操作,还能更容易得暴露类的设计问题。当构造函数变得臃肿或复杂时,这通常表明存在更深层次的设计缺陷。"]]],["p",{"ind":{"firstLine":32},"uuid":"m8sen9asmocfgwutd2s"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"比如一个类的构造函数有大量参数时,通常意味着类承担过多的职责,比如:"]]],["code",{"syntax":"python","theme":"default","wrap":true,"id":"9cmngf","title":"","fold":false,"hideHeader":false,"codeFolding":false,"showLineNumber":true,"font":{"fontFamily":"defaultFont","ligatures":false},"enableMacHeader":false,"uuid":"m8senypypuxyhyr67fq","code":"# 需要引起警觉:参数过多的构造函数\nclass UserReport:\n    def __init__(self, user, activity_list, login_calculator, active_days_calculator, \n                visualization_tool, report_exporter, notification_system):\n      self.user = user\n      self.activity_list = activity_list\n      self.login_calculator = login_calculator\n      self.active_days_calculator = active_days_calculator\n      self.visualization_tool = visualization_tool\n      self.report_exporter = report_exporter\n      self.notification_system = notification_system\n      self.statistics = {}"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"# 需要引起警觉:参数过多的构造函数\nclass UserReport:\n    def __init__(self, user, activity_list, login_calculator, active_days_calculator, \n                visualization_tool, report_exporter, notification_system):\n      self.user = user\n      self.activity_list = activity_list\n      self.login_calculator = login_calculator\n      self.active_days_calculator = active_days_calculator\n      self.visualization_tool = visualization_tool\n      self.report_exporter = report_exporter\n      self.notification_system = notification_system\n      self.statistics = {}"]]],["p",{"ind":{},"uuid":"m8senswt8hh9b4lxxu"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},""]]],["p",{"ind":{"firstLine":32},"uuid":"m8set5qglbw4oz6q9xa"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"一个常见的解决思路是使用Builder模式,让初始化过程更加优雅,但这通常只能掩盖问题,而不是解决问题"]]],["p",{"ind":{"firstLine":32},"uuid":"m8seusuvtw4l10khwto"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"因此可以将过多参数的构造函数当做red flag,正确的解决办法是重新查看类的设计,进行职责分离:"]]],["code",{"syntax":"python","theme":"default","wrap":true,"id":"v4vxnu","title":"","fold":false,"hideHeader":false,"codeFolding":false,"showLineNumber":true,"font":{"fontFamily":"defaultFont","ligatures":false},"enableMacHeader":false,"uuid":"m8sevc60ybj4cvamhbj","code":"# 核心报告类,只关注数据和基本统计\nclass UserReport:\n    def __init__(self, user, activities):\n      self.user = user\n      self.activities = activities\n      self.statistics = {}\n    \n    def calculate(self, calculator):\n      self.statistics = calculator.compute(self.activities)\n      return self\n\n# 分离的统计计算\nclass ActivityStatistics:\n    def compute(self, activities):\n      login_count = len()\n      unique_days = len(set(a.date for a in activities))\n      return {\"logins\": login_count, \"active_days\": unique_days}\n\n# 分离的报告导出功能\nclass ReportExport:\n    def to_pdf(self, report):\n      # PDF导出逻辑\n      pass\n    \n    def to_excel(self, report):\n      # Excel导出逻辑\n      pass\n\n# 分离的通知功能\nclass ReportNotification:\n    def send(self, report, recipients):\n      # 发送通知逻辑\n      pass"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"# 核心报告类,只关注数据和基本统计\nclass UserReport:\n    def __init__(self, user, activities):\n      self.user = user\n      self.activities = activities\n      self.statistics = {}\n    \n    def calculate(self, calculator):\n      self.statistics = calculator.compute(self.activities)\n      return self\n\n# 分离的统计计算\nclass ActivityStatistics:\n    def compute(self, activities):\n      login_count = len()\n      unique_days = len(set(a.date for a in activities))\n      return {\"logins\": login_count, \"active_days\": unique_days}\n\n# 分离的报告导出功能\nclass ReportExport:\n    def to_pdf(self, report):\n      # PDF导出逻辑\n      pass\n    \n    def to_excel(self, report):\n      # Excel导出逻辑\n      pass\n\n# 分离的通知功能\nclass ReportNotification:\n    def send(self, report, recipients):\n      # 发送通知逻辑\n      pass"]]],["p",{"ind":{"firstLine":32},"uuid":"m8seva5fr8jv295upyq"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"那么类的调用就会变得非常清晰:"]]],["code",{"syntax":"python","theme":"default","wrap":true,"id":"8i8dcg","title":"","fold":false,"hideHeader":false,"codeFolding":false,"showLineNumber":true,"font":{"fontFamily":"defaultFont","ligatures":false},"enableMacHeader":false,"uuid":"m8sews23ix3lidqathe","code":"# 清晰的职责分离\nuser = User(42, \"John Doe\", \"john@example.com\")\nactivities = activity_database.get_user_activities(user.id)\n\n# 创建和计算报告\ncalculator = ActivityStatistics()\nreport = UserReport(user, activities).calculate(calculator)\n\n# 导出报告(如果需要)\nif export_needed:\n    exporter = ReportExport()\n    pdf_file = exporter.to_pdf(report)\n\n# 发送通知(如果需要)\nif notify_admin:\n    notifier = ReportNotification()\n    notifier.send(report, [\"admin@example.com\"])"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"# 清晰的职责分离\nuser = User(42, \"John Doe\", \"john@example.com\")\nactivities = activity_database.get_user_activities(user.id)\n\n# 创建和计算报告\ncalculator = ActivityStatistics()\nreport = UserReport(user, activities).calculate(calculator)\n\n# 导出报告(如果需要)\nif export_needed:\n    exporter = ReportExport()\n    pdf_file = exporter.to_pdf(report)\n\n# 发送通知(如果需要)\nif notify_admin:\n    notifier = ReportNotification()\n    notifier.send(report, [\"admin@example.com\"])"]]],["p",{"ind":{"firstLine":32},"uuid":"m8sewqd8byu8kcnojhf"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"这种方式每个类都有明确的单一职责,构造函数简单明了,同时功能可以按需组合使用以及测试变得简单(可以单独测试每个组件)。"]]],["p",{"ind":{},"uuid":"m8rhnem5yf0mk2k20qs"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},""]]],["h1",{"ind":{},"uuid":"m8rho3k5hzpqod84ee8","spacing":{"before":26.666666666666664,"after":9,"line":0.8529411764705882}},["span",{"data-type":"text"},["span",{"bold":true,"sz":20,"szUnit":"pt","data-type":"leaf"},"特例"]]],["p",{"ind":{"firstLine":32},"uuid":"m8rho598l9w8l33quvq"},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"某些情况下,构造函数除了赋值,还可以做一些其他工作也是合理的,如下:"]]],["h2",{"ind":{},"uuid":"m8rhqp12ricxlz8b9n","spacing":{"before":21.333333333333332,"after":9,"line":0.8529411764705882}},["span",{"data-type":"text"},["span",{"bold":true,"sz":16,"szUnit":"pt","data-type":"leaf"},"参数合法性检查"]]],["p",{"uuid":"m8sfbhtdcvvzz0ldegg","ind":{"firstLine":32}},["span",{"data-type":"text"},["span",{"data-type":"leaf"},"在构造函数中进行基本的参数验证是合理的,这确保对象从创建之初就处于有效状态,例如下面例子,只要构造函数不进行外部依赖操作或复杂的逻辑运算都是合理的"]]],["code",{"syntax":"python","theme":"default","wrap":true,"id":"www3gq","title":"","fold":false,"hideHeader":false,"codeFolding":false,"showLineNumber":true,"font":{"fontFamily":"defaultFont","ligatures":false},"enableMacHeader":false,"uuid":"m8sfe2e69jgfjl6lgwv","code":"class User:\n    def __init__(self, id, name, email):\n      # 基本参数验证\n      if id
页: [1]
查看完整版本: 为什么构造函数需要尽可能的简单