设计模式:装饰模式

背景

有一个奶茶点餐系统,当顾客点完茶之后,计算出顾客需要支付的价格。

目前系统提供2种茶:苹果茶和芒果茶,系统实体关系如下:

(1)Tea:抽象父类,所有具体类都需要继承,提供2个抽象方法。

(2)AppleTea和MangoTea:具体类,实现description和price方法。

比如,当顾客点了苹果茶,系统调用AppleTea的price方法,计算出顾客需要支付10元。

为了给顾客更多选择,增加了2种配料:珍珠和椰果,顾客可以任意搭配。

现在请你重新设计系统:最终需要支付的价格 = 茶的价格 + 配料价格

第1版实现

分析需求可知,每种茶都可以选择配料,很明显这是一个通用行为,可以放在父类处理。

(1)在父类Tea中新增2个属性pearlQuantity和cocoQuantity,分别表示珍珠和椰果的份量。

(2)在父类Tea中实现price方法,计算配料的价格:

- (int)price {
    int totalPrice = 0;
    int pearlPrice = 2; // 珍珠价格
    int cocoPrice = 1; // 椰果价格
    totalPrice += pearlPrice * self.pearlQuantity;
    totalPrice += cocoPrice * self.cocoQuantity;
    return totalPrice;
}

(3) 重写子类AppleTea和MangoTea的price方法,在基础价格上加上配料价格。

确实满足了系统的要求,但仔细想想,这种设计可能会有哪些问题?

首先,配料价格的变化或新增了其他配料,都需要修改父类Tea的price方法,如果不小心修改出了问题,就会影响所有茶的价格。也就是说,虽然只是修改一个方法的代码,但是其影响面非常大,这也意味着系统的稳定性和扩展性很差

其次,如果把所有配料的计算逻辑都写到父类中,所有子类都会继承这些逻辑,但并不是所有的茶都适合加配料,比如咖啡就不适合加珍珠和椰果,造成一定的逻辑冗余问题

那是否有更好的设计方案呢?

第2版实现

分析问题发现,对于系统而言,配料的价格计算逻辑是一个变化点,可以应用封装变化原则,将配料价格的计算逻辑从父类剥离出来,用类进行抽象封装。

(1)TeaExt:配料父类,继承自Tea,提供统一的初始化方法,在指定的茶中加入配料。

// 将配料加入茶中
- (instancetype)initWithTea:(Tea *)tea {
    if (self = [super init]) {
        self.tea = tea;
    }
    return self;
}

(2)PearlExt和CocoExt:具体配料类,继承自TeaExt,重写price方法。

// 描述
- (NSString *)description {
    return [NSString stringWithFormat:@"%@ + 珍珠", [self.tea description]];
}

// 价格
- (int)price {
    // 茶的价格 + 配料价格
    return [self.tea price] + 2;
}

(3)顾客点茶:苹果茶 + 2份珍珠 + 1份椰果

- (void)test {
    Tea *tea = [AppleTea new]; // 点苹果茶
    tea = [[PearlExt alloc] initWithTea:tea]; // 加珍珠
    tea = [[PearlExt alloc] initWithTea:tea]; // 再加一份珍珠
    tea = [[CocoExt alloc] initWithTea:tea]; // 加椰果
    NSLog(@"点茶:%@,价格:%d", [tea description], [tea price]);
    // 点茶:苹果茶 + 珍珠 + 珍珠 + 椰果,价格:15
}

再来分析第1版实现中的问题:

首先,新增配料或修改配料价格,都不会影响其他配料的价格,系统的稳定性和扩展性好。

其次,茶和配料之间的搭配更加灵活,价格计算逻辑没有冗余。

这就是设计模式中的装饰模式。

装饰模式

装饰模式的意图:动态地给一个对象添加一些额外的职责。

装饰模式的结构:

(1)Component:抽象类,可以给这些对象动态地添加职责。

(2)ConcreteComponent:具体类,可以给这个对象动态地添加职责。

(3)Decorator:维持一个指向Component对象的指针, 并定义相同的接口。

(4)ConcreteDecorator:具体装饰对象,给Component对象添加职责。

对于点茶系统而言,Tea就是抽象类Component,AppleTea就是具体的ConcreteComponent,TeaExt就是抽象装饰类Decorator,CocoExt就是具体装饰类ConcreteDecorator,而price方法就是operation接口。

总结

从本文分析的点茶系统案例可以看出,继承的不合理使用可能影响系统的稳定性和扩展性。

所以在软件设计中有这样一条原则:合成复用原则

意思是:尽量使用组合或者聚合关系实现代码复用,少使用继承。

对于点茶系统而言,使用装饰模式将配料价格的计算逻辑通过组合的方式进行复用,而不是放在父类中通过继承的方式进行复用,从而降低代码的耦合性。

如果系统需要给单个对象添加职责,并且不影响其他对象,就可以考虑使用装饰模式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值