这是一道看起来很简单、实际重点在软件设计能力的题目。面试官并不关心日志格式、文件轮转或复杂配置,而是想看候选人能否设计出一个稳定、容易扩展的接口。
Implement a simple logger that can write messages to different destinations — a file or the console. It should be easily extensible to support new destinations without modifying the core logger logic.
实现一个简单的日志记录器,可以把消息写入不同位置,例如控制台或文件。
系统还需要方便扩展。未来增加数据库、网络服务或其他日志目的地时,不应该修改 Logger 的核心逻辑。
例如:
Logger:
ConsoleDestination
FileDestination
logger.log("Hello")调用一次 log 后,消息会同时发送到控制台和文件。
未来增加 DatabaseDestination 时,只需要实现新的目的地,不需要修改 Logger。
解题思路
Logger 不应该自己判断消息要写入控制台、文件还是数据库。它只负责保存一组日志目的地,并把收到的消息发送给它们。
所有目的地都遵循同一个接口,例如:
write(message)控制台目的地负责打印消息,文件目的地负责写入文件。Logger 只依赖这个统一接口,不依赖任何具体实现。
这样,Logger 的核心逻辑始终保持不变:
for each destination:
destination.write(message)新增日志目的地时,只需要创建一个实现相同接口的新类。这体现了一个很重要的设计原则:系统应该允许通过增加新实现来扩展功能,而不是不断修改已经稳定的核心代码。
关键追问
候选人一开始已经提出了正确的基本结构:Logger 内部保存一个 destinations 集合,每个 destination 自己实现 write 方法。
面试官随后向 destinations 中加入了一个普通对象。这个对象没有 write 方法,因此程序在真正发送日志时才出现运行时错误。
候选人最初考虑使用异常处理捕获错误,或者发现无效对象后将其删除。面试官继续追问:能否从设计上阻止这种错误,而不是等到发送日志时再处理?
更好的答案是定义统一的接口或抽象类。所有日志目的地必须实现 write 方法,不符合要求的对象不应该被当作合法 destination 使用。这样可以让错误更早暴露,也能让 Logger 的使用方式更加清晰。
容易出现的问题
一种常见写法是在 Logger 内部判断类型:
if destination is console
if destination is file
if destination is database这种实现暂时可以运行,但每增加一种目的地,都必须修改 Logger。系统规模扩大后,核心类会不断出现新的条件分支。
另一种问题是完全依赖动态调用,默认所有对象都有 write 方法。代码虽然简短,但错误通常要到运行时才会被发现。
异常捕获也不能替代良好的接口设计。异常处理可以决定某个目的地失败后是否继续写入其他目的地,但不能解决传入对象本身不符合约定的问题。
Csoahelp 提供技术面试实时文本辅助与 Mock Interview,帮助候选人在算法、系统设计和项目追问中更清晰地表达思路。
我们也有代面试,面试辅助,OA代写等服务助您早日上岸~

