news 2026/10/1 12:46:59

用真实爬虫项目学会Python面向对象编程:从类设计到继承封装实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用真实爬虫项目学会Python面向对象编程:从类设计到继承封装实战

说个很真实的场景:你自学Python三个月,函数、列表、字典都玩得挺溜,看教程做习题没问题,可一旦打开别人写的项目源码,满屏的class、self、definit,瞬间就懵了。这几乎是每个Python入门者都会撞上的墙。我当年也是这样——函数我懂,字典我懂,可这些类是怎么组合到一起的?为什么要绕这么多层?等到我自己在公司代码库里摸爬滚打几年之后才明白,面向对象编程不是一种花哨语法,而是一种组织代码的方式。如果你已经开始接触Python的类与对象,但总觉得“会写class但不会设计class”,这期内容就是为你准备的。

这期是面向对象编程实践篇,重点不是再讲一遍“什么是类”,而是讲清楚三个层面:怎么把业务需求拆成类,怎么用继承和多态让代码变得好维护,以及实战中会踩到哪些文档里不写的坑。为了让内容落地,我会用一个真实的爬虫小项目做演进演示,从一团函数改造成清晰的OOP架构。整个过程有完整代码、有设计思路、有避坑记录,学完你可以直接拿这套思路去重构自己的脚本。

1. 从函数堆砌到类的组织:OOP到底解决了什么问题

1.1 我经历过的“散装代码”阶段

先讲讲我自己走过的弯路。刚写Python那阵子,我的代码风格非常朴素——拿到需求就写函数,一个脚本从第一行一路写到最后一个return。一个数据抓取脚本大概是这样的:开头定义几个全局变量,中间是request_get、parse_html、save_to_file三个函数,最后一段for循环依次调用。脚本跑起来没问题,可需求一变就完蛋。

比如某天产品说:“我们的数据源从A网站换到B网站,字段也变了。”好,我需要改动parse_html函数,然后发现A网站特有的字段判断逻辑跟B网站的混在一起,改一个函数牵扯出一堆if分支。再比如我想给这个脚本加一个“断点续抓”功能,结果发现全局变量满天飞,状态全靠几个模块级的列表维护,一加功能就担心破坏别的逻辑。

那段时间我每天最怕的事情就是改自己的老代码。后来我才意识到,问题不是代码写得不够熟练,而是组织方式不适合复杂需求。函数式写法适合线性流程,可一旦涉及多种实体、多种状态、多种行为,函数之间互相引用、共享全局数据,复杂度就会指数上升。面向对象编程解决的本质问题,就是把你关注的“事物”打包成一个独立的整体,数据和操作这个数据的方法待在一起,互不污染,外部只跟这个整体打交道。

1.2 类与对象的核心直觉:模板与实例

很多人被“类”这个概念吓住,其实用生活类比特别简单。类是模具,对象是模具倒出来的成品。同一个模具可以倒出无数个成品,每个成品都有自己的小差异——这就是一个类可以创建多个实例,每个实例有自己独立的属性值。

拿用户系统举例。User这个类定义了用户这个事物有哪些数据(用户名、邮箱、权限等级),以及能做什么事(登录、登出、改密码)。每来一个真实用户,就new一个User实例,填上各自的用户名和邮箱。这么做的好处是:用户相关的所有代码都聚集在同一个地方,不会出现“改用户密码的函数在util.py,查用户状态的逻辑又散落在views.py”这种糟糕局面。

这种把数据和操作绑在一起的组织方式,就是“封装”。封装之后,外部代码不需要知道User内部怎么存储密码、怎么校验权限,只需要调用user.change_password(new_pwd)就行。这就是为什么你会在工程里看到那么多类的根本原因——它不是炫技,是为了让代码的每个部分职责清楚、边界明确。

2. 亲手写一个类:从设计到实现的完整套路

2.1 类的三件套:初始化、属性、方法

实践面向对象编程,第一步不是狂写class,而是先学会把一个类写完整。一个正常的Python类通常包含三个部分:初始化方法init、属性、方法。我见过太多入门者把三者混为一谈,导致类写出来像一锅粥。

