装饰者模式(动态添加新功能)
场景:星巴克咖啡
装饰者模式
装饰者模式实现
package DecoratorMode.DecoratorMode1;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
abstract class Coffee{
protected int price;
protected String description;
abstract int cost();
abstract String getDescription();
}
class LongBlackCoffee extends Coffee{
LongBlackCoffee(){
super(5,"黑咖啡");
}
@Override
int cost() {
return super.price;
}
@Override
String getDescription() {
return super.description;
}
}
class ShortBlackCoffee extends Coffee{
public ShortBlackCoffee() {
super(4,"浓缩咖啡");
}
@Override
int cost() {
return super.price;
}
@Override
String getDescription() {
return super.description;
}
}
abstract class Decorator extends Coffee{
Coffee coffee;
}
class Chocolate extends Decorator{
Chocolate(Coffee coffee){
super.coffee=coffee;
price=3;
description=" 巧克力 ";
}
@Override
int cost() {
return price+coffee.cost();
}
@Override
String getDescription() {
return description+coffee.getDescription();
}
}
class Milk extends Decorator{
Milk(Coffee coffee){
super.coffee=coffee;
price=2;
description=" 牛奶 ";
}
@Override
int cost() {
return price+coffee.cost();
}
@Override
String getDescription() {
return description+coffee.getDescription();
}
}
public class Client {
public static void main(String[] args) {
Coffee coffee=new LongBlackCoffee();
coffee=new Milk(coffee);
coffee=new Chocolate(coffee);
System.out.println(coffee.getDescription());
System.out.println(coffee.cost());
}
}
其实本质上就是一个链表,每加入一个配料(装饰者),就相当于在使用头插法在链表的头部加入了一个结点
而计算费用的过程则是cost方法的递归调用
而如果从面向对象的角度来理解,所有的装饰者和主体都是继承了一个抽象类,还是拿咖啡举例,一开始是普通的咖啡,装饰后仍然是一杯咖啡,每一次装饰都是将一个咖啡进行包装,包装后仍然是一杯咖啡,这样就可以继续被装饰。计算费用时会从最外层开始递归调用cost方法,得到费用。
使用集合来代替这个隐式的链表
因为我们这个场景比较简单,所有装饰器对属性的处理都是一样的(做加法),如果是这样的话,我们可以使用集合类型来代替这个链表
@Data
@AllArgsConstructor
@NoArgsConstructor
abstract class Coffee{
protected int price;
protected String description;
}
class LongBlackCoffee extends Coffee{
LongBlackCoffee(){
super(5,"黑咖啡");
}
}
class ShortBlackCoffee extends Coffee{
public ShortBlackCoffee() {
super(4,"浓缩咖啡");
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
abstract class Decorator{
protected int price;
protected String description;
abstract int cost(int rawCost);
}
class Milk extends Decorator{
Milk(){
super(3,"牛奶");
}
@Override
int cost(int rawCost) {
return rawCost+price;
}
}
class Chocolate extends Decorator{
Chocolate(){
super(3,"巧克力");
}
@Override
int cost(int rawCost) {
return rawCost+price;
}
}
class Order{
Coffee coffee;
List<Decorator> decorators=new LinkedList<>();
Order(Coffee coffee){
this.coffee=coffee;
}
void addDecorator(Decorator decorator){
decorators.add(decorator);
}
int getCost(){
int cost=coffee.price;
for (Decorator decorator : decorators) {
cost=decorator.cost(cost);
}
return cost;
}
}
public class Client {
public static void main(String[] args) {
Order order=new Order(new LongBlackCoffee());
order.addDecorator(new Chocolate());
order.addDecorator(new Milk());
System.out.println(order.getCost());
}
}
我们直接使用List也可以达到类似的效果,并且也更好理解
对比两者
前者将前面装饰的结果作为一个整体来加上本次装饰
而后者是遍历每一个装饰者,轮流对主体进行处理
前者更加灵活,后者更加好理解。一般情况下两者方式都可以实现功能。
JDK源码在开发时,可能还没有util这个工具类,所以有时候会看到装饰者模式的影子,但在实际开发中,使用后者不仅功能可以达到,扩展性,耦合度都低于前者,开发时使用后者即可。
源码分析IO流
IO流是在JDK1.0引入的,而util包是在JDK1.2引入的,在开发IO流的时候还没有List集合,所以自然会使用我们的装饰者模式
我们既可以叫装饰,也可以叫它包装