[{"data":1,"prerenderedAt":1678},["ShallowReactive",2],{"post:\u002Fposts\u002Fswdesign\u002Fsw-design-notes-22":3,"posts:ordered":1376},{"id":4,"title":5,"body":6,"category":1358,"cover":1359,"date":1360,"description":1361,"extension":1362,"featured":1363,"meta":1364,"navigation":1365,"path":1366,"seo":1367,"series":1368,"seriesOrder":1369,"stem":1370,"tags":1371,"__hash__":1375},"posts\u002Fposts\u002FswDesign\u002Fsw-design-notes-22.md","软件分析与设计笔记 22：分析建模：边界、实体与控制对象",{"type":7,"value":8,"toc":1327},"minimark",[9,16,25,30,33,46,55,59,114,124,143,150,154,161,166,176,183,189,210,216,220,226,245,255,259,265,275,285,297,301,305,311,318,322,325,504,514,517,523,527,530,536,542,552,557,561,565,568,614,617,724,731,735,742,767,773,777,781,787,807,813,839,843,846,909,922,926,933,944,948,951,972,975,1068,1075,1078,1081,1109,1112,1187,1190],[10,11,12],"blockquote",{},[13,14,15],"p",{},"这是我在学习软件分析与设计时整理的系列笔记。概念部分是我按自己的理解写的，示例与图示都是自己编、自己画的。理解有偏差的地方欢迎指出。",[10,17,18],{},[13,19,20,24],{},[21,22,23],"strong",{},"贯穿案例","：片区家电维修派单系统。21 篇讲了 UML 的符号，这一篇用它做一次真正的分析建模 —— 产物就是 27 篇考试里那道顺序图题。",[26,27,29],"h2",{"id":28},"_0-跳过分析直接写代码会怎样","0 跳过分析直接写代码会怎样",[13,31,32],{},"需求给了、数据库也建好了，直接开始写代码 —— 这是很多项目的实际做法。它通常在三到六个月后付出代价：",[34,35,36,40,43],"ul",{},[37,38,39],"li",{},"界面上的一段逻辑，被复制到三个页面里（因为写的时候没想清楚该放哪）。本例：客服端和工程师端都要「校验工单是否进行中」，结果这段判断写了两遍；",[37,41,42],{},"业务规则散落在控制器、数据访问和界面代码里。本例：「库存不足就转待件挂起」这条规则如果写在页面里，仓管端就没法复用；",[37,44,45],{},"想做一个「批量导入」，发现没有地方可以复用已有的处理逻辑。本例：想给仓管做「批量出库」，发现出库逻辑埋在单个出库的页面事件里。",[13,47,48,51,52],{},[21,49,50],{},"分析建模（analysis modelling）"," 就是在需求和代码之间插一层：",[21,53,54],{},"先想清楚每个用例由哪些对象协作完成。",[26,56,58],{"id":57},"_1-它在流程里的位置","1 它在流程里的位置",[60,61,62,78],"table",{},[63,64,65],"thead",{},[66,67,68,72,75],"tr",{},[69,70,71],"th",{},"阶段",[69,73,74],{},"回答的问题",[69,76,77],{},"产物",[79,80,81,93,103],"tbody",{},[66,82,83,87,90],{},[84,85,86],"td",{},"需求建模（requirements modelling）",[84,88,89],{},"系统要做什么",[84,91,92],{},"用例模型、活动图",[66,94,95,97,100],{},[84,96,50],{},[84,98,99],{},"由哪些对象协作完成",[84,101,102],{},"分析模型（类图 + 顺序图）",[66,104,105,108,111],{},[84,106,107],{},"设计建模（design modelling）",[84,109,110],{},"怎么落到平台与数据上",[84,112,113],{},"设计模型（含表、访问层）",[13,115,116,117,120,121],{},"分析建模有个重要特征：",[21,118,119],{},"它刻意不关心实现平台","。不讨论用哪个框架、哪张表、什么字段类型 —— 只讨论职责怎么分。",[21,122,123],{},"先把「该谁做」定下来，再决定「怎么做」。",[13,125,126,127,131,132,135,136,139,140],{},"这一点在本例里有个很具体的表现：分析模型里写「",[128,129,130],"code",{},":PartsController"," 调用 ",[128,133,134],{},":StockItem.getStock()","」，不写这个调用是走数据库还是走缓存，也不写 ",[128,137,138],{},"getStock()"," 返回的是对象还是 DTO。",[21,141,142],{},"这些等设计阶段再说。",[13,144,145],{},[146,147],"img",{"alt":148,"src":149},"分析建模里的三种对象与用例实现：边界对象负责系统与外部交互，实体对象承载业务数据与规则，控制对象协调用例流程；下方用一个顺序图示意如何从对象之间的消息导出类与操作，并标出三条关系限制","\u002Fimages\u002Fsw-design-notes-22-analysis-objects.svg",[26,151,153],{"id":152},"_2-三种分析对象","2 三种分析对象",[13,155,156,157,160],{},"分析对象（analysis object）通常按职责分成三种",[21,158,159],{},"构造型（stereotype）","：",[162,163,165],"h3",{"id":164},"_21-边界对象","2.1 边界对象",[13,167,168,171,172,175],{},[21,169,170],{},"① 边界对象（boundary object）"," —— 系统与外部交互的地方：界面、外部接口。它负责接收输入与展示结果，",[21,173,174],{},"不负责业务规则","。",[13,177,178,179,182],{},"它的一个重要特征是",[21,180,181],{},"改动最频繁","：界面几乎每次迭代都在变。所以业务规则一定不能写在边界对象里，否则每次改界面都要重新验证业务逻辑。",[13,184,185,186,160],{},"本例里有两个边界对象，而且是",[21,187,188],{},"两种不同的边界",[34,190,191,201],{},[37,192,193,196,197,200],{},[128,194,195],{},":PartsForm"," —— ",[21,198,199],{},"人的界面","（工程师填申请的那个表单）；",[37,202,203,196,206,209],{},[128,204,205],{},":SupplierAPI",[21,207,208],{},"系统的接口","（去查供应商库存的那一端）。",[13,211,212,213],{},"两者都叫 boundary，但性质完全不同：前者会因为「按钮放左边还是右边」而改，后者会因为「供应商改了接口版本」而改。",[21,214,215],{},"区分它们的意义在于：改动的触发源不同，所以不能混在一个类里。",[162,217,219],{"id":218},"_22-实体对象","2.2 实体对象",[13,221,222,225],{},[21,223,224],{},"② 实体对象（entity object）"," —— 业务上需要长期记住的东西，也就是笔记 09 里的业务实体。它承载数据与规则，通常能对应到数据表。",[13,227,228,229,232,233,236,237,240,241,244],{},"本例的实体是 ",[128,230,231],{},":WorkOrder","（工单）、",[128,234,235],{},":StockItem","（库存项）、",[128,238,239],{},":PartUsage","（配件消耗）、",[128,242,243],{},":IssueNote","（出库单）。",[13,246,247,248,251,252,254],{},"这里有个关键点值得强调：",[21,249,250],{},"业务规则应该落在实体上，而不是控制对象上。","「工单在什么状态下允许申请配件」这条规则属于 ",[128,253,231],{},"，因为它管的是工单自己的状态；如果写进控制器，那么别的用例（比如批量出库）想复用这条规则就复用不了。",[162,256,258],{"id":257},"_23-控制对象","2.3 控制对象",[13,260,261,264],{},[21,262,263],{},"③ 控制对象（control object）"," —— 协调一个用例的执行顺序：接收请求，判断该交给谁处理（这个动作就是委派，delegation），再汇总结果。它本身不该堆积业务规则。",[13,266,267,270,271,274],{},[21,268,269],{},"一般给每个用例配一个控制对象","，命名上直接对应：",[128,272,273],{},":RequestPartsController"," 实现「申请配件」这个用例。这个名字本身就能追溯 —— 看到类名就知道它服务哪个用例。",[13,276,277,278,281,282],{},"控制对象是三者里最容易失控的 —— 因为它「什么都管」，很容易变成什么都往里塞的",[21,279,280],{},"上帝类（god class）","。一个判断方法：",[21,283,284],{},"如果这个类的方法里出现了大量「先查 A、再判断 B、然后更新 C」，那这些逻辑大多属于实体对象。",[13,286,287,288,290,291,293,294,296],{},"本例里一个具体的越界：如果 ",[128,289,273],{}," 里写了「如果数量大于 20 就报错」，这条规则就放错了 —— 它属于 ",[128,292,239],{},"。控制器只该做「收到申请 → 让 ",[128,295,239],{}," 自己校验 → 根据结果决定下一步」。",[26,298,300],{"id":299},"_3-用顺序图做一次用例实现","3 用顺序图做一次用例实现",[162,302,304],{"id":303},"_31-做法","3.1 做法",[13,306,307,308,175],{},"把「每个用例由哪些对象协作完成」画出来，这个过程叫",[21,309,310],{},"用例实现（use case realization）",[13,312,313,314,317],{},"做法是：取一个用例的",[21,315,316],{},"主流程（basic flow）","，逐个步骤问「这一步由谁执行」，把参与者与对象的交互画成顺序图。",[162,319,321],{"id":320},"_32-本例完整走一遍申请配件","3.2 本例完整走一遍：申请配件",[13,323,324],{},"08 篇那个「申请配件」的主流程有 10 步。逐行问「谁执行」，得到这样一张对照：",[60,326,327,343],{},[63,328,329],{},[66,330,331,334,337,340],{},[69,332,333],{},"flow 步骤",[69,335,336],{},"谁发起",[69,338,339],{},"发给谁",[69,341,342],{},"消息",[79,344,345,364,382,400,418,433,451,469,488],{},[66,346,347,350,355,359],{},[84,348,349],{},"1. 工程师提交申请",[84,351,352],{},[128,353,354],{},":Engineer",[84,356,357],{},[128,358,195],{},[84,360,361],{},[128,362,363],{},"submit(workOrderId, partNo, qty)",[66,365,366,369,373,377],{},[84,367,368],{},"2. 校验工单状态",[84,370,371],{},[128,372,195],{},[84,374,375],{},[128,376,273],{},[84,378,379],{},[128,380,381],{},"requestParts(...)",[66,383,384,387,391,395],{},[84,385,386],{},"3. 查询当前库存",[84,388,389],{},[128,390,273],{},[84,392,393],{},[128,394,235],{},[84,396,397],{},[128,398,399],{},"getStock(partNo)",[66,401,402,405,409,413],{},[84,403,404],{},"4. 调用供应商接口",[84,406,407],{},[128,408,273],{},[84,410,411],{},[128,412,205],{},[84,414,415],{},[128,416,417],{},"queryAvailable(partNo)",[66,419,420,423,427,430],{},[84,421,422],{},"5. 比对判断",[84,424,425],{},[128,426,273],{},[84,428,429],{},"自己",[84,431,432],{},"自身方法",[66,434,435,438,442,446],{},[84,436,437],{},"6. 生成出库单",[84,439,440],{},[128,441,273],{},[84,443,444],{},[128,445,243],{},[84,447,448],{},[128,449,450],{},"createIssueNote(...)",[66,452,453,456,460,464],{},[84,454,455],{},"7. 写入工单",[84,457,458],{},[128,459,273],{},[84,461,462],{},[128,463,231],{},[84,465,466],{},[128,467,468],{},"addPartUsage(...)",[66,470,471,474,478,483],{},[84,472,473],{},"8. 通知仓管",[84,475,476],{},[128,477,273],{},[84,479,480],{},[128,481,482],{},":Storekeeper",[84,484,485],{},[128,486,487],{},"notify()",[66,489,490,493,497,501],{},[84,491,492],{},"9. 显示成功",[84,494,495],{},[128,496,273],{},[84,498,499],{},[128,500,195],{},[84,502,503],{},"返回结果",[13,505,506,507,513],{},"这张表就是顺序图的草稿。",[21,508,509,510,512],{},"注意第 4 步的接收者是 ",[128,511,205],{},"（boundary），不是实体"," —— 判断依据在 21 篇讲过：「你需要写代码去和它对接，那它是系统外部接口」。",[13,515,516],{},"把这张表画成图，就是本例的用例实现：",[13,518,519],{},[146,520],{"alt":521,"src":522},"申请配件的分析模型顺序图：五条生命线从左到右依次是工程师、申请界面、配件控制器、库存项与供应商接口，构造型分别是 actor、boundary、control、entity、boundary。消息按 1 到 9 编号，分别是提交申请、转控制器、查本地库存并虚线返回、调供应商接口并虚线返回、控制器自身比对、创建出库单、写入工单的配件消耗、通知仓管、返回成功消息给界面。底部标出三个观察点：控制器的委派比例健康、外部系统是边界对象、通知仓管这一步可疑","\u002Fimages\u002Fsw-design-notes-22-sequence-realization.svg",[162,524,526],{"id":525},"_33-顺序图暴露了什么","3.3 顺序图暴露了什么",[13,528,529],{},"画完这张图，有两件事立刻变得清楚：",[13,531,532,535],{},[21,533,534],{},"① 控制器确实在协调，但没有堆积规则","。它做了 4 次委派（去库存、去供应商、去出库单、去工单），自己只做了「比对」这一个判断。这个比例是健康的。",[13,537,538,541],{},[21,539,540],{},"② 第 8 步「通知仓管」是个可疑点","。仓库通知是「申请配件」这个用例的一部分吗？还是应该独立成一个用例？",[13,543,544,545,548,549,551],{},"讨论的结论是：",[21,546,547],{},"应该是独立的","。因为通知的渠道和时机（短信 \u002F App \u002F 邮件）与配件申请无关，而且仓管可能想改通知方式而不影响申请流程。所以分析模型里保留 ",[128,550,487],{},"，但设计时它可以变成一个独立的消息服务。",[13,553,554],{},[21,555,556],{},"这就是分析建模的价值：它让你在写代码之前就发现「这里可能该切开」。",[26,558,560],{"id":559},"_4-从顺序图导出类","4 从顺序图导出类",[162,562,564],{"id":563},"_41-导出规则","4.1 导出规则",[13,566,567],{},"顺序图画清楚之后，类图基本就出来了 —— 导出规则是机械的：",[60,569,570,580],{},[63,571,572],{},[66,573,574,577],{},[69,575,576],{},"顺序图里的东西",[69,578,579],{},"对应到类图",[79,581,582,590,598,606],{},[66,583,584,587],{},[84,585,586],{},"每个参与对象",[84,588,589],{},"一个类",[66,591,592,595],{},[84,593,594],{},"对象收到的消息",[84,596,597],{},"该类的一个操作",[66,599,600,603],{},[84,601,602],{},"需要记住的数据",[84,604,605],{},"类的属性",[66,607,608,611],{},[84,609,610],{},"谁调用谁",[84,612,613],{},"类之间的关系",[13,615,616],{},"按这张表，从上面那张顺序图导出的类图是这样的：",[60,618,619,632],{},[63,620,621],{},[66,622,623,626,629],{},[69,624,625],{},"类",[69,627,628],{},"构造型",[69,630,631],{},"操作（来自收到的消息）",[79,633,634,653,668,682,696,710],{},[66,635,636,641,644],{},[84,637,638],{},[128,639,640],{},"PartsForm",[84,642,643],{},"boundary",[84,645,646,649,650],{},[128,647,648],{},"submit()","、",[128,651,652],{},"displayResult()",[66,654,655,660,663],{},[84,656,657],{},[128,658,659],{},"RequestPartsController",[84,661,662],{},"control",[84,664,665],{},[128,666,667],{},"requestParts()",[66,669,670,675,678],{},[84,671,672],{},[128,673,674],{},"StockItem",[84,676,677],{},"entity",[84,679,680],{},[128,681,138],{},[66,683,684,689,691],{},[84,685,686],{},[128,687,688],{},"SupplierAPI",[84,690,643],{},[84,692,693],{},[128,694,695],{},"queryAvailable()",[66,697,698,703,705],{},[84,699,700],{},[128,701,702],{},"IssueNote",[84,704,677],{},[84,706,707],{},[128,708,709],{},"createIssueNote()",[66,711,712,717,719],{},[84,713,714],{},[128,715,716],{},"WorkOrder",[84,718,677],{},[84,720,721],{},[128,722,723],{},"addPartUsage()",[13,725,726,727,730],{},"注意这三个实体的操作全都是「",[21,728,729],{},"名词 + 动词","」的形式，而且都跟自己的数据有关 —— 这正是 21 篇强调的「行为跟着数据走」。",[162,732,734],{"id":733},"_42-为什么先画顺序图","4.2 为什么先画顺序图",[13,736,737,738,741],{},"这也是为什么推荐",[21,739,740],{},"先画顺序图、再画类图","：如果反过来，先列一堆类再想它们怎么协作，很容易列出一批「看起来合理但用不上」的类。",[13,743,744,745,748,749,748,752,755,756,759,760,762,763,766],{},"本例里可以验证这一点：如果先凭空列类，很容易列出「",[128,746,747],{},"NotificationService","」「",[128,750,751],{},"InventoryManager",[128,753,754],{},"ValidationHelper","」这类看起来专业的类 —— 但它们在这条流程里",[21,757,758],{},"一个都用不上","（通知只有一行 ",[128,761,487],{},"，校验只有 ",[128,764,765],{},"qty > 20"," 一个判断）。",[13,768,769,772],{},[21,770,771],{},"用了才建，不用不建","。这是分析建模最实用的一条纪律。",[26,774,776],{"id":775},"_5-关系上的限制","5 关系上的限制",[162,778,780],{"id":779},"_51-不是所有关系都能随便用","5.1 不是所有关系都能随便用",[13,782,783,784],{},"从顺序图导出关系时，有一点要留意：",[21,785,786],{},"不是所有关系都可以随便用。",[34,788,789,795,801],{},[37,790,791,794],{},[21,792,793],{},"依赖"," —— 出现在方法参数里，最轻；",[37,796,797,800],{},[21,798,799],{},"关联"," —— 需要长期保留引用时才用；",[37,802,803,806],{},[21,804,805],{},"聚合 \u002F 组合"," —— 要有明确的整体—部分语义。",[13,808,809,810],{},"一个常见错误是「为了让图好看，把所有关系都画成组合」。判断依据还是那句：",[21,811,812],{},"整体没了，这个部分还活得下去吗？",[13,814,815,816,818,819,821,822,818,824,826,827,830,831,834,835,838],{},"本例里 ",[128,817,231],{}," 与 ",[128,820,239],{}," 是真组合（配件消耗随工单消亡），而 ",[128,823,239],{},[128,825,235],{}," 只是",[21,828,829],{},"引用","（库存项独立存在，配件消耗只是记了个 ",[128,832,833],{},"partNo","）。",[21,836,837],{},"这两个如果都画成组合，实现的人会以为删工单要删库存项"," —— 那是灾难。",[162,840,842],{"id":841},"_52-分析模型的三条硬限制","5.2 分析模型的三条硬限制",[13,844,845],{},"讲义明确列出的三条，属于「画了就扣分」的类型：",[60,847,848,861],{},[63,849,850],{},[66,851,852,855,858],{},[69,853,854],{},"关系",[69,856,857],{},"能不能用",[69,859,860],{},"本例的说明",[79,862,863,881,898],{},[66,864,865,868,871],{},[84,866,867],{},"boundary 直接关联 entity",[84,869,870],{},"❌ 不推荐",[84,872,873,875,876,878,879],{},[128,874,195],{}," 不能直连 ",[128,877,235],{},"，中间必须隔着 ",[128,880,273],{},[66,882,883,886,888],{},[84,884,885],{},"actor 与 entity \u002F control 直接关联",[84,887,870],{},[84,889,890,892,893,895,896],{},[128,891,354],{}," 只接触 ",[128,894,195],{},"，不能直接连 ",[128,897,231],{},[66,899,900,903,906],{},[84,901,902],{},"不同构造型之间做继承 \u002F 聚合 \u002F 组合",[84,904,905],{},"❌ 无意义",[84,907,908],{},"「控制器继承实体」这种关系没有业务含义",[13,910,911,912,915,916,918,919,921],{},"第一条的理由值得说清：",[21,913,914],{},"如果界面能直接拿数据，那「业务规则该放哪」这件事就彻底失守了","。因为一旦 ",[128,917,195],{}," 能直接读 ",[128,920,235],{},"，它就会顺手在那里写判断，然后这段判断再也不会被别处复用。",[162,923,925],{"id":924},"_53-关系方向要反映调用方向","5.3 关系方向要反映调用方向",[13,927,928,929,932],{},"还有一条实践建议：",[21,930,931],{},"关系的方向要能反映调用方向","。如果 A 调 B、B 又调 A，那张图上会出现环 —— 环通常不是错，但值得停下来看看是不是职责切分有问题。",[13,934,935,936,939,940,943],{},"本例的分析模型是",[21,937,938],{},"无环","的：",[128,941,942],{},":Engineer → :PartsForm → :RequestPartsController → 各实体","。这个单向性是健康的 —— 它说明职责分得清楚，没有互相依赖。",[26,945,947],{"id":946},"_6-类图怎么文档化documenting-the-class-diagram","6 类图怎么文档化（documenting the class diagram）",[13,949,950],{},"分析模型最终要交出去，类图的说明通常有三种组织方式：",[952,953,954,960,966],"ol",{},[37,955,956,959],{},[21,957,958],{},"每张图配一段文字"," —— 逐个类说明职责。适合类少的场景；",[37,961,962,965],{},[21,963,964],{},"统一一张职责表"," —— 类名 + 职责 + 关键操作 + 协作对象。适合类多的场景，也便于对照检查职责有没有重叠；",[37,967,968,971],{},[21,969,970],{},"在图上直接标注"," —— 把职责写在类旁边的注释框里。适合评审时快速对齐。",[13,973,974],{},"本例用第二种最合适，因为类不多不少（六个）：",[60,976,977,989],{},[63,978,979],{},[66,980,981,983,986],{},[69,982,625],{},[69,984,985],{},"一句话职责",[69,987,988],{},"协作对象",[79,990,991,1004,1016,1029,1042,1055],{},[66,992,993,997,1000],{},[84,994,995],{},[128,996,640],{},[84,998,999],{},"接收工程师的申请输入，显示结果",[84,1001,1002],{},[128,1003,659],{},[66,1005,1006,1010,1013],{},[84,1007,1008],{},[128,1009,659],{},[84,1011,1012],{},"协调一次配件申请的执行顺序",[84,1014,1015],{},"其余五个",[66,1017,1018,1022,1025],{},[84,1019,1020],{},[128,1021,674],{},[84,1023,1024],{},"记住某个配件的当前库存与安全库存",[84,1026,1027],{},[128,1028,659],{},[66,1030,1031,1035,1038],{},[84,1032,1033],{},[128,1034,688],{},[84,1036,1037],{},"对接供应商，返回实时可售量",[84,1039,1040],{},[128,1041,659],{},[66,1043,1044,1048,1051],{},[84,1045,1046],{},[128,1047,702],{},[84,1049,1050],{},"记录一次出库（谁、何时、多少）",[84,1052,1053],{},[128,1054,659],{},[66,1056,1057,1061,1064],{},[84,1058,1059],{},[128,1060,716],{},[84,1062,1063],{},"记录工单状态与它用掉的配件",[84,1065,1066],{},[128,1067,659],{},[13,1069,1070,1071,1074],{},"三种方式可以混用。关键是",[21,1072,1073],{},"每个类的职责必须有一句话能说清楚"," —— 说不清，通常意味着这个类做了两件不相关的事。",[13,1076,1077],{},"上表里如果有哪一行写不出来，就该回去检查那个类了。",[26,1079,1080],{"id":1080},"本节自测",[952,1082,1083,1086,1089,1092,1095,1098,1106],{},[37,1084,1085],{},"分析建模和设计建模的关注点差别是什么？",[37,1087,1088],{},"业务规则为什么不该写在边界对象里？",[37,1090,1091],{},"怎么判断一个控制对象是不是已经变成上帝类？",[37,1093,1094],{},"从顺序图导出类图时，「对象收到的消息」对应到类图的什么？",[37,1096,1097],{},"为什么推荐先画顺序图再画类图，而不是反过来？",[37,1099,815,1100,1102,1103,1105],{},[128,1101,195],{}," 和 ",[128,1104,205],{}," 都是 boundary，为什么要分成两个类？",[37,1107,1108],{},"本例分析模型为什么是「无环」的？有环说明什么？",[162,1110,1111],{"id":1111},"参考答案",[952,1113,1114,1120,1127,1137,1153,1165,1177],{},[37,1115,1116,1119],{},[21,1117,1118],{},"分析建模只讨论「职责怎么分」，不关心实现平台","（不讨论框架、表、字段类型）；设计建模才把这些落到数据库、访问层与框架上（§1）。",[37,1121,1122,1123,1126],{},"因为",[21,1124,1125],{},"边界对象改动最频繁","（界面每次迭代都在变）。业务规则写在那里，每次改界面都要重新验证业务逻辑，而且别的用例无法复用（§2.1）。",[37,1128,1129,1130,1133,1134,1136],{},"看它的方法里是不是有大量 ",[21,1131,1132],{},"「先查 A、再判断 B、然后更新 C」"," —— 这类逻辑大多属于实体对象。本例反例：控制器里写「数量大于 20 就报错」，这条规则应属于 ",[128,1135,239],{},"（§2.3）。",[37,1138,1139,1140,1143,1144,1146,1147,1149,1150,1152],{},"对应到",[21,1141,1142],{},"该类的一个操作（operation）","。本例 ",[128,1145,231],{}," 收到 ",[128,1148,723],{}," 消息，所以它就有 ",[128,1151,723],{}," 这个操作（§4.1）。",[37,1154,1155,1156,1159,1160,649,1162,1164],{},"因为反过来先列类，很容易列出一批 ",[21,1157,1158],{},"「看起来合理但用不上」"," 的类。本例若凭空列，会列出 ",[128,1161,747],{},[128,1163,754],{}," 等，但流程里它们各只有一个方法（§4.2）。",[37,1166,1167,1168,160,1171,1173,1174,1176],{},"因为它们的",[21,1169,1170],{},"改动触发源不同",[128,1172,195],{}," 因「按钮放哪」而改，",[128,1175,205],{}," 因「供应商接口改版」而改。混在一个类里会让两种变化互相牵连（§2.1）。",[37,1178,1179,1180,1182,1183,1186],{},"因为调用方向是单向的：",[128,1181,942],{},"。有环说明",[21,1184,1185],{},"职责切分有问题","（A 与 B 互相依赖）（§5.3）。",[26,1188,1189],{"id":1189},"术语表",[60,1191,1192,1205],{},[63,1193,1194],{},[66,1195,1196,1199,1202],{},[69,1197,1198],{},"术语",[69,1200,1201],{},"中文",[69,1203,1204],{},"一句话解释",[79,1206,1207,1218,1229,1239,1250,1261,1272,1283,1294,1305,1316],{},[66,1208,1209,1212,1215],{},[84,1210,1211],{},"analysis modelling",[84,1213,1214],{},"分析建模",[84,1216,1217],{},"确定每个用例由哪些对象协作完成",[66,1219,1220,1223,1226],{},[84,1221,1222],{},"analysis object",[84,1224,1225],{},"分析对象",[84,1227,1228],{},"分析模型里承担一种职责的对象",[66,1230,1231,1234,1236],{},[84,1232,1233],{},"stereotype",[84,1235,628],{},[84,1237,1238],{},"给模型元素附加语义的分类标记",[66,1240,1241,1244,1247],{},[84,1242,1243],{},"boundary object",[84,1245,1246],{},"边界对象",[84,1248,1249],{},"系统与外部交互的对象，如界面与外部接口",[66,1251,1252,1255,1258],{},[84,1253,1254],{},"entity object",[84,1256,1257],{},"实体对象",[84,1259,1260],{},"承载业务数据与规则的对象",[66,1262,1263,1266,1269],{},[84,1264,1265],{},"control object",[84,1267,1268],{},"控制对象",[84,1270,1271],{},"协调用例执行顺序的对象",[66,1273,1274,1277,1280],{},[84,1275,1276],{},"use case realization",[84,1278,1279],{},"用例实现",[84,1281,1282],{},"用对象协作把用例的流程具体化",[66,1284,1285,1288,1291],{},[84,1286,1287],{},"basic flow",[84,1289,1290],{},"主流程",[84,1292,1293],{},"一切顺利时的步骤序列",[66,1295,1296,1299,1302],{},[84,1297,1298],{},"delegation",[84,1300,1301],{},"委派",[84,1303,1304],{},"把请求转给该处理它的对象",[66,1306,1307,1310,1313],{},[84,1308,1309],{},"god class",[84,1311,1312],{},"上帝类",[84,1314,1315],{},"职责过多、什么都管的类",[66,1317,1318,1321,1324],{},[84,1319,1320],{},"documenting the class diagram",[84,1322,1323],{},"类图文档化",[84,1325,1326],{},"用文字、表格或注释说明每个类的职责",{"title":1328,"searchDepth":1329,"depth":1329,"links":1330},"",3,[1331,1333,1334,1339,1344,1348,1353,1354,1357],{"id":28,"depth":1332,"text":29},2,{"id":57,"depth":1332,"text":58},{"id":152,"depth":1332,"text":153,"children":1335},[1336,1337,1338],{"id":164,"depth":1329,"text":165},{"id":218,"depth":1329,"text":219},{"id":257,"depth":1329,"text":258},{"id":299,"depth":1332,"text":300,"children":1340},[1341,1342,1343],{"id":303,"depth":1329,"text":304},{"id":320,"depth":1329,"text":321},{"id":525,"depth":1329,"text":526},{"id":559,"depth":1332,"text":560,"children":1345},[1346,1347],{"id":563,"depth":1329,"text":564},{"id":733,"depth":1329,"text":734},{"id":775,"depth":1332,"text":776,"children":1349},[1350,1351,1352],{"id":779,"depth":1329,"text":780},{"id":841,"depth":1329,"text":842},{"id":924,"depth":1329,"text":925},{"id":946,"depth":1332,"text":947},{"id":1080,"depth":1332,"text":1080,"children":1355},[1356],{"id":1111,"depth":1329,"text":1111},{"id":1189,"depth":1332,"text":1189},"学习",null,"2026-11-13","不经过分析直接写代码会踩什么坑、三种分析对象各管一段、怎么用顺序图完成一次用例实现，以及从顺序图导出类的属性、操作与关系的方法。","md",false,{},true,"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-22",{"title":5,"description":1361},"软件分析与设计笔记",22,"posts\u002FswDesign\u002Fsw-design-notes-22",[1214,1372,1373,1374],"面向对象","UML","学习笔记","soPdCg9hFINBxZ-agzMbGryde0a5Gtscry0xWaxjBLo",[1377,1383,1389,1395,1401,1407,1413,1414,1420,1426,1432,1438,1444,1450,1456,1462,1468,1474,1480,1486,1492,1498,1504,1510,1516,1522,1527,1532,1538,1544,1548,1552,1556,1560,1564,1568,1572,1576,1580,1584,1588,1594,1598,1604,1608,1612,1616,1620,1624,1628,1632,1637,1641,1645,1650,1655,1661,1665,1670,1674],{"path":1378,"title":1379,"description":1380,"date":1381,"series":1368,"seriesOrder":1382},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-28","软件分析与设计笔记 28：draw.io 作图手册：五张图的符号位置、落笔顺序与自检","不模拟软件界面，只讲清三件事：每个 UML 符号在 draw.io 里搜什么词、一张图该按什么顺序落笔、画完拿什么标准自查。覆盖用例图、活动图、顺序图、类图、界面草图五种图纸，末尾附「最容易画反的十二笔」总检表。","2026-11-19",28,{"path":1384,"title":1385,"description":1386,"date":1387,"series":1368,"seriesOrder":1388},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-27","软件分析与设计笔记 27：Paper 1 应试：从读案例到交卷的一次完整演算","拿一份完整的案例，从头到尾走一遍：怎么读案例列角色、怎么在四个小问之间分配时间、每一张图从哪一句话推出来、两张表怎么填、交卷前拿什么清单自查。附 C25 真题与样卷的题型分值对照。","2026-11-18",27,{"path":1390,"title":1391,"description":1392,"date":1393,"series":1368,"seriesOrder":1394},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-26","软件分析与设计笔记 26：总结：把整条链路串起来","系列收尾：从需求到代码的完整链路回顾、每一环的产出与验收方式、六条贯穿全系列的原则，以及一份可以直接拿去做项目自查的清单。","2026-11-17",26,{"path":1396,"title":1397,"description":1398,"date":1399,"series":1368,"seriesOrder":1400},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-25","软件分析与设计笔记 25：代码重构：坏味道与手法","重构不改变外部行为、只改善内部结构：四种常见坏味道与对应手法、为什么动手之前必须先有测试、什么时候该重构，以及重构和前面那些设计模型是什么关系。","2026-11-16",25,{"path":1402,"title":1403,"description":1404,"date":1405,"series":1368,"seriesOrder":1406},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-24","软件分析与设计笔记 24：设计原则：内聚、耦合与多态","好设计怎么衡量：内聚与耦合分别在说什么、一次从「差设计」到「多态重设计」的完整对照、抽象类与接口的选择依据，以及复用为什么会从优势变成风险。","2026-11-15",24,{"path":1408,"title":1409,"description":1410,"date":1411,"series":1368,"seriesOrder":1412},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-23","软件分析与设计笔记 23：设计建模：从对象模型到能实现的方案","分析模型能用但还不能直接开工：对象模型怎么映射到关系模型、继承映射的三种方案怎么选、数据访问层与 Data Broker 解决什么问题，以及三层架构与 MVC 的分工。","2026-11-14",23,{"path":1366,"title":5,"description":1361,"date":1360,"series":1368,"seriesOrder":1369},{"path":1415,"title":1416,"description":1417,"date":1418,"series":1368,"seriesOrder":1419},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-21","软件分析与设计笔记 21：UML：类图与顺序图","为什么需要一种共同的画法：类符号三格怎么画、五种关系的符号与设计含义、每种关系落到 Java 是什么写法，顺序图的三要素怎么摆；以及这三种最容易找不到的符号在 draw.io 里各搜什么词。","2026-11-12",21,{"path":1421,"title":1422,"description":1423,"date":1424,"series":1368,"seriesOrder":1425},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-20","软件分析与设计笔记 20：面向对象概念：对象、关系与多态","面向对象不是「用某个语言写代码」：对象与类的区别、封装真正在保护什么、五种关系（依赖、关联、聚合、组合、继承）各自何时用，以及多态为什么能减少条件判断。","2026-11-11",20,{"path":1427,"title":1428,"description":1429,"date":1430,"series":1368,"seriesOrder":1431},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-19","软件分析与设计笔记 19：规范化与逻辑数据模型","一次改地址引发的数据不一致，讲清规范化的真正动机；UNF 到 3NF 每一步在解决什么问题，派生字段为什么不能存，优化为什么和规范化方向相反，以及逻辑数据模型在 3NF 之上补了什么。","2026-11-10",19,{"path":1433,"title":1434,"description":1435,"date":1436,"series":1368,"seriesOrder":1437},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-18","软件分析与设计笔记 18：数据库分析：实体、关系与数据字典","为什么不能拿到需求就动手建表：实体与关系怎么判定、基数为什么必须由业务方确认、数据命名要遵守什么规则，以及数据字典四列里最容易被空着的那一列。","2026-11-09",18,{"path":1439,"title":1440,"description":1441,"date":1442,"series":1368,"seriesOrder":1443},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-17","软件分析与设计笔记 17：用生成式 AI 做设计与开发：能用的场景与要防的坑","生成式 AI 在工作流里真正站得住的三个场景 —— 造示例数据、解释陌生代码、起草初稿；它为什么会复现社会偏见、为什么会编得像真的，以及哪些内容绝不能直接采用。","2026-11-08",17,{"path":1445,"title":1446,"description":1447,"date":1448,"series":1368,"seriesOrder":1449},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-16","软件分析与设计笔记 16：从 persona 到 URS：一份交付物的完整骨架","把前面十几篇串成一份能交出去的东西：用户需求规格说明书的九个部分、每一章来自哪个环节、由谁确认，以及面向决策者的提案该讲清哪三件事。","2026-11-07",16,{"path":1451,"title":1452,"description":1453,"date":1454,"series":1368,"seriesOrder":1455},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-15","软件分析与设计笔记 15：非功能需求：把「快、稳、安全」变成数字","为什么「系统要快」不是一条需求：非功能需求怎么量化，决定性能的用户量、交易量、数据量三张表怎么估，安全需求要写清什么，以及可靠性的「不可用」该怎么定义。","2026-11-06",15,{"path":1457,"title":1458,"description":1459,"date":1460,"series":1368,"seriesOrder":1461},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-14","软件分析与设计笔记 14：软件测试：层级、方法与等价类划分","「我们测过了」这句话问题在哪：为什么静态评审和动态测试缺一不可，五个测试层级各自验证什么，白盒与黑盒的差别，以及用等价类划分和边界值把无限输入变成有限用例的方法。","2026-11-05",14,{"path":1463,"title":1464,"description":1465,"date":1466,"series":1368,"seriesOrder":1467},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-13","软件分析与设计笔记 13：需求获取：五种调查方法与一场访谈","需求从哪里来：五种事实调查方法各自适合回答什么、代价是什么，访谈从准备到记录的全过程，问卷设计里最容易犯的错，以及访谈记录必须写下来的那几项信息。","2026-11-04",13,{"path":1469,"title":1470,"description":1471,"date":1472,"series":1368,"seriesOrder":1473},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-12","软件分析与设计笔记 12：前端设计：把流程变成屏幕","界面设计不是「画得好看」：从活动图走到屏幕的三步，CRUD 任务流怎么当检查表，界面必须回答的三个问题，信息架构与五种导航类型，视觉一致性与留白，以及移动端与 Web 端的差别。","2026-11-03",12,{"path":1475,"title":1476,"description":1477,"date":1478,"series":1368,"seriesOrder":1479},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-11","软件分析与设计笔记 11：Agentic AI 与业务流程自动化","AI agent 到底是什么：五项能力与四个核心组件，什么场景该用定时任务、什么该用程序、什么才值得用 agent，两种实现路径的差别，必须提前防住的四类失败模式，以及设计方法从提示词到编排的演进。","2026-11-02",11,{"path":1481,"title":1482,"description":1483,"date":1484,"series":1368,"seriesOrder":1485},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-10","软件分析与设计笔记 10：精炼用例模型：include、extend 与泛化","三种精炼用例关系的判断标准：include 什么时候用、它和 extend 的区别到底在哪、参与者泛化与用例泛化各解决什么问题，以及精炼到什么程度就该停手。","2026-11-01",10,{"path":1487,"title":1488,"description":1489,"date":1490,"series":1368,"seriesOrder":1491},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-09","软件分析与设计笔记 09：功能需求（二）：从流程到业务实体","活动图画完之后，怎么从流程里筛出真正的业务实体：三条判断标准、哪些名词其实不该建实体、属性从哪里来，以及领域模型和后面的数据模型分别负责什么。","2026-10-31",9,{"path":1493,"title":1494,"description":1495,"date":1496,"series":1368,"seriesOrder":1497},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-08","软件分析与设计笔记 08：功能需求（一）：用例模型与活动图","从用户故事到用例的翻译过程：什么才算「好」的参与者，系统边界怎么划，用例描述必须写清哪四件事，活动图怎么把分支、并行与泳道画清楚，以及这两个图在 draw.io 里各用哪个形状、最容易画错哪一笔。","2026-10-30",8,{"path":1499,"title":1500,"description":1501,"date":1502,"series":1368,"seriesOrder":1503},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-07","软件分析与设计笔记 07：用户故事：把需求写成可验收的一句话","用户故事的三段结构，怎么从用户画像和数据地图里把它提取出来，一条故事多大算合适、时序为什么也是需求的一部分，以及验收标准怎么写才算真的可验收。","2026-10-29",7,{"path":1505,"title":1506,"description":1507,"date":1508,"series":1368,"seriesOrder":1509},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-06","软件分析与设计笔记 06：范围界定（数据侧）：用数据校验你的判断","访谈结论和数字对不上时该怎么办：分析的四个层次分别在回答什么问题、为什么不能跳级、数据准备与搜集怎么做成一张应用地图，以及数据最容易在哪几个地方骗你。","2026-10-28",6,{"path":1511,"title":1512,"description":1513,"date":1514,"series":1368,"seriesOrder":1515},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-05","软件分析与设计笔记 05：范围界定（用户侧）：用户画像与客户旅程图","为什么「所有用户」是一个陷阱，项目边界该怎么划，用户画像要写哪五个要素、最容易写成什么废纸，客户旅程图怎么把阶段、想法和情绪画在同一张纸上，以及怎么从这两样东西一步步推到可验证的需求。","2026-10-27",5,{"path":1517,"title":1518,"description":1519,"date":1520,"series":1368,"seriesOrder":1521},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-04","软件分析与设计笔记 04：接手陌生案例的第一步：三问与一张表","拿到一个完全陌生的业务案例，怎么从零开始不跑偏：只问 Who、What、Why 三个问题，把「问题—事实—假设—理由」记成一张四列表，说清为什么假设必须写明理由，以及带着问题进访谈为什么比空手去有效得多。","2026-10-26",4,{"path":1523,"title":1524,"description":1525,"date":1526,"series":1368,"seriesOrder":1329},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-03","软件分析与设计笔记 03：Agile 与 Scrum：迭代、角色与产出物","从「三周做完交付，客户说这不是我要的」讲起，说清瀑布式与迭代式的根本差别，Scrum 的三个角色和三种产出物，一个 sprint 里四次固定活动各自在解决什么问题，以及敏捷里最常见的几个误解。","2026-10-25",{"path":1528,"title":1529,"description":1530,"date":1531,"series":1368,"seriesOrder":1332},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-02","软件分析与设计笔记 02：设计思维、用户驱动与数据驱动","从「客服要一个能一次看完所有排期的界面」这个翻车案例讲起，说清设计思维到底是什么、用户驱动与数据驱动各自能看见什么又看不见什么、为什么真正的落点是把两者合起来的 data-informed design，以及访谈中怎么问出有用的信息。","2026-10-24",{"path":1533,"title":1534,"description":1535,"date":1536,"series":1368,"seriesOrder":1537},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-01","软件分析与设计笔记 01：课程地图与从需求到设计的主干","这一篇不写具体方法，先把整条链路铺开：软件分析与设计到底要解决什么问题，从需求到设计分成哪两段，中间有哪十几个环节，一份完整的交付物里应该有什么，以及后面 27 篇笔记怎么排、新人该按什么顺序读。","2026-10-23",1,{"path":1539,"title":1540,"description":1541,"date":1542,"series":1543,"seriesOrder":1537},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-01","Spring 学习笔记 01：IoC、依赖注入与第一个 Spring Boot 应用","从「手写 new 出来的三层结构」的痛点讲起，说清 IoC 容器、Bean 与依赖注入是怎么回事，再用 Maven 与 Spring Initializr 跑起第一个能返回 JSON 的应用。","2026-10-15","Spring 学习笔记",{"path":1545,"title":1546,"description":1547,"date":1542,"series":1543,"seriesOrder":1332},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-02","Spring 学习笔记 02：Spring Data JPA 基础——实体映射与 Repository","用 ORM 把类映射到表：@Entity、@Id、@GeneratedValue 怎么用，CrudRepository 三层接口怎么选，H2 与 MySQL 怎么配，以及 ddl-auto 到底该写哪个值。",{"path":1549,"title":1550,"description":1551,"date":1542,"series":1543,"seriesOrder":1329},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-03","Spring 学习笔记 03：JPA 查询——派生方法、JPQL 与分页排序","Repository 自带的方法只够按主键查，这篇讲三种自己造查询的方式：方法名派生、@Query 写 JPQL、原生 SQL，以及 Pageable 分页排序和投影返回。",{"path":1553,"title":1554,"description":1555,"date":1542,"series":1543,"seriesOrder":1521},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-04","Spring 学习笔记 04：JPA 关联映射与仓储测试","一对多、多对多怎么映射，谁负责维护外键，FetchType 与 CascadeType 该怎么选，顺带用 @DataJpaTest 与 AssertJ 把仓储层测起来。",{"path":1557,"title":1558,"description":1559,"date":1542,"series":1543,"seriesOrder":1515},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-05","Spring 学习笔记 05：Spring MVC 与 Thymeleaf 入门","一次页面请求在 Spring MVC 里走了哪几步，@Controller 怎么把数据交给模板，@PathVariable 与 @RequestParam 怎么取参数，以及控制器为什么必须是无状态的。",{"path":1561,"title":1562,"description":1563,"date":1542,"series":1543,"seriesOrder":1509},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-06","Spring 学习笔记 06：表单绑定与服务端校验","把表单字段绑到对象上，用 Bean Validation 注解做服务端校验，把错误信息显示回用户刚填的那一页，再补上自定义校验注解与全局异常处理。",{"path":1565,"title":1566,"description":1567,"date":1542,"series":1543,"seriesOrder":1503},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-07","Spring 学习笔记 07：Thymeleaf 片段复用与国际化","用 th:fragment 与 th:insert\u002Fth:replace 把导航栏、页脚抽成片段，页面套用统一布局，再用 messages.properties 与 LocaleResolver 做中英文切换。",{"path":1569,"title":1570,"description":1571,"date":1542,"series":1543,"seriesOrder":1497},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-08","Spring 学习笔记 08：HTTP Session 与会话状态管理","HTTP 无状态带来的问题，Cookie 与 Session 怎么分工，Spring 里读写会话的几种方式，以及会话固定、超时、多实例共享这些必须想清楚的事。",{"path":1573,"title":1574,"description":1575,"date":1542,"series":1543,"seriesOrder":1491},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-09","Spring 学习笔记 09：拦截器 HandlerInterceptor","登录检查、权限判断、请求耗时统计这类横切逻辑该放哪：HandlerInterceptor 的三个回调各在什么时机执行，怎么注册与排除路径，以及它和过滤器的分工。",{"path":1577,"title":1578,"description":1579,"date":1542,"series":1543,"seriesOrder":1485},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-10","Spring 学习笔记 10：Service 层与事务","一组数据库操作要么全成功要么全失败，靠 @Transactional 怎么做到；传播行为、隔离级别、回滚规则怎么选，以及自调用失效、乐观锁悲观锁这些实战里的坑。",{"path":1581,"title":1582,"description":1583,"date":1542,"series":1543,"seriesOrder":1479},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-11","Spring 学习笔记 11：REST API 基础——把 Service 暴露成接口","同一套 Service 怎么同时服务页面和客户端：@RestController 的路由与方法语义、状态码怎么给、DTO 怎么定，以及 MockMvc 端到端和切片测试的两种写法与各自的坑。",{"path":1585,"title":1586,"description":1587,"date":1542,"series":1543,"seriesOrder":1473},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-12","Spring 学习笔记 12：REST 进阶——版本、更新、跨域与文档化","接口能跑通之后要处理的几件事：URL 与响应结构怎么定、版本怎么演进、更新怎么做到可安全重试、CORS 怎么配、HATEOAS 与 Spring Data REST 该不该用。",{"path":1589,"title":1590,"description":1591,"date":1592,"series":1593,"seriesOrder":1521},"\u002Fposts\u002Fjava\u002Fjava-functionalprogramming-notes","Java 函数式编程笔记：Lambda、函数式接口与 Streams","学习 Java 函数式编程时的个人笔记：Lambda 表达式的五步简写与方法引用、常见函数式接口、Stream 流水线与惰性求值，以及创建流、中间操作、终止操作与收集器的用法。示例代码均为自写，运行结果来自本地实测。","2026-10-13","Java 学习笔记",{"path":1595,"title":1596,"description":1597,"date":1592,"series":1593,"seriesOrder":1329},"\u002Fposts\u002Fjava\u002Fjava-generics-notes","Java 泛型笔记：泛型方法、有界类型与泛型类","学习 Java 泛型时的个人笔记：从重复的重载方法与多态尝试讲起，再到泛型方法与类型推断、有界泛型类型、Comparable 与 Number 边界，以及泛型类在集合中的用法。示例代码均为自写，运行结果来自本地实测。",{"path":1599,"title":1600,"description":1601,"date":1602,"series":1603,"seriesOrder":1521},"\u002Fposts\u002Fmysql\u002Fmysql-learning-04","MySQL 学习笔记 04：数据操纵（增删改）","MySQL 学习笔记第四篇：INSERT 插入（含批量插入与 INSERT ... SELECT）、UPDATE 更新、DELETE 删除，以及各自的坑。","2026-10-10","SQL 学习笔记",{"path":1605,"title":1606,"description":1607,"date":1602,"series":1603,"seriesOrder":1515},"\u002Fposts\u002Fmysql\u002Fmysql-learning-05","MySQL 学习笔记 05：DDL 概览","MySQL 学习笔记第五篇：数据定义语言概览——Schema、Table、View、Index、Constraints 这几类对象分别对应哪些 CREATE \u002F ALTER \u002F DROP 命令。",{"path":1609,"title":1610,"description":1611,"date":1602,"series":1603,"seriesOrder":1509},"\u002Fposts\u002Fmysql\u002Fmysql-learning-06","MySQL 学习笔记 06：CREATE TABLE 与数据类型","MySQL 学习笔记第六篇：CREATE TABLE 的完整语法、常用数据类型的选型建议，以及外键的两种定义方式。",{"path":1613,"title":1614,"description":1615,"date":1602,"series":1603,"seriesOrder":1503},"\u002Fposts\u002Fmysql\u002Fmysql-learning-07","MySQL 学习笔记 07：索引","MySQL 学习笔记第七篇：索引的作用与 B+ 树结构、单列与复合索引的创建、索引的代价与适用场景。",{"path":1617,"title":1618,"description":1619,"date":1602,"series":1603,"seriesOrder":1497},"\u002Fposts\u002Fmysql\u002Fmysql-learning-08","MySQL 学习笔记 08：DROP 与 ALTER","MySQL 学习笔记第八篇：DROP 删除数据库对象、ALTER 修改表结构，以及常见的坑。",{"path":1621,"title":1622,"description":1623,"date":1602,"series":1603,"seriesOrder":1491},"\u002Fposts\u002Fmysql\u002Fmysql-learning-09","MySQL 学习笔记 09：约束与数据完整性","MySQL 学习笔记第九篇：SQL 约束——NOT NULL、CHECK、PRIMARY KEY、FOREIGN KEY 等如何保证数据完整性，以及它们的对比。",{"path":1625,"title":1626,"description":1627,"date":1602,"series":1603,"seriesOrder":1485},"\u002Fposts\u002Fmysql\u002Fmysql-learning-10","MySQL 学习笔记 10：视图","MySQL 学习笔记第十篇：视图（Views）的定义与特性、和表的对比、创建与使用，以及可更新视图的限制。",{"path":1629,"title":1630,"description":1631,"date":1602,"series":1603,"seriesOrder":1479},"\u002Fposts\u002Fmysql\u002Fmysql-learning-11","MySQL 学习笔记 11：存储过程","MySQL 学习笔记第十一篇：存储过程（Stored Procedures）的概念、创建与调用、带参数的写法，以及优缺点。",{"path":1633,"title":1634,"description":1635,"date":1636,"series":1603,"seriesOrder":1537},"\u002Fposts\u002Fmysql\u002Fmysql-learning-01","MySQL 学习笔记 01：MySQL 基础知识","MySQL 学习笔记第一篇：MySQL Server 与 Workbench 的安装，数据库与 DBMS 的基本概念，关系模型、ACID 事务、常见数据类型，以及后续的学习路线。","2026-10-09",{"path":1638,"title":1639,"description":1640,"date":1636,"series":1603,"seriesOrder":1332},"\u002Fposts\u002Fmysql\u002Fmysql-learning-02","MySQL 学习笔记 02：SELECT 查询与过滤","MySQL 学习笔记第二篇：SELECT 的基本语法与逻辑执行顺序，WHERE 过滤（比较、LIKE、IN、BETWEEN、NULL）、各种 JOIN、UNION 与子查询。",{"path":1642,"title":1643,"description":1644,"date":1636,"series":1603,"seriesOrder":1329},"\u002Fposts\u002Fmysql\u002Fmysql-learning-03","MySQL 学习笔记 03：聚合、分组与输出控制","MySQL 学习笔记第三篇：COUNT\u002FSUM\u002FAVG 等聚合函数、GROUP BY 分组、HAVING 过滤、ORDER BY 与 LIMIT，以及一条 SELECT 的完整逻辑执行顺序。",{"path":1646,"title":1647,"description":1648,"date":1649,"series":1593,"seriesOrder":1332},"\u002Fposts\u002Fjava\u002Fjava-oop-notes","Java 面向对象笔记：类、继承、多态与异常","学习 Java 面向对象编程时的个人笔记：类与对象、封装与访问修饰符、继承与多态、集合、抽象类与接口、异常处理。示例代码均为自写，运行结果来自本地实测。","2026-10-06",{"path":1651,"title":1652,"description":1653,"date":1654,"series":1593,"seriesOrder":1537},"\u002Fposts\u002Fjava\u002Fjava-basics-notes","Java 基础语法笔记：从环境安装到方法与数组","学习 Java 基础语法时的个人笔记：JDK 安装、输入输出、变量与运算符、条件与循环、字符串、数组与方法。示例代码均为自写，运行结果来自本地实测。","2026-10-05",{"path":1656,"title":1657,"description":1658,"date":1659,"series":1660,"seriesOrder":1537},"\u002Fposts\u002Fgit\u002Fgit-install-and-basics","Git 安装与基本使用","从装好 Git 到跑通日常提交流程，把最常用的几条命令和它们背后的三个区域讲清楚。","2026-10-04","Git 入门",{"path":1662,"title":1663,"description":1664,"date":1659,"series":1660,"seriesOrder":1332},"\u002Fposts\u002Fgit\u002Flocal-remote-repository","本地远程仓库的部署","用自己的机器当 Git 服务器：建一个裸仓库、走 SSH 连上去，把本地提交推上去。",{"path":1666,"title":1667,"description":1668,"date":1659,"series":1669,"seriesOrder":1537},"\u002Fposts\u002Fguide\u002Fguide-file-layout","文件都放在哪","文章、图片、封面分别放在仓库的什么位置，哪些目录可以放手改、哪些碰了会出问题。","博客使用指南",{"path":1671,"title":1672,"description":1673,"date":1659,"series":1669,"seriesOrder":1332},"\u002Fposts\u002Fguide\u002Fguide-frontmatter","frontmatter 每个字段是什么意思","文章开头那段被横线包住的字段，逐个说明作用、默认行为和写错的后果。",{"path":1675,"title":1676,"description":1677,"date":1659,"series":1669,"seriesOrder":1329},"\u002Fposts\u002Fguide\u002Fguide-markdown","正文怎么写 —— Markdown 与排版效果","标题、链接、图片、代码、表格分别怎么写，以及本站对它们做了哪些自定义处理。",1793268185526]