先看一段典型代码:

class Order: def __init__(self, order_id, items, customer): self.order_id = order_id self.items = items self.customer = customer self.status = "pending" def total_price(self): return sum(item["price"] * item["qty"] for item in self.items) def mark_shipped(self): self.status = "shipped"

这个类的设计逻辑很清晰:__init__负责“造出一个订单”并设置初始状态;属性order_id、items、customer、status是这个订单的数据;方法total_price和mark_shipped是这个订单能做的事。外部使用的时候,先Order(...)创建实例,再调用实例方法完成操作,数据变化只发生在实例自己身上,不会污染其他订单。

我见过很多人写类的时候,习惯把所有计算逻辑都塞进methods,然后属性随便定义,这在我看来顺序反了。正确的做法是反过来:先想清楚这个类需要维护哪些状态(属性),再根据状态的变化来设计方法。属性是这个类的“底盘”,方法是在底盘上操作的手段。底盘不稳,方法再多也是空中楼阁。

2.2 self到底是个什么鬼

self大概是Python初学者最常问的问题之一。别的语言写this,Python写self,而且每个方法定义都要带上它,传参数的时候又不用传,非常迷惑。

一句话讲明白:self就是这个实例本身。当你执行order = Order("A001", items, "张三")的时候,Python默默把order这个实例作为第一个参数传给了__init__,所以__init__里self.order_id = "A001"的意思就是“给order这个实例设置一个order_id属性”。同理,调用order.total_price()时,Python自动把order传进去作为self,方法里就能用self.items拿到这个实例自己的数据。

理解self之后,很多怪现象就解释通了。比如你在方法里写name = "local",那只是局部变量,跟实例一点关系没有;只有写了self.name = "local"才会成为实例属性。这也是我自己早年间经常踩的坑:方法里算着算着,忘记加self,结果外部永远取不到那个值。

2.3 实例变量和类变量,用错会出大问题

属性也有分类,实例变量和类变量不是一回事。实例变量是每个对象自己的,类变量是整个类共享的。这个区别在数字、字符串之类的不可变类型上表现不明显,但一旦遇到可变类型如列表、字典,就非常危险。

class Employee: department = "研发部" # 类变量,所有员工共享 def __init__(self, name): self.name = name # 实例变量,每个员工独立 e1 = Employee("张三") e2 = Employee("李四") e2.department = "市场部" # 注意:这会给e2新建一个实例属性,不会改类变量 print(e1.department) # 研发部 print(Employee.department) # 研发部

这条行为还算安全。真正危险的是不赋值、直接修改内部内容:

class Team: members = [] # 危险:可变类变量 def __init__(self, name): self.name = name t1 = Team("前端组") t1.members.append("王五") t2 = Team("后端组") print(t2.members) # ['王五'],整个类共享了

我在实际项目里见过这种bug导致的生产事故,排查了很久。解决办法很简单:可变对象一律在__init__里初始化为实例变量,比如self.members = []。类变量只用来放常量、共享配置之类真正需要共享的东西。

3. 继承与多态:让代码真正可复用

3.1 继承的第一条准则:先抽象,再复用

继承是OOP里看着最爽、用起来最容易出问题的特性。很多初学者恨不得把所有类都放进一条继承链里,结果是父类臃肿、子类互相干扰,改一处崩一片。我自己实践下来的经验是:继承前先做抽象,别为了复用而继承。

什么叫做“先抽象”?就是先从多个相似对象里提炼出公共的东西,让父类只保留真正通用的行为和属性,子类只做各自特化的部分。举个例子,我在写数据采集器的时候,需要同时抓取几个不同来源的数据。它们的第一步都是构建请求、发出请求、检查响应,这部分高度一致,适合放进基类;而解析响应的逻辑每个来源都不一样,就应该作为抽象方法留在子类里实现。

