首页/新闻资讯/正文详情

类和对象练习:日期求解经典练习题的设计思路与实战拆解

发布时间:2026/9/26 9:13:56 来源:云帆数科 栏目:资讯中心
类和对象练习:日期求解经典练习题的设计思路与实战拆解
类和对象练习日期求解一道经典练习题背后的设计思路与实战拆解带过几期新人之后我发现一个很有意思的现象几乎所有面向对象入门教材都喜欢拿日期计算来当练习题。从最开始C语言里的输入年月日判断是第几天到Java课上的设计一个日期类再到Python里的datetime重造轮子日期这个题材几乎贯穿了每个程序员的入门阶段。我自己当年也被这道题折磨过很长时间。第一次用C语言写的时候一个函数搞定一切新增一个判断闰年的需求就把主函数改得面目全非后来用Java重新设计才意识到类和对象这两个概念不是语法规定而是为了解决真实问题自然长出来的组织方式。这篇博客我就从类和对象练习日期求解这个标题出发把这道经典题目从需求拆解、类设计、代码实现到调试心得完整走一遍希望对正在学面向对象的朋友们有点实质帮助。1. 日期题目为什么是类和对象练习的经典载体很多人不理解为什么偏偏是日期而不是学生成绩、图书信息、银行账户这些看起来更业务化的场景我自己的体会是日期这个题材有几个其他题材不具备的优点让它天然适合用来训练面向对象思维。1.1 日期数据自带属性行为双重结构日期本身就是一个领域模型——它既有静态的一面年月日三个基本字段又有动态的一面判断闰年、计算天数、比较先后、格式转换。这种静态属性动态行为的组合恰好对应了类的两个核心组成部分成员变量和成员方法。对比一下图书信息管理系统图书的属性确实很丰富但图书的行为往往是借书、还书这样的外部操作很难归属到图书对象本身。而日期则不同isLeapYear()、daysInMonth()、dayOfYear()这些操作天然就是日期对象自己的事——你问一个日期对象你是这一年的第几天它完全有能力自己回答。这种内聚性让初学者能直观感受到对象应该为自己负责的设计原则。1.2 从面向过程到面向对象的自然过渡绝大多数初学者接触Java或C之前在C语言课上已经写过输入年月日求这是第几天的题目。那时候的写法长这样int isLeapYear(int year) { return (year % 4 0 year % 100 ! 0) || (year % 400 0); } int dayOfYear(int year, int month, int day) { int days[] {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; if (isLeapYear(year)) { days[1] 29; } int sum 0; for (int i 0; i month - 1; i) { sum days[i]; } return sum day; }这个版本用三个参数把日期拆开了传。如果再加一个需求——求两个日期相隔多少天就又要写一个函数把年月日一个个传进去如果日期不合法调用方根本不知道得自己在调用前先做一次校验。你会发现日期这个实体被彻底拆成了散装数据代码一旦多起来维护就是一场灾难。面向对象解法完全不同。日期被封装成一个完整的对象年月日是它的内部状态判断闰年、求天数、比较先后是它的行为能力。调用方只需要构造一个日期对象然后问它你是这一年的第几天至于内部怎么算调用方根本不需要关心——这才是封装的意义。1.3 难度阶梯天然合理日期题目可以从简到难排出一个非常自然的梯度第一层静态方法版输入年月日返回天数还是面向过程第二层设计一个Date类属性和getter/setter第三层Date类提供dayOfYear()方法内部封装闰年判断和月份天数表第四层日期比较、日期差、格式化输出甚至自己实现一个简单的日期运算工具每一层都在前一层的代码结构上做增量改动初学者可以清楚看到加了封装之后调用代码变得更简洁了加了方法之后重复代码被消除了这些实在的好处。相比那些一上来就整一套图书管理系统的题目日期题目的认知负担小得多适合集中注意力理解核心概念。2. 需求拆解日期求解题到底要求什么2.1 典型的题目描述以我见过最经典的版本为例题目描述通常是这样设计一个日期类Date包含year、month、day三个私有属性提供以下功能判断某年是否为闰年求某年某月的天数求某个日期是这一年的第几天已知某日期和天数n求n天后的日期计算两个日期相差的天数不同教材会在这个基础上加减功能但核心就这几项。这个题目最关键的是第三问它要求学生把一年中每个月的天数闰年规则逐月累加这些逻辑合理组织起来。2.2 边界条件与隐含需求我把做这个题目时容易踩坑的点提前列出来基本都是测试用例里必考的内容闰年判断的边界1900年不是闰年整百但不被400整除2000年是闰年被400整除2024年是闰年被4整除但不被100整除月份天数表的正确性2月需要根据闰年动态变化12月的下一个月是1月但年份要加1日期合法性校验月份在1~12之间日期不能超过当月最大天数跨年计算比如2024年12月31日加1天应该是2025年1月1日这些边界条件看着简单但初次写代码的人十有八九会漏掉某一项。漏掉的原因一般不是不会写而是根本没意识到这是一个需要单独处理的情况——所以需求拆解这步一定要慢。2.3 算法选型查表法优于条件判断法求某日期是第几天的算法本质就是把1月到month-1月的天数全部累加再加上day。这里有一个常见的实现选择问题——是写一堆if-else判断每个月的天数还是用一个数组存储12个月的天数// 方案Aif-else版本不推荐 if (month 1) sum 0; else if (month 2) sum 31; else if (month 3) sum 31 28; else if (month 4) sum 31 28 31; // ... 越写越长 // 方案B查表法 int[] daysOfMonth {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; int sum 0; for (int i 0; i month - 1; i) { sum daysOfMonth[i]; } sum day;查表法的优势有几个表格本身就是领域知识的直接映射看一眼数组就能确认每个月天数对不对循环累加的逻辑完全一致不需要重复写12次更重要的是后续如果要支持某个月的第一个工作日最后一个周末这类计算表格还可以扩展成二维数组灵活性强得多。数组下标还有一个精妙之处daysOfMonth[0]对应1月daysOfMonth[11]对应12月这是教科书上很少明说但实际写代码时必须清楚的约定。很多人第一次写漏了一个1调试半天才发现是下标错位。3. Date类的整体设计成员变量、构造器与核心方法3.1 类的骨架设计基于前面拆解出的需求我推荐的Date类设计如下public class Date { private int year; // 年 private int month; // 月1~12 private int day; // 日1~当月最大天数 // 构造器 public Date(int year, int month, int day) { // 校验后赋值 } // getter方法 public int getYear() { return year; } public int getMonth() { return month; } public int getDay() { return day; } // 判断闰年 public boolean isLeapYear() { ... } // 获取某月的天数 public int daysInMonth() { ... } // 计算这一天是当年的第几天 public int dayOfYear() { ... } // 加上指定天数返回新对象不修改当前对象 public Date plusDays(int days) { ... } // 计算与另一个日期的相隔天数 public int daysBetween(Date other) { ... } // 格式化输出 Override public String toString() { ... } }有几个地方需要特别说明。属性必须是private这是封装的基本要求。很多人觉得反正我自己用public也行但养成private的习惯非常重要——等到代码规模变大你绝对不希望某个类的外部代码直接乱改内部字段那会导致同一份数据在不同地方被以不同方式修改bug排查起来非常痛苦。3.2 构造器中的参数校验防御性编程的第一课构造器是对象生命周期的起点。如果一个Date对象在创建的时候就不合法——比如month13或day32那后面所有计算都会跟着错。所以构造器内部必须做校验不合法就直接抛异常让错误尽早暴露。public Date(int year, int month, int day) { if (month 1 || month 12) { throw new IllegalArgumentException(无效的月份: month); } if (day 1 || day daysInMonth(year, month)) { throw new IllegalArgumentException(无效的日期: year - month - day); } this.year year; this.month month; this.day day; }这里有个先后顺序问题daysInMonth(year, month)需要依赖年份来判断闰年2月的天数所以校验月份必须在校验日期之前。这个顺序不能反。我见过很多学生的代码是先校验日期再校验月份一旦输入month13daysInMonth里的数组就越界了。3.3 闰年判断与月份天数两个底层工具的独立性isLeapYear和daysInMonth这两个方法是整个类的地基所有其他计算方法最终都要调用它们。我建议把它们设计成静态方法原因很简单判断某年是否闰年、某月有多少天这些结论不依赖于具体的某个日期对象只需要年份和月份数字就够了。静态方法更符合这个工具属性。闰年判断的规则一句话能被4整除但不能被100整除或者能被400整除。写成代码就是public static boolean isLeapYear(int year) { return (year % 4 0 year % 100 ! 0) || (year % 400 0); }注意括号的位置优先级高于||如果去掉括号写成year % 4 0 year % 100 ! 0 || year % 400 0结果其实一样但可读性差很多。在有经验的团队里可读性优先级非常高——代码是写给人读的顺便让机器能执行。月份天数表建议做成private static final常量数组放在类顶部private static final int[] DAYS_IN_MONTH { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };然后daysInMonth方法public static int daysInMonth(int year, int month) { if (month 2 isLeapYear(year)) { return 29; } return DAYS_IN_MONTH[month - 1]; }static final意味着这个常量数组在类加载时就已经存在且不可重新赋值这样既保证了数据安全又避免了每次调用方法时都重新创建数组的性能浪费。虽然对这道练手题来说性能差异微不足道但好的代码习惯都是这样一点点养成的。3.4 dayOfYear方法核心业务逻辑的三种写法dayOfYear是这道题的核心它求的是某个日期在这一年中排第几天。方法一循环累加最直观public int dayOfYear() { int sum 0; for (int i 1; i month; i) { sum daysInMonth(year, i); } return sum day; }方法二利用数组提前算好前缀和。比如PREFIX_SUM[i]表示前i个月的总天数这样dayOfYear就变成了一次数组访问private static final int[] PREFIX_SUM buildPrefixSum(); private static int[] buildPrefixSum() { int[] prefix new int[12]; int sum 0; for (int i 0; i 11; i) { sum DAYS_IN_MONTH[i]; prefix[i 1] sum; } return prefix; } public int dayOfYear() { return day PREFIX_SUM[month - 1] (month 2 isLeapYear(year) ? 1 : 0); }方法三直接用java.time里的LocalDate来实现一行搞定public int dayOfYear() { return LocalDate.of(year, month, day).getDayOfYear(); }三种方案的对比很有意思。方法一逻辑最清楚适合学习阶段。方法二效率最高但可读性差一些还引入了闰年且月份大于2时需要补一天这个容易被忽略的细节。方法三在实际项目中绝对是最佳选择因为JDK的LocalDate类经过了大量测试比自己造的轮子靠谱得多——但如果你在做作业老师大概率会要求不用LocalDate实现。我自己带新人时一般建议先写方法一确认全对之后再看方法二理解一下优化思路最后了解方法三的存在就行。核心不是写出多高级的代码而是把基础逻辑吃透。4. 关键算法详解日期合法性与天数计算的边界处理4.1 年份中0与1月的处理处理日期时新手最容易乱的地方是下标vs自然数的转换。月份从1开始1月1而数组下标从0开始daysInMonth[0]31对应1月。每一处从month取数组值时都需要month - 1。这一步错一个地方程序就会乱套。我把这类错误统称为差一错误。实际调试的时候可以打印每个中间变量的值比如计算2024年3月10日是第几天daysInMonth(2024, 1) 31daysInMonth(2024, 2) 29闰年sum 60加day10结果是第70天用简单数据手工推一遍很快就能定位问题。4.2 plusDays跨月、跨年的进位处理求n天后的日期比求第几天复杂不少因为涉及跨月和跨年的进位。我推荐的方式是先把当前日期转成该年的位置然后往后推进。但这里有一个坑——过了12月31日之后年份会变单纯用dayOfYear处理不了年份进位。一种稳妥的写法是先给月份和年份做累加然后循环修正public Date plusDays(int days) { if (days 0) { throw new IllegalArgumentException(days不能为负数当前实现暂不支持往前推); } int newYear year; int newMonth month; int newDay day; int remaining days; while (remaining 0) { int currentMonthDays daysInMonth(newYear, newMonth); if (newDay remaining currentMonthDays) { newDay remaining; remaining 0; } else { remaining - (currentMonthDays - newDay 1); newDay 1; newMonth; if (newMonth 12) { newMonth 1; newYear; } } } return new Date(newYear, newMonth, newDay); }这个算法的思路是逐月消化先看当前月还能容纳多少天如果剩余天数放得下就直接加放不下就把当前月剩下的天数全部消费掉然后进位到下个月再把剩余天数继续传递。每次进位后重新计算目标月的天数——因为不同月份天数不同必须循环动态计算。day remaining currentMonthDays这个判断是边界条件的关键。有人会写成那意味着恰好等于当月最大天数时也进位了比如1月31日加0天会变成2月1日这就错了。4.3 daysBetween两个日期相差天数的计算思路计算两个日期相差多少天最好的方式是借助基准日期的概念——定义一个基准点比如公元1年1月1日分别计算两个日期到基准点的总天数然后相减。要计算公元1年1月1日到某个日期的总天数核心是累计年份贡献的天数public long toDays() { long total 0; // 年份贡献 for (int y 1; y year; y) { total isLeapYear(y) ? 366 : 365; } // 月份贡献 for (int m 1; m month; m) { total daysInMonth(year, m); } // 日期贡献 total day; return total; } public int daysBetween(Date other) { return (int) Math.abs(this.toDays() - other.toDays()); }这个循环看起来很笨——如果日期跨越几千年循环次数会很多。但对于练习题来说完全够用而且逻辑一目了然。如果追求效率可以用数学公式直接计算每年贡献但那会引入复杂的常数计算反而降低了可读性。Math.abs取绝对值能保证不管调用顺序如何结果都是一致的非负数。在很多实际业务里你可能还需要负数来表示日期A在日期B之前那就应该直接返回this.toDays() - other.toDays()让调用方自己判断正负。5. 调试技巧与常见错误分析从程序跑不通到逻辑全对5.1 用测试驱动的方式验证逻辑自己写测试用例时我习惯准备两组数据第一组是可手工验证的基础用例确保基本逻辑正确日期预期结果说明2024-03-10第70天2024年是闰年1月31天2月29天10天702023-03-10第69天2023年不是闰年1月31天2月28天10天692024-12-31第366天闰年最后一天2023-12-31第365天平年最后一天第二组是随机但可验证的压力用例我会写一个辅助方法用LocalDate作为基准来对照校验public static void testRandomDates() { Random random new Random(); for (int i 0; i 10000; i) { int y random.nextInt(2000) 1; int m random.nextInt(12) 1; int d random.nextInt(daysInMonth(y, m)) 1; Date date new Date(y, m, d); int expected LocalDate.of(y, m, d).getDayOfYear(); if (date.dayOfYear() ! expected) { System.out.println(出错: y - m - d 期望 expected 实际 date.dayOfYear()); return; } } System.out.println(全部通过); }很多初学者把测试理解成随便打几个数看结果对不对。真正严谨的做法是构建这种对照验证让程序自己随机生成大量用例再和已知正确的实现做比对。这不仅是练习题的做法也是实际工作中测试代码的基本思路。5.2 典型错误TOP5与定位方法做这个题目常见的错误我按出现频率排个序错误1闰年判断规则记忆错误。最常见的是漏掉整百年必须被400整除这个条件把1900年误判为闰年。这类错误在手工测试时很难发现因为大家通常只测2020、2024这种普通年份。想避免就得把1900、2000这两个边界年份写进测试用例。错误2月份天数表下标偏移。把daysInMonth[9]当成9月的天数实际它存的是10月。定位方法很简单——算一个2024年5月1日的dayOfYear手算预期是312931301122如果程序输出132从4月以后的天数全错了基本就是下标多偏移了一位。错误3构造器中参数的赋值顺序。这看起来不像bug但真实会发生。有人先写this.month month再校验month是否在1~12范围结果校验失败时对象已经被部分赋值了。更隐蔽的版本是把入参和字段搞混比如public Date(int year, int month, int day) { this.year year; year this.month; // 手滑写反了 // ... }编译器一般会警告但有些伪装得很好编译器不报错运行时逻辑全乱。建议在构造器内部统一用this.xxx访问字段避免出现字段名和入参名视觉混淆。错误4plusDays方法忘记处理加到月末最后一天再进位的情况。我前面给出的算法用remaining 0作为终止条件天然包含了恰好落在月末最后一天的情况。如果自己写循环时用while (newDay remaining 30)这种魔法数字判断就会在31天的月份上翻车。错误5toString方法返回格式不统一。我们比较两个日期对象是否相等时如果先调用toString打印再肉眼对比很容易因为2024-3-5和2024-03-05的差异而误判。建议定义统一的输出格式Override public String toString() { return String.format(%d-%02d-%02d, year, month, day); }这样所有日期输出都是2024-03-05这种标准格式调试时做字符串对比也会方便得多。5.3 面向对象设计中一个深坑可变对象与别名问题在给Date类设计plusDays时我有一个重要决定不修改原对象而是返回一个新对象。为什么因为Date是一个值对象值对象的特征是状态不变操作产生新对象。这样设计有几个直接好处不会因为不小心修改了某个日期导致依赖它的其他变量全部出错多次调用plusDays不会产生累积副作用多个对象可以安全共享同一个日期实例对比一下// 错误示范修改自身 public void plusDays(int days) { // 修改 this.day, this.month, this.year } Date d1 new Date(2024, 1, 31); Date d2 d1; d1.plusDays(1); // 试图让d1变成2024-02-01 // 结果d1和d2同时都变成了2024-02-01d2的原日期丢了// 正确示范返回新对象 public Date plusDays(int days) { // 计算后在最后 return new Date(newYear, newMonth, newDay); } Date d1 new Date(2024, 1, 31); Date d2 d1.plusDays(1); // d1还是2024-01-31d2是2024-02-01这个设计原则叫不可变性在Java标准库的LocalDate中也是这么实现的。理解了这一点你就不会写出那种调一个方法把对象改了结果其他引用同一对象的变量全跟着变的诡异bug了。6. 题目之外的延伸将Date类用于实战的进阶方案6.1 用静态工厂方法代替构造器构造器有一个限制名字只能是类名。如果哪天你想提供从字符串解析日期的功能用构造器会非常别扭Date d new Date(2024-03-05); // 和之前的构造器签名完全不同但到底是哪个更好的方式是提供静态工厂方法public static Date of(int year, int month, int day) { return new Date(year, month, day); } public static Date parse(String text) { // 假设输入格式为 yyyy-MM-dd String[] parts text.split(-); int year Integer.parseInt(parts[0]); int month Integer.parseInt(parts[1]); int day Integer.parseInt(parts[2]); return new Date(year, month, day); }调用时一眼就能看出意图Date.of(2024, 3, 5)是直接指定年月日Date.parse(2024-03-05)是从文本解析。这种方法的可读性远远好于一堆重载构造器。Java标准库中的LocalDate.of()、LocalDate.parse()也都是这个模式。6.2 实现Comparable接口让日期可以排序如果是一组日期需要排序最优雅的方式是让Date类实现ComparableDate接口public class Date implements ComparableDate { // ... Override public int compareTo(Date other) { if (this.year ! other.year) { return Integer.compare(this.year, other.year); } if (this.month ! other.month) { return Integer.compare(this.month, other.month); } return Integer.compare(this.day, other.day); } }实现之后你就可以直接调用Arrays.sort(dates)对一组日期排序也可以把Date对象放进TreeSet等需要排序的集合里。这个扩展的代码量不大但能让学生实际感受接口在Java里扮演的角色——它不提供实现只约定行为让外部工具比如排序算法可以放心地对你的类做操作。6.3 与本地日期库的互操作在真实项目中我强烈建议直接使用java.time.LocalDate而不是自己写的Date类。但作为练习写完自己的Date类后可以加两个转换方法让它和Java标准库互相转换public LocalDate toLocalDate() { return LocalDate.of(year, month, day); } public static Date fromLocalDate(LocalDate localDate) { return new Date(localDate.getYear(), localDate.getMonthValue(), localDate.getDayOfMonth()); }这种做法在实际开发中也很常见——自己领域里的值对象和外部库的类型之间做一个适配层。通过这个练习你能体会到标准库不一定满足所有需求但留着转换通道总是好的这一层设计权衡。6.4 画一张类图帮助梳理关系很多教材讲到这里会提到UML类图。对这道题来说画类图最有价值的时刻是你已经写完代码之后再回头把类的关系梳理一遍。Date类的依赖关系比较简单Date依赖自身方法返回Date类型、接收Date参数Date依赖int的基本类型主程序依赖Date类构造对象并调用方法画类图时可以标注每个方法的可见性表示public-表示private和方法签名。这个习惯在团队协作中非常重要——大家看代码之前先看类图能快速理解整个模块的结构。7. 从练习到实战一个完整示例的代码总览最后给出一个完整的Date类参考实现Java版本包括前面讨论的全部核心功能。你可以在本地IDEA或VS Code中直接建立项目把代码粘贴进去运行。import java.time.LocalDate; public class Date implements ComparableDate { private final int year; private final int month; private final int day; private static final int[] DAYS_IN_MONTH { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; public Date(int year, int month, int day) { if (month 1 || month 12) { throw new IllegalArgumentException(无效的月份: month); } if (day 1 || day daysInMonth(year, month)) { throw new IllegalArgumentException(无效的日期: year - month - day); } this.year year; this.month month; this.day day; } public int getYear() { return year; } public int getMonth() { return month; } public int getDay() { return day; } public static boolean isLeapYear(int year) { return (year % 4 0 year % 100 ! 0) || (year % 400 0); } public static int daysInMonth(int year, int month) { return (month 2 isLeapYear(year)) ? 29 : DAYS_IN_MONTH[month - 1]; } public int dayOfYear() { int sum 0; for (int m 1; m month; m) { sum daysInMonth(year, m); } return sum day; } public Date plusDays(int days) { if (days 0) { throw new IllegalArgumentException(暂不支持负数天数); } int newYear year; int newMonth month; int newDay day; int remaining days; while (remaining 0) { int currentMonthDays daysInMonth(newYear, newMonth); if (newDay remaining currentMonthDays) { newDay remaining; remaining 0; } else { remaining - (currentMonthDays - newDay 1); newDay 1; newMonth; if (newMonth 12) { newMonth 1; newYear; } } } return new Date(newYear, newMonth, newDay); } public long toDays() { long total 0; for (int y 1; y year; y) { total isLeapYear(y) ? 366 : 365; } for (int m 1; m month; m) { total daysInMonth(year, m); } return total day; } public int daysBetween(Date other) { return (int) Math.abs(this.toDays() - other.toDays()); } Override public int compareTo(Date other) { if (this.year ! other.year) return Integer.compare(this.year, other.year); if (this.month ! other.month) return Integer.compare(this.month, other.month); return Integer.compare(this.day, other.day); } Override public String toString() { return String.format(%d-%02d-%02d, year, month, day); } public static Date parse(String text) { String[] parts text.split(-); return new Date( Integer.parseInt(parts[0]), Integer.parseInt(parts[1]), Integer.parseInt(parts[2]) ); } public LocalDate toLocalDate() { return LocalDate.of(year, month, day); } public static Date fromLocalDate(LocalDate ld) { return new Date(ld.getYear(), ld.getMonthValue(), ld.getDayOfMonth()); } }再附一个简单的主程序做验证public class Main { public static void main(String[] args) { Date d1 new Date(2024, 3, 10); System.out.println(d1 是 2024 年的第 d1.dayOfYear() 天); Date d2 d1.plusDays(300); System.out.println(d1 加上 300 天后是 d2); Date d3 new Date(2023, 12, 31); System.out.println(d1 和 d3 相差 d1.daysBetween(d3) 天); Date d4 Date.parse(2024-12-25); System.out.println(解析得到: d4 是第 d4.dayOfYear() 天); } }运行输出2024-03-10 是 2024 年的第 70 天 2024-03-10 加上 300 天后是 2025-01-04 2024-03-10 和 2023-12-31 相差 70 天 解析得到: 2024-12-25是第 360 天你可以手工验证一下2024年3月10日是第70天对吗1月31天2月29天10天70正确。2024年3月10日加300天3月剩21天到月底4月30天、5月31天……一路累计最后落在2025年1月4日这个结果我用LocalDate验证过正确。回头说一下自己在实际练习中的体会。日期求解这道题表面上是练类和对象实际上练的是三件更重要的事第一把领域知识闰年规则、月份天数准确转化成代码逻辑第二设计对象时想清楚状态归谁管、方法怎么分第三对边界条件的敏感度。这三点恰恰是日常写代码最常用的能力。如果你正在学面向对象不建议急着跳到Spring、MyBatis那些框架把这类基础题目做扎实后面遇到任何业务模型你都会具备一种本能——先拆属性再定行为最后用对象把它们组织起来。

相关推荐

SpringBoot+Vue图书馆座位预约系统实战:前后端分离与并发优化
SpringBoot+Vue图书馆座位预约系统实战:前后端分离与并发优化

简介:一套基于SpringBoot与Vue的图书馆座位预约系统完整项目包,面向Java开发者和信息化管理系统学习者,也可作为高校毕业设计参考。系统聚焦图书馆座位资源的数字化管理,融合人工智能理念,支持座位智能推荐与需求预测&… · 2026/9/26 9:13:56

KT106全向麦ENC+AEC升级实战:嵌入式音频系统级重构
KT106全向麦ENC+AEC升级实战:嵌入式音频系统级重构

1. 项目概述:为什么会议室里的“声音”比想象中更难搞你有没有经历过这样的会议现场:人声鼎沸,空调嗡嗡响,隔壁敲键盘像打鼓,投影仪风扇声此起彼伏——而你刚开口说“我们来看第三页PPT”,对方听到的却是“… · 2026/9/26 9:13:50

自建CRM实战:从零部署DeskcommCRM的完整指南
自建CRM实战:从零部署DeskcommCRM的完整指南

“DeskcommCRM”这个名字,我第一反应是“桌面通信客户关系管理”的结合体。后来我做了不少调研,发现这类自建CRM在小团队里越来越流行——比起动辄按坐席收费的SaaS软件,自己在一台云主机上跑一套开源CRM,数据完全握在自己手里&am… · 2026/9/26 9:13:50

【资讯热点】2026 AI 编程新范式:从 Copilot 到 Agentic Coding 的效率革命——TaoToken 统一 Key 接入实战
【资讯热点】2026 AI 编程新范式:从 Copilot 到 Agentic Coding 的效率革命——TaoToken 统一 Key 接入实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 11:04:16

Windows OpenClaw 2.7.9 可视化部署教程:TaoToken 统一 Key 接入本地 AI 自动化工具
Windows OpenClaw 2.7.9 可视化部署教程:TaoToken 统一 Key 接入本地 AI 自动化工具

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 11:04:16

一步步编写操作系统 76:用汇编语言编写字符打印函数,TaoToken 统一 Key 打通 AI 辅助调试链路
一步步编写操作系统 76:用汇编语言编写字符打印函数,TaoToken 统一 Key 打通 AI 辅助调试链路

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 11:04:16

【经验】Ubuntu 离线装 VsCode 插件读 Linux kernel 源码:TaoToken 统一 Key 配置骨架
【经验】Ubuntu 离线装 VsCode 插件读 Linux kernel 源码:TaoToken 统一 Key 配置骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 11:04:16

Claude Code官方桌面端正式发布,TaoToken统一Key接入配置指南
Claude Code官方桌面端正式发布,TaoToken统一Key接入配置指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 11:04:16

OpenClaw 源码解读第 5 章:Agent 抽象如何把大模型调用变成「人格化」助手
OpenClaw 源码解读第 5 章:Agent 抽象如何把大模型调用变成「人格化」助手

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 11:04:10

数据库课后习题答案别硬背:当测试用例集刷,效率翻倍
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍

简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21

OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40

向下兼容与向上兼容:接口设计中的兼容性策略与工程实践
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践

一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码