《软件开发沉思录:ThoughtWorks 文集》之对象健身操详解 是 ThoughtWorks 团队的一篇经典文章,通过形象的“健身操”比喻,讨论了面向对象设计中的一些重要原则。以下是该文中涉及的几个核心观点
1. 面向对象设计的“健身操”
面向对象设计像我们的身体一样,需要保持健康和灵活。我们要通过适当的设计“锻炼”对象,让它们能够灵活应对不断变化的需求。这就要求我们在设计时关注以下几点:
- 灵活性:对象之间的协作应该是灵活的,能够随着需求的变化而调整
- 高内聚,低耦合:每个对象应该有明确的职责,避免过多耦合
- 避免过度设计:设计时避免进行过多的抽象,保持简单易懂
2. 避免过度设计,保持简单
过度设计会导致系统过于复杂、难以维护。面向对象设计的目标应该是简洁、易理解的结构,而不是为了抽象而抽象
代码示例:
假设我们设计一个计算员工薪资的系统,过度设计可能导致不必要的继承和接口:
// 过度设计的例子:多层次的复杂抽象
type SalaryCalculator interface {
CalculateSalary() float64
}
type Employee struct {
Name string
Salary float64
}
func (e Employee) CalculateSalary() float64 {
return e.Salary
}
type Manager struct {
Employee
Bonus float64
}
func (m Manager) CalculateSalary() float64 {
return m.Salary + m.Bonus
}
优化后的设计:
// 简单的设计,避免过度抽象
type Employee struct {
Name string
Salary float64
}
func (e Employee) CalculateSalary() float64 {
return e.Salary
}
解释: 通过减少不必要的继承和接口,优化后的设计更简洁,易于维护和扩展
3. 小步快走,逐步改进
设计过程中,我们不应该一次性做出所有的设计决策。应该通过小步快走的方式,逐步完善系统,及时发现和修正问题
代码示例:
最初的价格计算:
package main
import "fmt"
func calculateTotalPrice(products []float64) float64 {
total := 0.0
for _, price := range products {
total += price
}
return total
}
func main() {
products := []float64{19.99, 25.99, 12.50}
total := calculateTotalPrice(products)
fmt.Println("Total Price:", total)
}
逐步加入折扣:
package main
import "fmt"
func calculateTotalPrice(products []float64, discount float64) float64 {
total := 0.0
for _, price := range products {
total += price
}
total -= discount // 加入折扣
return total
}
func main() {
products := []float64{19.99, 25.99, 12.50}
discount := 5.0
total := calculateTotalPrice(products, discount)
fmt.Println("Total Price after discount:", total)
}
进一步加入税费:
package main
import "fmt"
func calculateTotalPrice(products []float64, discount, taxRate float64) float64 {
total := 0.0
for _, price := range products {
total += price
}
total -= discount
total += total * taxRate
return total
}
func main() {
products := []float64{19.99, 25.99, 12.50}
discount := 5.0
taxRate := 0.08
total := calculateTotalPrice(products, discount, taxRate)
fmt.Println("Total Price after discount and tax:", total)
}
解释: 通过小步快走的方式,逐步添加新功能,保持系统的灵活性,避免一次性做出复杂的设计决策
4. 高内聚,低耦合
每个对象应该有明确的职责,避免承担过多的任务。通过高内聚、低耦合的设计,使得系统更加灵活、可维护
代码示例:
// 高内聚,低耦合的设计
type Order struct {
ID string
Amount float64
}
func (o Order) GetOrderDetails() string {
return fmt.Sprintf("Order ID: %s, Amount: %.2f", o.ID, o.Amount)
}
type Invoice struct {
Order Order
}
func (i Invoice) GenerateInvoice() string {
return fmt.Sprintf("Invoice for: %s", i.Order.GetOrderDetails())
}
func main() {
order := Order{ID: "123", Amount: 200.0}
invoice := Invoice{Order: order}
fmt.Println(invoice.GenerateInvoice())
}
解释: Order
类负责订单信息,Invoice
类负责生成发票。每个类的职责清晰,互不干扰,便于独立扩展和修改
5. 接口与实现分离
接口与实现的分离可以让我们更容易地替换实现,而不需要修改依赖接口的代码。通过这种设计,系统可以更加灵活
代码示例:
最初的邮件通知实现:
// 发送通知的接口
type Notifier interface {
Notify(message string)
}
// 邮件通知实现
type EmailNotifier struct{}
func (e *EmailNotifier) Notify(message string) {
fmt.Println("Sending email:", message)
}
// 使用通知的代码
func main() {
var notifier Notifier
notifier = &EmailNotifier{}
notifier.Notify("Hello, this is a test message!")
}
当我们需要添加短信通知时,只需新增一个新的实现:
// 短信通知实现
type SMSNotifier struct{}
func (s *SMSNotifier) Notify(message string) {
fmt.Println("Sending SMS:", message)
}
func main() {
// 切换不同的通知实现
var notifier Notifier
notifier = &SMSNotifier{}
notifier.Notify("Hello, this is a test SMS!")
}
解释: 接口 Notifier
允许我们在不修改原有代码的情况下,轻松替换通知的实现。这种设计使得系统更具灵活性和扩展性
6. 代码是“活”的
代码和系统的设计不是一成不变的,它们会随着需求的变化而逐步演化。我们需要保持代码的灵活性,并准备好随时调整和优化
代码示例:
初始版本的价格计算系统:
// 初始设计,计算商品总价格
type Product struct {
Name string
Price float64
}
func calculateTotal(products []Product) float64 {
total := 0.0
for _, product := range products {
total += product.Price
}
return total
}
func main() {
products := []Product{
{"Product A", 30.0},
{"Product B", 20.0},
}
fmt.Println("Total Price:", calculateTotal(products))
}
随着需求变化,我们逐步添加了折扣和税费功能:
第一步:加入折扣
func calculateTotalWithDiscount(products []Product, discount float64) float64 {
total := 0.0
for _, product := range products {
total += product.Price
}
total -= discount
return total
}
第二步:加入税费
func calculateTotalWithDiscountAndTax(products []Product, discount, taxRate float64) float64 {
total := 0.0
for _, product := range products {
total += product.Price
}
total -= discount
total += total * taxRate
return total
}
解释: 随着业务需求变化,我们逐步增加了折扣和税费的计算。这种“逐步演化”的设计能有效应对需求的变化,并保持代码的灵活性
总结:
通过上述的设计原则和代码示例,我们展示了如何通过适当的面向对象设计,使代码保持高内聚、低耦合、灵活、易维护。这些原则不仅有助于代码的清晰性和可扩展性,还能在面对需求变化时,保持系统的适应性
这些设计理念是面向对象编程的“健身操”,能够确保我们的系统像身体一样保持活力和健康