class BaseFetcher: def __init__(self, url, timeout=10): self.url = url self.timeout = timeout self.session = self._build_session() def _build_session(self): import requests s = requests.Session() s.headers.update({"User-Agent": "Mozilla/5.0"}) return s def fetch(self): resp = self.session.get(self.url, timeout=self.timeout) resp.raise_for_status() return self.parse(resp.text) def parse(self, html): raise NotImplementedError("子类必须实现parse方法")

仔细看这个基类,fetch流程是固定的,子类只需要实现parse。这样做的价值是:将来新增第三个数据源,我只要再写一个子类,继承BaseFetcher,实现parse就行,请求逻辑、异常处理、会话管理这些完全不用重写。

3.2 super()与协作式多重继承

子类里写__init__的时候,千万别忘了把父类的初始化逻辑接上。Python提供了super()来做这件事。这个函数我多解释一句,因为理解它的人真的不多。

class JsonFetcher(BaseFetcher): def __init__(self, url, timeout=10, encoding="utf-8"): super().__init__(url, timeout) self.encoding = encoding def parse(self, text): import json return json.loads(text)

子类的__init__先通过super().__init__把自己的url和timeout传给父类,让父类把公共部分初始化好,然后再设置子类自己的encoding。很多初学者会忘记这一步,导致父类的__init__没执行,session为None,后面调用就报错。这不是语法问题,是设计习惯问题——子类应当把自己当成父类的一种特化,而不是完全独立的个体。

至于多重继承,我的建议很简单:能少用就少用。Python的MRO(方法解析顺序)虽然规则清楚,但多重继承一旦超过两层,可读性急剧下降。现实业务里的协作式多重继承,我在公司代码里见到的成功案例屈指可数。相比而言,把公共部分拆出来作为模块、用组合的方式去调用,往往更方便排查问题。

3.3 多态与鸭子类型:不写接口也能统一调用

多态这个词听着学术,其实就是一句话:不同类的对象,如果拥有相同的方法名,调用方可以用同样的方式使用它们,而不需要关心具体类型。爬虫例子里,JsonFetcher.parse和HtmlFetcher.parse的实现完全不同,但只要它们都叫parse,fetch流程里一行parse(text)就能通吃。

Python里更极致的多态是鸭子类型:只要会叫,管它是不是鸭子。我不需要让所有抓取器都继承同一个基类,只要它们都实现了fetch和parse方法,在循环里就可以统一调用:

fetchers = [ JsonFetcher("https://api.example.com/data"), HtmlFetcher("https://example.com/page"), ] for fetcher in fetchers: data = fetcher.fetch() save_data(data)

这种写法的好处是极其灵活,新加一个采集源不用改循环逻辑。缺点也很明显:缺少类型约束,传进来一个没有parse方法的对象就会在运行时才报错。工程上通常的做法是:用abc模块里的ABCMeta和abstractmethod给基类加一道保险,子类忘记实现parse,一实例化就报错,而不是等到运行fetch才痛苦排查。

4. 封装的艺术:把内部细节藏起来

4.1 私有属性:下划线不是摆设

Python没有真正的private关键词,但下划线约定真的是很重要的工程默契。一个下划线开头的属性或方法,比如_parse_result,表示“内部使用的,外部别碰”。这不是强制规则,但团队协作中遵守这个约定,代码边界就会清晰很多。

两个下划线开头的,比如__secret,会触发名字重整(name mangling),Python在类定义时自动把属性名改成_ClassName__secret。很多教程拿这个讲“私有”,但说实话,两个下划线更适合用于避免子类意外的属性覆盖,而不是为了实现严格的访问控制。我自己写类时更常用单下划线来表示“内部细节”,只有在确实要防止子类误重名时才用双下划线。

class Account: def __init__(self, owner, balance): self.owner = owner self._balance = balance def deposit(self, amount): if amount <= 0: raise ValueError("存款金额必须大于0") self._balance += amount def withdraw(self, amount): if amount > self._balance: raise ValueError("余额不足") self._balance -= amount

外部如果想改balance,只能通过deposit和withdraw,而这俩方法内部校验了规则。这就是封装的意义:不是禁止你访问,而是给你一个合规的操作入口,把非法状态变化挡在外面。

4.2 property装饰器:像访问属性一样使用方法

