1. 项目概述从一道题看C面向对象设计最近在辅导一些同学准备编程练习和面试时碰到一个挺有意思的题目就是“停车场收费问题”。题目本身描述很简单通常就是给定一些车辆进入和离开的时间记录然后根据一个计费规则计算出每辆车的停车费用。这种题目在C的练习和面试里很常见它看起来是个简单的计算题但如果你只把它当成一个数学题来做那就错过了它背后真正的价值。这道题实际上是一个绝佳的“麻雀”能让我们解剖C面向对象设计、数据结构选择、边界条件处理以及代码健壮性的方方面面。我见过很多初学者一拿到题目就开始埋头写main函数用一堆if-else和原始数组去硬算。代码写出来往往冗长、难以维护而且一旦计费规则稍微变复杂比如分时段计价、会员折扣、不同车型不同费率整个程序就得推倒重来。这显然不是我们学习C尤其是学习面向对象编程的初衷。这道题的核心是如何将现实世界中的实体车辆、停车场、计费规则和过程停车、计费抽象成计算机中的类和对象并设计它们之间的交互。这比单纯算出正确答案要重要得多。所以今天我们不只讲这道题“怎么做”我们重点聊聊“怎么设计”。我会用一个完整的、可扩展的C项目结构来拆解这个问题分享我在设计这类系统时的思路、踩过的坑以及如何让代码既清晰又强壮。无论你是正在刷题的学生还是希望巩固面向对象基础的开发者相信都能从中获得一些启发。2. 需求深度解析与核心设计思路2.1 题目背后的真实场景与需求挖掘我们先抛开“分数20”这个标签想想真实的停车场收费系统需要什么。题目给出的通常是简化的模型但我们的设计要预留扩展空间。核心需求可以拆解为以下几点车辆信息管理系统需要记录每辆车的唯一标识如车牌号、进入时间、离开时间。这是最基础的数据。计费规则引擎这是核心业务逻辑。规则可能包括首小时价格、后续每小时价格、是否有每日最高限额、是否区分白天夜间、是否有免费时长如15分钟内免费等。题目可能只给出一种简单规则但我们的设计应能容纳多种规则。停车记录处理系统需要处理车辆的“进入”和“离开”两种事件。当车辆离开时需要根据其进入时间和当前计费规则实时计算出费用。数据查询与统计可能还需要支持查询某辆车的停车记录、某个时间段的收入总额等。基于这些需求如果我们用面向过程的思维可能会写出一个充斥着复杂条件判断的巨型函数。而面向对象的设计则要求我们识别出系统中的“名词”和“动词”。名词类/对象Vehicle车辆、ParkingRecord停车记录、PricingRule计费规则、ParkingLot停车场管理系统。动词方法enter()进入、exit()离开、calculateFee()计算费用、addRule()添加规则。2.2 面向对象设计思路高内聚与低耦合我的设计思路是遵循“高内聚、低耦合”的原则。ParkingRecord类高内聚地封装一次停车行为的所有数据。它不应该知道如何计费只负责存储车辆ID、进入时间、离开时间并可能提供一个计算停车时长的方法。PricingRule抽象类/接口将计费规则抽象出来。这是实现低耦合的关键。我们可以定义一个PricingRule基类其中有一个纯虚函数calculateFee(const ParkingRecord record)。然后针对“简单按时计费”、“分段计费”、“每日封顶计费”等不同规则派生不同的子类如SimpleHourlyRule、TieredPricingRule。这样计费逻辑的变化就被隔离在规则类内部停车场管理系统完全不需要改动。ParkingLot类作为系统门面和控制器。它持有当前的计费规则一个PricingRule指针或引用并维护一个进行中的停车记录列表例如用std::mapstd::string, ParkingRecord以车牌号为键。它的enter和exit方法负责更新记录并在exit时调用当前规则计算费用。Vehicle类在这个简单模型中可能只是一个包含车牌号等信息的轻量类或者直接用std::string表示车牌。如果未来需要增加车型、车主信息等可以扩展此类。这样的设计当计费规则需要从“每小时5元”变成“前2小时5元之后每小时3元每天最高50元”时我只需要创建一个新的TieredDailyCapRule类并设置给ParkingLot其他所有代码都无需变动。这就是面向对象设计带来的可维护性优势。注意在小型题目或面试中如果时间极其紧张可以适当简化例如将规则以参数形式传入计算函数。但心中必须有这个设计蓝图并向面试官说明这能很好地体现你的软件设计能力。3. 核心类的详细实现与关键技术点3.1 时间处理std::chrono的精准与便捷处理时间是这个项目的一个关键点也是容易出错的地方。我强烈建议使用 C11 引入的chrono库它比传统的time_t和struct tm更类型安全、更强大。为什么不用ctimectime库处理本地时间和时区转换比较麻烦而且精度通常只到秒。std::chrono可以轻松处理毫秒、微秒并且通过system_clock能直接获取系统时间通过duration_cast可以方便地在不同时间单位间转换。如何表示一个时间点我们使用std::chrono::system_clock::time_point。在ParkingRecord类中可以这样存储#include chrono class ParkingRecord { public: using TimePoint std::chrono::system_clock::time_point; ParkingRecord(const std::string plateNumber, const TimePoint entryTime) : plateNumber_(plateNumber), entryTime_(entryTime), exitTime_() {} // 离开时间初始化为默认值 void setExitTime(const TimePoint exitTime) { exitTime_ exitTime; } // 计算停车时长返回以小时为单位的double方便计费 double getParkingDurationHours() const { if (exitTime_ TimePoint()) { // 未设置离开时间 // 可以抛出异常或返回0这里返回当前时长模拟进行中 auto now std::chrono::system_clock::now(); auto duration now - entryTime_; return std::chrono::durationdouble, std::chrono::hours::period(duration).count(); } auto duration exitTime_ - entryTime_; return std::chrono::durationdouble, std::chrono::hours::period(duration).count(); } private: std::string plateNumber_; TimePoint entryTime_; TimePoint exitTime_; // 使用默认构造的time_point表示“未设置” };如何获取当前时间在车辆进入或离开时调用std::chrono::system_clock::now()即可获得一个精确的time_point。实操心得在整个项目中统一使用std::chrono避免与C风格时间函数混用。将time_point的别名如using TimePoint ...放在一个公共头文件或类中确保整个项目类型一致。计算时长时使用duration_cast或直接利用duration的模板参数转换为所需单位如小时如上例所示。注意durationdouble, hours::period的用法它直接得到以小时为单位的浮点数包含小数部分对于精确计费至关重要。3.2 计费规则类的抽象与实现这是系统最灵活的部分。我们定义一个抽象基类#include memory class PricingRule { public: virtual ~PricingRule() default; // 根据停车记录计算费用返回费用单位元 virtual double calculateFee(const ParkingRecord record) const 0; }; using PricingRulePtr std::shared_ptrPricingRule; // 使用智能指针管理规则对象然后实现一个最简单的“首小时后每小时计费”规则这也是题目中最常见的类型class SimpleHourlyRule : public PricingRule { public: SimpleHourlyRule(double firstHourRate, double subsequentHourRate, double freeMinutes 0.0) : firstHourRate_(firstHourRate) , subsequentHourRate_(subsequentHourRate) , freeMinutes_(freeMinutes) {} double calculateFee(const ParkingRecord record) const override { double durationHours record.getParkingDurationHours(); double durationMinutes durationHours * 60; // 处理免费时长 if (durationMinutes freeMinutes_ 1e-6) { // 考虑浮点误差 return 0.0; } double totalFee 0.0; if (durationHours 1.0) { // 不超过1小时按首小时费率计算可能不是完整首小时费题目需明确 // 常见情况不足1小时按1小时算或者按比例计算。这里假设按首小时费率计。 totalFee firstHourRate_; } else { // 超过1小时首小时费用 后续小时费用 totalFee firstHourRate_; double extraHours durationHours - 1.0; // 后续小时通常不足1小时按1小时算这里使用ceil向上取整 int extraFullHours static_castint(std::ceil(extraHours - 1e-9)); // 减一个小量避免边界问题 totalFee extraFullHours * subsequentHourRate_; } // 确保费用非负 return std::max(totalFee, 0.0); } private: double firstHourRate_; // 首小时费用元 double subsequentHourRate_; // 后续每小时费用元 double freeMinutes_; // 免费时长分钟 };关键点解析浮点数比较durationMinutes freeMinutes_ 1e-6这是处理浮点数精度误差的常用技巧。永远不要直接写比较浮点数。计费逻辑的边界题目必须明确“不足1小时如何计费”。是“不足1小时按1小时算”还是“按分钟比例计算”上述代码实现了前者使用std::ceil。如果是后者计算会更简单总费用 首小时费 (时长-1)*后续小时费率但需要明确费率是每小时单价计算时需要按比例折算。向上取整的技巧std::ceil(extraHours - 1e-9)是为了防止当extraHours恰好是整数如2.000000时由于浮点误差被错误地向上取整为3。减去一个极小量可以避免这个问题。3.3 停车场管理系统的核心逻辑ParkingLot类将一切串联起来#include unordered_map #include vector #include iostream class ParkingLot { public: ParkingLot(PricingRulePtr rule) : currentRule_(std::move(rule)) {} // 车辆进入 bool vehicleEnter(const std::string plateNumber) { auto now std::chrono::system_clock::now(); // 检查车辆是否已在场内 if (activeRecords_.find(plateNumber) ! activeRecords_.end()) { std::cerr 错误车牌号 plateNumber 的车辆已在停车场内 std::endl; return false; } // 创建停车记录 activeRecords_.emplace(plateNumber, ParkingRecord(plateNumber, now)); std::cout plateNumber 于 timeToString(now) 进入停车场。 std::endl; return true; } // 车辆离开并返回费用 double vehicleExit(const std::string plateNumber) { auto it activeRecords_.find(plateNumber); if (it activeRecords_.end()) { std::cerr 错误未找到车牌号 plateNumber 的入场记录 std::endl; return -1.0; // 用负数表示错误 } auto now std::chrono::system_clock::now(); it-second.setExitTime(now); // 设置离开时间 double fee currentRule_-calculateFee(it-second); // 将记录移至历史 completedRecords_.push_back(it-second); activeRecords_.erase(it); std::cout plateNumber 于 timeToString(now) 离开停车场停车费用为 fee 元。 std::endl; return fee; } void setPricingRule(PricingRulePtr newRule) { if (newRule) { currentRule_ std::move(newRule); } } private: // 辅助函数将time_point转换为可读字符串仅用于演示 std::string timeToString(const ParkingRecord::TimePoint tp) { auto tt std::chrono::system_clock::to_time_t(tp); char buffer[80]; std::strftime(buffer, sizeof(buffer), %Y-%m-%d %H:%M:%S, std::localtime(tt)); return std::string(buffer); } private: PricingRulePtr currentRule_; // 当前计费规则 std::unordered_mapstd::string, ParkingRecord activeRecords_; // 进行中的记录 std::vectorParkingRecord completedRecords_; // 已完成的记录 };设计要点错误处理vehicleEnter和vehicleExit都有基本的错误检查重复进入、无记录离开并返回bool或特殊值来指示操作成功与否。在实际系统中可能需要更完善的异常机制。数据存储使用std::unordered_map来存储进行中的记录以实现O(1)时间复杂度的车牌号查找。历史记录用std::vector存储因为通常只需要顺序访问或遍历。规则管理通过智能指针PricingRulePtr持有规则对象使得规则可以动态更换并且内存管理更安全。4. 从搭建到运行一个完整的项目示例4.1 项目文件结构与编译环境一个清晰的项目结构有助于管理。假设我们创建一个名为ParkingFeeSystem的文件夹里面包含以下文件ParkingFeeSystem/ ├── include/ # 头文件目录 │ ├── ParkingRecord.h │ ├── PricingRule.h │ ├── SimpleHourlyRule.h │ └── ParkingLot.h ├── src/ # 源文件目录 │ ├── ParkingRecord.cpp │ ├── SimpleHourlyRule.cpp │ ├── ParkingLot.cpp │ └── main.cpp # 主程序包含测试用例 └── CMakeLists.txt # CMake构建脚本推荐为什么用CMakeCMake是一个跨平台的构建系统生成器。使用CMake你可以轻松地在Linux用Makefile、Windows用Visual Studio项目或macOS上构建你的项目而不需要为每个平台写不同的构建脚本。对于现代C项目它几乎是标配。一个简单的CMakeLists.txt可以这样写cmake_minimum_required(VERSION 3.10) project(ParkingFeeSystem) set(CMAKE_CXX_STANDARD 17) # 使用C17标准 set(CMAKE_CXX_STANDARD_REQUIRED ON) # 包含头文件目录 include_directories(${PROJECT_SOURCE_DIR}/include) # 添加可执行文件 add_executable(ParkingFeeSystem src/main.cpp src/ParkingRecord.cpp src/SimpleHourlyRule.cpp src/ParkingLot.cpp )在项目根目录下执行以下命令即可编译Linux/macOSmkdir build cd build cmake .. makeWindows上可以使用CMake GUI生成Visual Studio解决方案。4.2 主程序与综合测试在main.cpp中我们编写测试代码来验证整个系统的功能#include ParkingLot.h #include SimpleHourlyRule.h #include thread #include chrono int main() { // 1. 创建计费规则首小时5元后续每小时3元15分钟免费 auto rule std::make_sharedSimpleHourlyRule(5.0, 3.0, 15.0); // 2. 创建停车场系统并设置规则 ParkingLot parkingLot(rule); // 3. 模拟车辆进出 std::cout 停车场收费系统模拟开始 std::endl; // 车辆A停车70分钟超过1小时不足免费时长 parkingLot.vehicleEnter(京A12345); std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟时间流逝实际测试中应模拟或直接设置时间 // 注意实际测试中我们无法真的让程序睡眠70分钟。这里为了演示我们通常采用“模拟时间”或直接构造记录。 // 更实际的单元测试是直接创建带有特定进入、离开时间的ParkingRecord对象。 // 让我们换一种测试方式直接测试计费规则 std::cout \n--- 直接测试计费规则 --- std::endl; { auto entry std::chrono::system_clock::now(); auto exit entry std::chrono::minutes(70); // 70分钟后 ParkingRecord testRecord(测试车, entry); testRecord.setExitTime(exit); double fee rule-calculateFee(testRecord); std::cout 停车70分钟费用应为 5 1*3 8元计算得到 fee 元 std::endl; } { auto entry std::chrono::system_clock::now(); auto exit entry std::chrono::minutes(10); // 10分钟后在免费时长内 ParkingRecord testRecord(测试车, entry); testRecord.setExitTime(exit); double fee rule-calculateFee(testRecord); std::cout 停车10分钟在15分钟免费期内费用应为 0元计算得到 fee 元 std::endl; } { auto entry std::chrono::system_clock::now(); auto exit entry std::chrono::minutes(125); // 2小时5分钟 ParkingRecord testRecord(测试车, entry); testRecord.setExitTime(exit); double fee rule-calculateFee(testRecord); // 计费首小时5元剩余1小时5分钟按2小时算2*36元总计11元。 std::cout 停车2小时5分钟费用应为 5 2*3 11元计算得到 fee 元 std::endl; } // 4. 测试停车场管理流程 std::cout \n--- 测试停车场管理流程 --- std::endl; parkingLot.vehicleEnter(沪B88888); // 假设一段时间后... // 这里我们无法真实等待所以这个流程测试更依赖于对vehicleExit内部调用calculateFee的信任。 // 在实际项目中我们会用“模拟时间”或依赖注入的方式来测试ParkingLot。 double calculatedFee parkingLot.vehicleExit(沪B88888); // 这里exit时间就是当前时间 std::cout 流程测试完成计算费用为 calculatedFee 元 std::endl; // 5. 测试错误情况 std::cout \n--- 测试错误处理 --- std::endl; parkingLot.vehicleEnter(粤C66666); bool reEnter parkingLot.vehicleEnter(粤C66666); // 重复进入应失败 double invalidExitFee parkingLot.vehicleExit(不存在的车牌); // 无记录离开应返回错误 std::cout \n 模拟测试结束 std::endl; return 0; }测试要点单元测试思维将计费规则PricingRule的测试与停车场流程ParkingLot的测试分开。规则测试是纯逻辑测试不依赖时间流逝。模拟时间对于依赖时间的逻辑在单元测试中不应使用sleep而应直接构造具有特定时间点的对象。上面的测试展示了如何通过std::chrono::minutes来构造一个“未来”的离开时间。流程测试ParkingLot的流程测试主要验证车辆进入、离开的记录管理是否正确错误处理是否生效。计费是否正确则依赖于对PricingRule的单元测试。5. 进阶扩展与性能优化思考5.1 如何支持更复杂的计费规则我们的设计优势在于扩展性。假设现在需要支持“每日最高收费50元”的规则我们只需新增一个规则类class DailyCapRule : public PricingRule { public: DailyCapRule(std::shared_ptrPricingRule baseRule, double dailyCap) : baseRule_(baseRule), dailyCap_(dailyCap) {} double calculateFee(const ParkingRecord record) const override { double baseFee baseRule_-calculateFee(record); // 计算停车跨越的天数简化版按24小时为一天 double durationHours record.getParkingDurationHours(); int numberOfDays static_castint(std::ceil(durationHours / 24.0)); // 如果单日停车且基础费用超过日封顶则按封顶价计费 // 如果跨天则需要更复杂的按天拆分计算这里简化处理为总费用不超过天数*日封顶 double cappedFee std::min(baseFee, numberOfDays * dailyCap_); return cappedFee; } private: std::shared_ptrPricingRule baseRule_; // 基础计费规则 double dailyCap_; };这个DailyCapRule使用了“装饰器模式”Decorator Pattern。它内部包装了一个基础规则比如SimpleHourlyRule先按基础规则算出费用然后再应用日封顶逻辑。这样我们就能组合出非常复杂的规则例如“首小时后每小时3元但每天最高50元”而无需修改任何现有代码。// 组合规则首小时5元后续3元/小时每天封顶50元 auto baseRule std::make_sharedSimpleHourlyRule(5.0, 3.0); auto dailyCapRule std::make_sharedDailyCapRule(baseRule, 50.0); ParkingLot lot(dailyCapRule);5.2 数据持久化与查询功能一个完整的系统需要将停车记录保存下来。我们可以为ParkingLot增加文件读写功能。#include fstream #include sstream class ParkingLot { // ... 其他成员 ... public: bool saveRecordsToFile(const std::string filename) const { std::ofstream outFile(filename); if (!outFile.is_open()) { std::cerr 无法打开文件 filename std::endl; return false; } // 保存历史记录 for (const auto record : completedRecords_) { // 简单格式车牌,进入时间戳,离开时间戳,费用 // 时间戳可以用 time_since_epoch().count() 获取 auto entryTs std::chrono::duration_caststd::chrono::seconds(record.entryTime_.time_since_epoch()).count(); auto exitTs std::chrono::duration_caststd::chrono::seconds(record.exitTime_.time_since_epoch()).count(); double fee currentRule_-calculateFee(record); // 注意这里用当前规则重新计算历史记录最好直接存储费用。 outFile record.plateNumber_ , entryTs , exitTs , fee \n; } outFile.close(); return true; } void loadRecordsFromFile(const std::string filename) { std::ifstream inFile(filename); if (!inFile.is_open()) { std::cerr 无法打开文件或文件不存在 filename std::endl; return; } std::string line; while (std::getline(inFile, line)) { std::stringstream ss(line); std::string plate; long long entrySec, exitSec; double fee; char comma; if (ss plate comma entrySec comma exitSec comma fee) { ParkingRecord::TimePoint entryTp(std::chrono::seconds(entrySec)); ParkingRecord::TimePoint exitTp(std::chrono::seconds(exitSec)); ParkingRecord record(plate, entryTp); record.setExitTime(exitTp); completedRecords_.push_back(record); } } inFile.close(); } };注意事项存储费用时存在一个设计问题如果计费规则后来改变了重新加载历史记录并用新规则计算费用会得到与当初不同的结果。因此更合理的做法是在车辆离开、费用确定时就将最终费用存入记录对象并随记录一起持久化。这需要在ParkingRecord中增加一个fee_字段并在vehicleExit中赋值。时间戳的存储使用time_since_epoch()可以获取一个与时钟无关的、自纪元起算的时长便于序列化和跨平台。5.3 性能考量与数据结构选择查找效率我们使用std::unordered_map来管理进行中的车辆基于车牌号的查找是平均O(1)时间复杂度这对于停车场这种需要快速响应的场景是合适的。内存占用ParkingRecord对象存储了两个time_point和一个string。time_point通常很小可能是8字节。对于海量历史记录如果内存紧张可以考虑只将最近一段时间的记录留在内存更早的记录归档到数据库或文件中。并发安全如果这个停车场系统需要处理多个入口/出口的并发请求多线程那么ParkingLot类的方法就不是线程安全的。需要对activeRecords_和completedRecords_的访问加锁例如使用std::mutex。这是一个重要的进阶话题在面试中如果被问到“如何支持高并发”你可以从锁、并发数据结构或乐观锁的角度进行阐述。6. 常见问题排查与调试技巧在实际编码和调试过程中你可能会遇到以下问题6.1 时间计算总是为0或结果不对问题现象getParkingDurationHours()返回0或者计算出的费用与预期不符。排查步骤检查时间点赋值确保entryTime_和exitTime_被正确设置。在ParkingRecord构造函数和setExitTime方法中加入调试输出打印传入的time_point转换后的时间字符串。验证std::chrono::system_clock::now()在调用now()的地方打印一下看看获取的系统时间是否合理。注意system_clock可能受系统时间更改影响。检查时间差转换确认duration_cast或你的时长计算逻辑是否正确。一个常见的错误是直接对duration对象进行整数除法导致截断。务必使用浮点数类型来保存小时数如durationdouble, hours::period。浮点数精度如前所述比较和计算时考虑浮点误差。使用 (target epsilon)而不是 target。调试示例double getParkingDurationHours() const { auto duration exitTime_ - entryTime_; auto hours std::chrono::duration_caststd::chrono::hours(duration); auto minutes std::chrono::duration_caststd::chrono::minutes(duration % std::chrono::hours(1)); std::cout [调试] 时长分解: hours.count() 小时 minutes.count() 分钟 std::endl; return std::chrono::durationdouble, std::chrono::hours::period(duration).count(); }6.2 车辆重复进入或离开时程序行为异常问题现象同一辆车可以重复进入或者离开一辆不存在的车导致程序崩溃或返回错误值。解决方案我们的代码中已经在vehicleEnter和vehicleExit加入了查找检查。确保你的查找逻辑正确。unordered_map::find和erase的用法要准确。考虑更健壮的错误处理。例如vehicleExit返回-1表示错误调用者需要检查这个返回值。更好的方式是使用std::optionaldouble或者抛出异常如std::runtime_error来明确表示失败。6.3 计费规则变化时历史记录费用“变”了问题根源如5.2节所述历史记录没有存储“最终费用”而是每次用当前规则重新计算。解决方案 修改ParkingRecord类增加fee_成员变量和setFee/getFee方法。在ParkingLot::vehicleExit中计算完费用后调用record.setFee(fee)将其保存。这样无论计费规则如何变化历史记录的收费金额都是固定的、符合当时规则的结果。持久化时也保存这个fee_值。6.4 跨平台编译问题Windows/Linux/macOS常见问题时间函数使用std::chrono可以最大程度避免平台差异。但要注意std::localtime不是线程安全的Windows下某些版本可能是安全的但为了可移植性应使用线程安全的版本如localtime_r(Linux) 或localtime_s(Windows)。在我们的示例中timeToString函数仅用于演示在生产代码中应使用更安全的方式。文件路径Windows使用反斜杠\类Unix系统使用斜杠/。在代码中写文件路径时可以使用正斜杠/它在Windows上也通常被接受。或者使用std::filesystem::path(C17) 来处理路径它是跨平台的。编译器支持确保你的编译器支持C11/14/17标准。在CMake中设置set(CMAKE_CXX_STANDARD 11)。如果使用filesystem需要C17并链接stdcfs(GCC) 或cfs(Clang)。一个健壮的timeToString实现C11/14#include iomanip #include sstream std::string timeToString(const ParkingRecord::TimePoint tp) { auto tt std::chrono::system_clock::to_time_t(tp); std::tm tmBuf; // 使用线程安全版本 #ifdef _WIN32 localtime_s(tmBuf, tt); #else localtime_r(tt, tmBuf); #endif std::ostringstream oss; oss std::put_time(tmBuf, %Y-%m-%d %H:%M:%S); return oss.str(); }通过这样一步步的设计、实现、测试和问题排查我们不仅完成了一道编程题更构建了一个具备工业级设计雏形的迷你系统。这其中的面向对象思想、设计模式应用、时间处理、错误处理、测试方法才是这道“停车场收费问题”带给我们的真正价值。下次再遇到类似的业务逻辑题目不妨先停下来想想如何用良好的设计来优雅地解决它而不仅仅是写出正确的答案。