通过chatgpt练习设计模式--装饰者模式

接下来我们继续练习装饰者模式

好的,让我们继续练习装饰者模式。考虑一个简单的咖啡店场景,有不同种类的咖啡(例如浓咖啡、拿铁),以及可以在咖啡中添加不同的调料(例如牛奶、糖)。我们希望通过装饰者模式实现灵活地为咖啡添加调料。

请你设计相应的类和结构,并使用 Go 语言实现。在设计中要考虑以下几点:

  1. 定义一个 Coffee 接口,表示咖啡。
  2. 实现具体的咖啡类,例如 EspressoLatte 等,它们都实现了 Coffee 接口。
  3. 定义一个 Decorator 接口,表示调料。
  4. 创建具体的调料类,例如 MilkSugar 等,它们都实现了 Decorator 接口。
  5. 使用装饰者模式,在不改变咖啡和调料类的情况下,动态地为咖啡添加调料。

请开始设计和编码,如果有疑问或需要提示,请随时告诉我。

请给出编码

package main

import "fmt"

// Step 1: 定义 Coffee 接口
type Coffee interface {
    Cost() int
    Description() string
}

// Step 2: 实现具体的咖啡类
type Espresso struct{}

func (e *Espresso) Cost() int {
    return 5
}

func (e *Espresso) Description() string {
    return "Espresso"
}

type Latte struct{}

func (l *Latte) Cost() int {
    return 7
}

func (l *Latte) Description() string {
    return "Latte"
}

// Step 3: 定义 Decorator 接口
type Decorator interface {
    Cost() int
    Description() string
}

// Step 4: 创建具体的调料类
type Milk struct {
    coffee Coffee
}

func (m *Milk) Cost() int {
    return m.coffee.Cost() + 2
}

func (m *Milk) Description() string {
    return m.coffee.Description() + " with Milk"
}

type Sugar struct {
    coffee Coffee
}

func (s *Sugar) Cost() int {
    return s.coffee.Cost() + 1
}

func (s *Sugar) Description() string {
    return s.coffee.Description() + " with Sugar"
}

func main() {
    // 创建一杯 Espresso
    espresso := &Espresso{}

    // 添加 Milk 调料
    coffeeWithMilk := &Milk{coffee: espresso}
    fmt.Println("Cost:", coffeeWithMilk.Cost(), ", Description:", coffeeWithMilk.Description())

    // 添加 Sugar 调料
    coffeeWithSugar := &Sugar{coffee: espresso}
    fmt.Println("Cost:", coffeeWithSugar.Cost(), ", Description:", coffeeWithSugar.Description())

    // 组合调料,先加 Milk 再加 Sugar
    coffeeWithMilkAndSugar := &Sugar{coffee: coffeeWithMilk}
    fmt.Println("Cost:", coffeeWithMilkAndSugar.Cost(), ", Description:", coffeeWithMilkAndSugar.Description())
}

这段代码演示了咖啡和调料的组合,使用了装饰者模式。你可以通过组合不同的咖啡和调料来灵活地创建新的组合,而不需要修改原有的类。

这个代码扩展性如何

这段代码具有良好的扩展性,主要原因如下:

  1. 开闭原则(OCP): 通过接口和组合的方式,我们可以轻松地添加新的具体咖啡类或调料类而无需修改现有代码。新的类只需实现 Coffee 接口或 Decorator 接口即可。
  2. 灵活的组合: 由于咖啡和调料都实现了相同的接口,可以通过不同的组合方式创建各种不同口味的咖啡。比如,你可以先加入牛奶,再加入糖,或者先加入糖,再加入牛奶。
  3. 单一责任原则(SRP): 每个类都有清晰的职责,Espresso 和 Latte 负责定义咖啡,Milk 和 Sugar 负责定义调料,它们都遵循单一责任原则。

这使得代码易于理解、维护和扩展。如果需要引入新的咖啡类型或调料,只需创建相应的类并实现接口,而不会影响现有的代码逻辑。

这个代码还有什么缺陷

尽管这段代码在很大程度上符合设计模式的原则,但也存在一些潜在的缺陷:

  1. 硬编码价格: 在 main 函数中,每个咖啡和调料的价格都是硬编码的。这种情况下,如果价格发生变化,需要修改代码,不够灵活。
  2. 具体装饰者类数量增加: 如果有很多不同的调料,那么 Latte 和 Espresso 的构造函数可能会变得很冗长,不够直观。
  3. 组合的顺序问题: 装饰者模式的一个特点是装饰可以按照不同的顺序组合,但有时候这也可能导致一些意料之外的结果。在这个例子中,组合的顺序决定了最终的价钱。

这些问题可以通过进一步的改进来解决,比如使用配置文件管理价格、优化构造函数以提高可读性、考虑更复杂的计价策略等。

那么请你根据改进建议完善代码,给出最终代码

以下是根据之前的反馈改进的装饰者模式的Go代码:

package main

import "fmt"