有时候你希望外部用属性访问的方式去获取一个经过计算的值,而不是调用方法。property装饰器就是干这个的。这个特性看似不起眼,却能把接口做得很优雅。

class Circle: def __init__(self, radius): self.radius = radius @property def area(self): return 3.14159 * self.radius ** 2 c = Circle(5) print(c.area) # 不用写c.area(),跟属性一样直接读

这个方法非常适合用于“只读、由其他属性计算得来”的值。另外一个常见需求是给属性加校验逻辑,可以用setter版本:

class Temperature: def __init__(self, celsius): self._celsius = celsius @property def celsius(self): return self._celsius @celsius.setter def celsius(self, value): if value < -273.15: raise ValueError("温度不能低于绝对零度") self._celsius = value t = Temperature(25) t.celsius = -300 # 抛异常,非法值进不来

这种写法的价值在于:未来如果想在读取或设置时加入日志、缓存或权限校验,不用改外部代码,只需改property内部逻辑即可。我重构老代码的时候经常用property,收益立竿见影。

4.3 封装边界:什么时候该公开,什么时候该隐藏

封装不是越严越好,我见过太多“过度封装”的代码:一个简单工具类,内部所有方法都是下划线开头,外部只有一个run入口,想定制个什么参数都只能靠改源码。这种封装是给自己找麻烦。

我自己的经验是三条原则。第一,稳定的、外部确定需要的能力,公开;不确定的、容易变的内部实现细节,隐藏。第二,通过公开接口读写的属性尽量做成property,一旦要加校验还能兜底。第三,不要为了“以后可能用”去预封装,YAGNI原则照样适用于面向对象设计。等你真正有第二个调用方了,再抽公共接口都来得及。

5. 魔法方法与数据模型:让你的类像原生类型一样好用

5.1str__与__repr:打印对象的正确姿势

我发现很多入门者写类的时候不重写魔法方法,导致调试的时候print(instance)只看到一堆<main.Order object at 0x7f...>,挠头半天也看不出这个对象里到底是什么。魔法方法就是Python留给你的钩子,重写之后你的对象能像内置类型一样融入语言本身。

__str__是为了让人看懂,print的时候调用;__repr__是为了让开发者看清楚,交互式环境直接输入对象名时显示的就是它。两者可以都实现,但至少要保证__repr__存在。一个实用的技巧:__repr__返回一个能重新创建这个对象的表达式字符串。

class Product: def __init__(self, name, price): self.name = name self.price = price def __repr__(self): return f"Product(name={self.name!r}, price={self.price!r})" def __str__(self): return f"{self.name}: ¥{self.price:.2f}"

有了这俩,调试体验直线上升。我在排查数据采集问题时,经常直接在一个列表里print一堆Product实例,日志里一眼就能看出哪条数据价格不对,不需要为每个类写专门的print方法。

5.2 比较与排序:让对象能放进sorted()

默认情况下,自定义类的对象不能直接用<比较大小,排序时也会报错。如果你希望Order可以按订单金额排序、Student可以按成绩排序,就需要实现__lt__(小于)。从Python 3开始,排序只需要一个__lt__就够,但如果需要别的比较,可以让functools.total_ordering帮你补齐。

from functools import total_ordering @total_ordering class Order: def __init__(self, order_id, amount): self.order_id = order_id self.amount = amount def __eq__(self, other): return self.amount == other.amount def __lt__(self, other): return self.amount < other.amount orders = [Order("A001", 200), Order("A002", 50), Order("A003", 999)] for o in sorted(orders): print(o.order_id, o.amount)

这套在数据分析场景很管用。之前我在做量化策略回测的时候,把每笔交易封装成Trade对象,有了__lt__和__eq__,只要一个sorted(trades),就能按盈亏排序,复盘效率高很多。再多说一句,实现__eq__时记得判断other的类型,不然拿Order跟数字比较时可能把程序搞崩。

5.3 上下文管理器:enter/__exit__与with语句

with open(...) as f是大家熟悉的写法,但你也可以让自己的类支持with。只要实现__enter__和__exit__,对象就能放进with语句里,资源自动清理。这个特性特别适合管理数据库连接、文件句柄、锁资源。

