通俗易懂说java设计模式-装饰器模式

初学装饰器模式的人可能会觉得很混乱,很多文章只说代码怎么写,但不说为什么这么写。由此产生了很多疑问在:为什么装饰器里面已经有了被装饰的对象,还要继承抽象类或实现接口?不是说不要继承吗?这么做有什么意义?看完这篇文章你将知其然且知其所以然。

假设我们开了一家饭馆,刚开始我们只做两种food,分别是rice和noodle,不使用装饰器模式。

定义food抽象类

public abstract class Food {
    public String name;
    public abstract String getName();
    public abstract void cook();
}

 定义rice和noodle类

public class Rice extends Food {
    public Rice(){
        this.name="饭";
    }
    @Override
    public String getName() {
        return this.name;
    }
    @Override
    public void cook() {
        System.out.println("炒饭");
    }
}
public class Noodle extends Food {
    public Noodle(){
        this.name="面";
    }
    @Override
    public String getName() {
        return this.name;
    }
    @Override
    public void cook() {
        System.out.println("煮面");
    }
}

继承的问题:假设饭店引入新食材鸡蛋,所以也就有了新菜式蛋炒饭和蛋面。我们可以选择继承的方式,实现饭和面的两个子类蛋炒饭和蛋面。又过了两天,我们可能又要加菜品,加一个肉,新增四个菜品,炒肉饭、猪肉面、蛋肉炒饭和蛋肉煮面,一下子需要增加四个类的定义。随着新食材的加入,子类的数量将越来越庞大。

装饰器的思想:为了解决上述的问题,我们可能会这么想。有没有一种方式,使得我们可以灵活地根据需要对一个food进行改造。当引入一个新食材的时候,我们通过方法调用(构造装饰器对象)而不是定义一个新类,就可以对food进行改造成为一个新的food,并且我们希望这个改造是可以嵌套的。比如一个炒饭food,先用蛋进行改造成为蛋炒饭food,当我们引入肉的时候,又可以嵌套地用肉对炒饭和蛋炒饭进行改造。我们把上述的改造称为装饰,就是装饰器模式的思想。需要牢记的一点是,我们希望装饰器类创建的对象不是一个装饰器,而是经过装饰的food对象。

装饰器的实现原理:虽然我们不需要为每一个菜式定义一个新类,但是食材作为food的装饰器还是需要定义的。一个装饰器之所以要继承food类,是要保证装饰器通过装饰food而构造的新对象也是一个food。这么做的原因,首先从字面理解,装饰是很轻的动作,一个food装饰完当然应该还是food,其次这么做可以方便对food类的多态使用,并且是实现嵌套调用的关键。装饰器从代码层面来看,首先它要继承被装饰对象的抽象类或接口。继承food类后,我们还需要调用原food的方法和属性,才能够加以装饰,构造出装饰后的food方法和属性。这就是为什么我们要把整个被装饰对象传进来,装饰不是无中生有,而是在原先的基础上进行。如以下代码中,使用被装饰对象的cook方法来构造装饰后对象的cook方法,用原food的name构造装饰后的name。

public class EggDecoration extends Food {
    private Food food;
    public EggDecoration(Food food){
        this.food=food;
        this.name=food.name+"加蛋";
    }
    @Override
    public String getName() {
        return this.name;
    }
    @Override
    public void cook() {
        this.food.cook();//被装饰对象的cook方法
        System.out.println("煎鸡蛋");//装饰器在原对象基础上新增的装饰
    }
}
public class MeatDecoration extends Food{
    public Food food;
    public MeatDecoration(Food food){
        this.food=food;
        this.name=food.name+"加肉";
    }
    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public void cook() {
        this.food.cook();
        System.out.println("炒肉");
    }
}

 使用装饰器

public class Test {
    public static void main(String args[]){
        Rice rice=new Rice();
        EggDecoration eggRice=new EggDecoration(rice);//装饰后的对象不称为eggDecoration,因为装饰后仍然是food
        MeatDecoration meatEggRice=new MeatDecoration(eggRice);//同理,如此命名可读性更好
        System.out.println(meatEggRice.getName());
        meatEggRice.cook();
    }
}

运行结果

饭加蛋加肉
炒饭
煎鸡蛋
炒肉

上面的例子已经完成了装饰器模式,但是还存在改进的空间,可以定义装饰器的抽象类对装饰器进行规范 

定义装饰器的抽象类

public abstract class FoodDecoration extends Food {
    private Food food;
    public FoodDecoration(Food food) {
        this.food=food;
    }
    public String getName() {
        return this.name;
    }
    public void cook() {
        this.food.cook();
    }
}

装饰器对装饰器的抽象进行实现

public class EggDecoration extends FoodDecoration {
    private Food food;
    public EggDecoration(Food food){
        super(food);
        this.name=food.name+"加蛋";
    }
    @Override
    public void cook() {
        super.cook();
        System.out.println("煎鸡蛋");
    }
}
public class MeatDecoration extends FoodDecoration{
    public Food food;
    public MeatDecoration(Food food){
        super(food);
        this.name=food.name+"加肉";
    }

    @Override
    public void cook() {
        super.cook();
        System.out.println("炒肉");
    }
}

优点:使用装饰器模式后,我们无需为每一种food和新食材的组合预先定义菜品,我们根据需要通过装饰器就可以灵活地装饰出各种新food,大大减少了定义类的数量。装饰器模式还实现了装饰和food的解耦,在不修改原food或定义子类重写原food方法的前提下,通过装饰就可以实现类的功能拓展。

缺点:“套娃”现象严重,代码不美观。当装饰层数多时,内存占用较高。如本例中的蛋肉炒饭有三层装饰,每一层嵌套装饰,除了装饰本身是一个food之外,还需要在内部保存一个被装饰的food对象。为了创建一个蛋肉炒饭占用了三个food对象的空间(若用继承方法只需要一个food的内存占用)。


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值