// Component Interface
type Beverage interface {
	Cost() float64
	Description() string
}

// ConcreteComponent
type Espresso struct{}

func (e *Espresso) Cost() float64 {
	return 1.99
}

func (e *Espresso) Description() string {
	return "Espresso"
}

// ConcreteComponent
type Latte struct{}

func (l *Latte) Cost() float64 {
	return 2.49
}

func (l *Latte) Description() string {
	return "Latte"
}

// Decorator
type CondimentDecorator interface {
	Beverage
}

// ConcreteDecorator
type Milk struct {
	beverage Beverage
}

func NewMilk(beverage Beverage) *Milk {
	return &Milk{beverage: beverage}
}

func (m *Milk) Cost() float64 {
	return m.beverage.Cost() + 0.5
}

func (m *Milk) Description() string {
	return m.beverage.Description() + ", Milk"
}

// ConcreteDecorator
type Mocha struct {
	beverage Beverage
}

func NewMocha(beverage Beverage) *Mocha {
	return &Mocha{beverage: beverage}
}

func (m *Mocha) Cost() float64 {
	return m.beverage.Cost() + 0.75
}

func (m *Mocha) Description() string {
	return m.beverage.Description() + ", Mocha"
}

func main() {
	// Order a Latte with Milk and Mocha
	latte := &Latte{}
	latteWithMilkAndMocha := NewMocha(NewMilk(latte))

	fmt.Printf("Cost: $%.2f\n", latteWithMilkAndMocha.Cost())
	fmt.Printf("Description: %s\n", latteWithMilkAndMocha.Description())
}

改进的关键点:

  1. 引入 CondimentDecorator 接口,使得装饰者和具体组件都实现相同的接口。
  2. 使用 NewMilkNewMocha 函数来创建装饰者,提高可读性。
  3. 计价策略仍然保留在具体组件和具体装饰者中,可以根据实际需求进行修改。

这个设计更符合开闭原则,新的饮料和调料可以通过实现 Beverage 接口和 CondimentDecorator 接口来轻松扩展,而不需要修改现有代码。

计价策略仍然保留在具体组件和具体装饰者中,避免硬编码,请修改后给出完整代码

以下是经过改进的装饰者模式的Go代码,使用传入参数的方式将计价策略传递给具体组件和具体装饰者:

package main

import "fmt"

// Component Interface
type Beverage interface {
	Cost() float64
	Description() string
}

// ConcreteComponent
type Espresso struct {
	cost        float64
	description string
}

func NewEspresso() *Espresso {
	return &Espresso{cost: 1.99, description: "Espresso"}
}

func (e *Espresso) Cost() float64 {
	return e.cost
}

func (e *Espresso) Description() string {
	return e.description
}

// ConcreteComponent
type Latte struct {
	cost        float64
	description string
}

func NewLatte() *Latte {
	return &Latte{cost: 2.49, description: "Latte"}
}

func (l *Latte) Cost() float64 {
	return l.cost
}

func (l *Latte) Description() string {
	return l.description
}

// Decorator
type CondimentDecorator interface {
	Beverage
}

// ConcreteDecorator
type Milk struct {
	beverage    Beverage
	extraCost   float64
	extraDesc   string
}

func NewMilk(beverage Beverage, extraCost float64, extraDesc string) *Milk {
	return &Milk{beverage: beverage, extraCost: extraCost, extraDesc: extraDesc}
}

func (m *Milk) Cost() float64 {
	return m.beverage.Cost() + m.extraCost
}

func (m *Milk) Description() string {
	return fmt.Sprintf("%s, %s", m.beverage.Description(), m.extraDesc)
}

// ConcreteDecorator
type Mocha struct {
	beverage    Beverage
	extraCost   float64
	extraDesc   string
}

func NewMocha(beverage Beverage, extraCost float64, extraDesc string) *Mocha {
	return &Mocha{beverage: beverage, extraCost: extraCost, extraDesc: extraDesc}
}

func (m *Mocha) Cost() float64 {
	return m.beverage.Cost() + m.extraCost
}

func (m *Mocha) Description() string {
	return fmt.Sprintf("%s, %s", m.beverage.Description(), m.extraDesc)
}

func main() {
	// Order a Latte with Milk and Mocha
	latte := NewLatte()
	latteWithMilkAndMocha := NewMocha(NewMilk(latte, 0.5, "Milk"), 0.75, "Mocha")

	fmt.Printf("Cost: $%.2f\n", latteWithMilkAndMocha.Cost())
	fmt.Printf("Description: %s\n", latteWithMilkAndMocha.Description())
}

这样设计的优势在于,计价策略由调用者传递,使得具体组件和具体装饰者不再硬编码计价策略,而是接受调用者提供的计价信息。这样的设计更加灵活,支持更多变化。

算法基本型 文章被收录于专栏

算法基本型感悟

全部评论

相关推荐

点赞 收藏 评论
分享
牛客网
牛客企业服务