class DatabaseConnection: def __enter__(self): print("打开连接") return self def __exit__(self, exc_type, exc_val, exc_tb): print("关闭连接") return False # 返回False表示异常继续抛出 with DatabaseConnection() as db: print("执行查询")

__exit__的返回值有个讲究:返回True表示吞掉异常,返回False表示异常照常抛出。默认建议写False,除非你真的有非常明确的理由要吞异常。否则调试的时候异常莫名其妙消失,那才是真灾难。

除了上述魔法方法,call__也值得知道:让实例像函数一样被调用,适合构建可配置的回调对象。还有__iter/next,可以让你的类变成一个可迭代对象,在for循环里直接用。这些能力拼在一起,你的类在调用者眼里就跟内置类型几乎没差别了。

6. 实战案例:把爬虫脚本改造成OOP架构

6.1 原始脚本的样子与痛点

前面讲了这么多理论,现在用完整案例串一遍。假设我们要做一个简单的数据采集程序,从两个不同风格的网站抓取商品信息,最后汇总导出。大多数入门者的第一版会写成函数堆叠的脚本样式:

import requests import json BASE_URL_A = "https://api.shop-a.com/products" BASE_URL_B = "https://api.shop-b.com/items" def fetch_a(): resp = requests.get(BASE_URL_A, timeout=10) data = resp.json() result = [] for item in data["products"]: result.append({ "name": item["title"], "price": item["sale_price"], "source": "A", }) return result def fetch_b(): resp = requests.get(BASE_URL_B, timeout=10) data = resp.json() result = [] for item in data["item_list"]: result.append({ "name": item["name"], "price": item["price"] * 0.9, "source": "B", }) return result all_items = fetch_a() + fetch_b() with open("output.json", "w") as f: json.dump(all_items, f, ensure_ascii=False, indent=2)

这个脚本能跑,但痛点很多。每次要加一个站点,就要写一个fetch_c,然后在列表里再拼一个。请求异常处理重复、超时设置不统一、站点字段差异逻辑散布在各函数里。将来想加代理、加限速、加导出Excel,所有函数都要动一遍。这种代码就是典型的“能跑,但改不起”。

6.2 重构第一步:用类封装请求与解析

我重构的第一步是先把“数据采集”这个动作抽象成类。一个采集器该有的能力非常清楚:发请求、拿响应、解析内容、返回结构化数据。把请求公共逻辑放进基类,站点差异留给子类。

import requests import json class BaseCollector: def __init__(self, base_url, timeout=10): self.base_url = base_url self.timeout = timeout self.session = requests.Session() self.session.headers.update({"User-Agent": "Mozilla/5.0 (compatible)"}) def fetch(self): resp = self.session.get(self.base_url, timeout=self.timeout) resp.raise_for_status() return self.parse(resp.text) def parse(self, text): raise NotImplementedError

这个结构的核心是:fetch只有一份,所有站点共用;parse留给子类。现在写第一个子类:

class ShopACollector(BaseCollector): def parse(self, text): data = json.loads(text) result = [] for item in data["products"]: result.append({ "name": item["title"], "price": item["sale_price"], "source": "A", }) return result

再写第二个:

class ShopBCollector(BaseCollector): def parse(self, text): data = json.loads(text) result = [] for item in data["item_list"]: result.append({ "name": item["name"], "price": round(item["price"] * 0.9, 2), "source": "B", }) return result

你发现没有,主流程已经非常干净了。想抓哪个站点,就实例化哪个Collector。想抓全部,就把它们放进一个列表循环调用。加新站点,只需新增一个子类,现有代码一行都不用改。

6.3 重构第二步:用组合处理导出逻辑

数据抓回来之后,往往还要写入Excel。这个需求我这里用组合的方式实现,而不是让Collector来继承什么exporter类。组合的意思很直白:一个类持有另一个类的实例,通过调用方法完成协作,而不是往上找共同的父类。

class ExcelExporter: def export(self, items, filename): try: import pandas as pd except ImportError: raise RuntimeError("需要安装pandas:pip install pandas") df = pd.DataFrame(items) df.to_excel(filename, index=False) print(f"已导出 {len(items)} 条数据到 {filename}")

组合的实践大概长这样:

collectors = [ ShopACollector("https://api.shop-a.com/products"), ShopBCollector("https://api.shop-b.com/items"), ] all_items = [] for collector in collectors: try: all_items.extend(collector.fetch()) except Exception as e: print(f"采集失败:{collector.base_url},错误:{e}") ExcelExporter().export(all_items, "products.xlsx")

注意这里我做了异常隔离:单个站点采集失败不会中断整个任务。这在真实场景里非常重要——外部接口总会出各种幺蛾子,一个站点挂了不能让其他站点的数据全部流失。如果当初的代码是全函数堆在一个脚本里,要实现这种隔离就得往每个函数里塞try/except,而现在的结构天然就支持。

6.4 演进后对比:为什么这才是可维护的代码

改造前后的功能几乎一样,但代码结构完全不同了。旧版加一个站点要复制一段fetch逻辑,改一下解析分支。新版加一个站点只需要写一个子类,聚焦在parse方法上。旧版想换导出格式,得改整个脚本底部的输出部分;新版导出是独立的ExcelExporter,换CSV导出自都不用动采集逻辑。

这套设计直接呼应了面向对象编程的三个基石:封装把采集请求和站点解析隔离在各自的类里;继承把公共的请求流程沉淀在基类;多态让不同站点可以用同一套循环逻辑统一调用。最关键的是,这套结构的可测试性也大大提升。我能单独写一个测试文件,实例化ShopACollector,传入伪造的HTML文本测试parse,再也不用被网络请求卡测试流程。

7. 从入门到踩坑:OOP实践中的高频问题与避坑清单

7.1 可变默认参数:隐藏的魔鬼

我见过不少Python老手在函数默认参数上翻车,类的构造函数也一样。如果你把可变对象当默认参数,比如definit(self, items=[]),那么所有没有传items的实例会共享同一个列表。后面实例往里添加元素,之前实例的items也会变。

class Cart: def __init__(self, items=[]): # 危险写法 self.items = items c1 = Cart() c1.items.append("apple") c2 = Cart() print(c2.items) # ['apple'],出问题了

正确写法是默认用None,然后在方法内部新建列表:

class Cart: def __init__(self, items=None): self.items = items if items is not None else []

这个坑踩过一次就很难忘。我现在写任何构造函数,第一反应就是检查默认参数是不是可变类型。

7.2 isinstance与type:别乱用

在面向对象编程里,isinstance和type都能用来判断类型,但语义完全不同。type(obj) == SomeClass是精确匹配,isinstance(obj, SomeClass)还会考虑继承关系。绝大多数场景下你想要的其实是isinstance,因为既然用了OOP,就应当拥抱多态。

不过isinstance也有它的隐性问题。如果代码里到处是isinstance(item, SomeClass)这种分支,说明你的多态设计没做好——更好的做法是同一类型的对象都用同一种方法调用,把差异下沉到各自的类里。用isinstance做分流的正确场景通常是处理外部第三方库的类型,而不是自己类体系内部。

7.3 继承层级别贪深:组合优于继承

继承树越深,脑子越乱。三层以上的继承,已经很难说清楚某个方法到底来自哪个父类。我自己的经验是:超过两层继承就要警惕,能用组合就用组合。

组合的长处是关系清晰:A拥有B,A调用B的方法,而不是A继承B后重写一堆方法。爬虫例子里我看似用了继承,但只是一层基类加多个叶子子类,属于最安全的继承形态。一旦你发现子类在重写父类时还要依赖父类内部的私有状态,就有必要停下来想一下这个继承是不是搞错了方向。

7.4 循环导入、属性遮蔽与过度封装:三个现实教训

循环导入是OOP实践里很常见的问题。你在模块a里定义了类A,模块b里定义类B,A要引用B,B又要引用A,结果一运行报出ImportError。解决办法不是硬着头皮调整导入顺序,而是把公共依赖下沉到第三个模块,或者把其中一个类里的引用改成延迟导入——就是在方法内部再用import。我在重构爬虫框架时就遇到过这个,最后是把配置抽到了一个单独的config模块,问题才干净地解决。

属性遮蔽则是另一个容易忽略的坑。子类定义了和父类同名的属性或方法,父类内部调用时却不知道子类已经把它换了。这不算bug,但会让调试极为困惑。比如父类有个self._items,子类不小心也用了self._items存放别的数据,父类方法读的时候就会拿到子类的值。最稳的做法是内部私有属性统一命名规范,子类尽量别碰父类的下划线属性,需要修改一律通过方法完成。

至于过度封装,我在4.3已经提过,这里再补一句现实观察:我评审过很多新人代码,问题往往不是封得不够,而是封得太死。一个人工智能算法工程师朋友说过一句话,我觉得放这里特别合适:“设计类的时候,多想想未来三个月要改需求的是什么部分,给那个部分留呼吸的缝。”我认为这是衡量封装边界最务实的标准。

我自己实践OOP这几年,最大的体会是没有万能的项目架构,只有不断权衡取舍的工程判断。面向对象编程无论是入门还是实践,核心其实不在语法,在于你能不能把复杂问题拆成一堆边界清晰的小对象,让每个对象只操心自己那一亩三分地。按这个标准写出来的代码,哪怕逻辑再复杂,读起来也会轻松得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 12:46:53

Java后端服务在Linux服务器上的生存指南

1. 这不是“部署教程”&#xff0c;而是后端服务在真实服务器上活下来的生存手册你写完 Spring Boot 项目&#xff0c;打了个 jar 包&#xff0c;兴冲冲java -jar app.jar一跑——本地 localhost:8080 能访问&#xff0c;日志刷得飞起&#xff0c;心里美滋滋。结果一上服务器&a…

作者头像 李华
网站建设 2026/10/1 12:46:53

YOLO驾驶员疲劳检测模型实战:从数据集构建到PERCLOS告警

简介&#xff1a;面向计算机视觉与智能驾驶安全领域的研究者、开发者&#xff0c;该资料包含基于YOLO算法的驾驶员疲劳检测完整模型与配套数据集&#xff0c;可识别驾驶员闭眼、打哈欠等疲劳行为&#xff0c;适用于疲劳驾驶预警系统研发、算法课程教学、毕业论文或课题验证。压…

作者头像 李华
网站建设 2026/10/1 12:46:50

Jev模型实战:LangChain与LangGraph中的结构化输出与自动化测试脚本生成

1. 从“不说话的模型”说起&#xff1a;Jev 到底是个什么东西 第一次看到“Jev&#xff1a;一个不说话的模型”这个标题&#xff0c;我脑子里蹦出来的第一个念头是——这年头还有模型不爱说话&#xff1f;毕竟从 ChatGPT 开始&#xff0c;大家已经习惯了模型“话痨”式的交互方…

作者头像 李华
网站建设 2026/10/1 12:46:32

OpenRig 实战指南:Node.js + tmux + YAML + Codex 四件套本地AI开发基座搭建

1. OpenRig 是什么&#xff1a;一个被严重误读的开源项目名称OpenRig 这个词在当前中文技术社区里&#xff0c;正经历一场典型的“语义漂移”——它既不是某个广为人知的成熟开源框架&#xff0c;也不是某家大厂发布的官方工具套件&#xff0c;更不是 Codex、Node.js 或 YAML 的…

作者头像 李华
网站建设 2026/10/1 12:45:33

无独显16G内存本地部署大模型:Ollama与llama.cpp实战指南

1. 低配电脑跑大模型&#xff0c;先搞清楚你到底在折腾什么先把结论摆在前面&#xff1a;2026 年了&#xff0c;一台没有独立显卡、只有 16G 内存的普通办公电脑&#xff0c;本地部署 AI 大模型这件事&#xff0c;能跑&#xff0c;但能跑的东西和你想象中的东西&#xff0c;大概…

作者头像 李华