装饰器模式与代理模式比较

当有这样的业务需求的时候——要为写好的代码在目标代码之前或者之后添加部分操作时,此时最笨的方法就是直接在目标代码的前后加上我们需要的功能代码,但是这样违背了java封装的特性。更好一点的方法就是使用设计模式——代理模式,然而,装饰器模式也有同类的功能,那么着两种设计模式到底有什么区别呢?下面就分别来学习一下这两种设计模式。

装饰器模式类图如下:

该类图包括几个部分:一个接口(装饰器与需要被装饰的实体类都需要实现该接口,公用方法在该接口中定义),一个实现类,一个装饰器的接口,具体实现的装饰器。

在UML图中我们可以到,具体的实现类(需要被装饰的类,ConcreteComponent)与我们的装饰器(Decorator)都实现了相同的接口(Component)。

那么我们来看具体的装饰器模式的代码:

设计模式来源于生活,生活中就有这样的需求,商店每逢遇到不同的节日就会举行不同程度的促销活动,商品的价格也会有不同程度的折扣,假设商品时一个类,那么我们就需要不同的方法来返回商品在不同折扣下的价格了,这时就可以用到设计模式中的装饰器模式。

package DecorateModel;

public interface PriceComponent {
    public float getPrice();
}
package DecorateModel;

public class Good implements PriceComponent{
    
    private float price=0.0f ;//商品价格

    public Good(float price) {
        this.price = price;
    }

    @Override
    public float getPrice() {
        return price;
    }

    public void setPrice(float price) {
        this.price = price;
    }
}
package DecorateModel;
/**
 * 半折价格装饰类
 * @author Administrator
 *
 */
public class PriceHalfDecorate implements PriceComponent{
    
    private Good good ;
    
    public PriceHalfDecorate(Good good) {
        super();
        this.good = good;
    }
    
    @Override
    public float getPrice() {
        return this.good.getPrice()/2;
    }
}
package DecorateModel;

public class Price98Decorate implements PriceComponent{
    
    private Good good ;//需要包装的商品
    
    public Price98Decorate(Good good) {
this.good = good;
    }

    @Override
    public float getPrice() {
        return this.good.getPrice()*0.98f;
    }
}
package DecorateModel;

public class SaleActivity {
    
    public static void main(String[] args) {
        Good shoes = new Good(289f);
        //半价促销活动
        PriceHalfDecorate halfShoes = new PriceHalfDecorate(shoes);
        System.out.println("鞋子价格:"+halfShoes.getPrice());
        //98折促销
        Price98Decorate price98Shoes  = new Price98Decorate(shoes);
        System.out.println("98折鞋子价格:"+price98Shoes.getPrice());
    }
}    

从上面代码可以看出来,装饰器模式就是通过各种不同的装饰器来装饰我们的目标类,但是目标类和装饰器类都必须实现相同的接口,以便装饰器类知道需要装饰的是哪一个操作(接口中定义的操作,就是装饰器要装饰的操作)。装饰器在java设计中也比较常见,最常见的例如IO流的操作

代理模式的UML类图中可以看出来,代理类和被代理的类也都实现了同一个接口(Subject),接口中定义了需要被代理的方法,在代理类中存在一个被代理对象,并且他们之间的关系是编译时期就已经确定了。

package AgencyModel;

public interface Subject {
    public void sayLove();
}
package AgencyModel;

public class ShyBoy implements Subject{
    
    @Override
    public void sayLove() {
        System.out.println("小丽,我喜欢你呀!");
    }
}
package AgencyModel;

public class FriendProxy implements Subject{

    private Subject myFriend ;
    
    public FriendProxy() {
        myFriend = new ShyBoy(); 
    }

    @Override
    public void sayLove() {
        System.out.println("小丽,我是shyBoy的好朋友!shyBoy让我跟你说");
        myFriend.sayLove();
    }
}

上面的代码描述的就是一个代理的实际情况:一个很害羞的男孩(ShyBoy)非常喜欢小丽这位女孩子,但是有不好意思直接跟小丽表白,因此ShyBoy找到了他的好朋友FriendProxy,让好友代理自己跟小丽表白。因此ShyBoy,FriendProxy都有对小丽表白的方法sayLove(),因此将这个方法抽象到一个接口中,就是我们的Subject接口。因为ShyBoy与FriendProxy是相互认识的,所有只要存在FriendProxy时ShyBoy也一并存在(关系在编译期就确定,在代理类的构造方法中初始化被代理的对象)。

 

装饰器模式与代理模式的差异:

装饰器模式关注于在一个对象上动态的添加方法,然而代理模式关注于控制对对象的访问。换句话 说,用代理模式,代理类(proxy class)可以对它的客户隐藏一个对象的具体信息。因此,当使用代理模式的时候,我们常常在一个代理类中创建一个对象的实例。并且,当我们使用装饰器模 式的时候,我们通常的做法是将原始对象作为一个参数传给装饰者的构造器。

使用代理模式,代理和真实对象之间的的关系通常在编译时就已经确定了,而装饰者能够在运行时递归地被构造。   

 

转神人解释代理模式与装饰器模式:

代理模式
考虑现实生活中的代理商. 你想要进一批货,但是出于某种原因,你无法直接与生产商联系(有可能是因为你生产商与你相隔太远, 或者比如你进的货是军 火, 一般来说生产商也不会直接露面滴), 这时候你就需要一个代理商, 他能够接受你的订单, 并且也能给你需要的货品, 但是记住,代理商并不真正生产货品,他的能力在于他有办法从生产商那里给你搞到货品.

那么对于买家,也就是接口的调用者而言, 我并不关心你到底是代理商还生产商,我只要你能够跟我交易就可以. 从这角度理解的话,代理隔离了调用者和实现者直接的联系.

实际编码中的例子呢, 比如WebService的调用你就可以把他理解成一个(远程)代理.

装饰模式
语义上理解,装饰是什么呢? 装饰就是在原本的东西上添油加醋嘛. 装饰的原则就是,对于一个西瓜, 不管我怎么装饰,它始终都是一个西瓜, 我不能最终把它装饰一番之后当成土豆去卖.

举个例子,大家应该都买过那种现做的冰淇淋. 一般都是这样卖的, 普通的冰淇淋必选,上面可以加上各种葡萄干啊,榛子啊,蓝莓酱啊之类的,当然每加一样你就要多交一点钱啦:). 那针对这个给冰淇淋算价钱的问题, 写成代码呢, 差不多就是这样子的.
Java代码

//抽象冰淇淋
abstract class AbstractIceCream{
public abstract int getPrice();
}

//真正的冰淇淋
class IceCream extends AbstractIceCream{

public int getPrice(){
return 2; //原味冰淇淋只卖5块~~
}
}
//冰淇淋的巧克力装饰器
class ChocolateAdapter extends AbstractIceCream {
private AbstractIceCream iceCream; //to be adapted.
public ChocolateAdapter(AbstractIceCream iceCream){
this.iceCream = iceCream
}
public getPrice (){
return this.iceCream.getPrice()+3; //假设加一层巧克力要加3块钱好了~
}
}
//冰淇淋的蓝莓酱装饰器
class BlueberryAdapter extends AbstractIceCream {
private AbstractIceCream iceCream; //to be adapted.
public BlueberryAdapter(AbstractIceCream iceCream){
this.iceCream = iceCream
}
public getPrice (){
return this.iceCream.getPrice()+5; //假设加一层蓝莓酱要加5块钱好了~
}
}

//顾客来了
public class Client{
public static void main(String args[]){
//给我来个蓝莓冰淇淋
AbstractIceCream blueberryIceCream = new BlueberryAdapter(new IceCream());

//给我来个蓝莓巧克力冰淇淋~~
AbstractIceCream bb_ch_iceCream = new BlueberryAdapter(new ChocolateAdapter(new IceCream()));

//来了一个巧克力超级粉丝说,我要加3层巧克力~~
AbstractIceCream lot_of_chocolate_iceCream = new ChocolateAdapter(new ChocolateAdapter(new ChocolateAdapter(new IceCream())))

//然后算帐看看,你猜这些冰淇淋分别要多少钱呢...
println(blueberryIceCream.getPrice());
println(bb_ch_iceCream.getPrice());
println(lot_of_chocolate_iceCream.getPrice());

}
}

写 成这样我想大家应该能理解了? 再啰嗦两句,装饰器模式实际上在一定程度上解决了一个问题, 那就是类继承的局限性,类爆炸. 像上面的例子中,用最原始的集成方案的话,大概需要一下几个类: 冰淇淋, 巧克力的冰淇淋, 草莓的冰淇淋, 巧克力和草莓都有的冰淇淋...貌似还可以接受,但由于这些辅料是可以随意组合的, 那么比如我又新添了一个辅料香草,那我就又要新增 N个子类..., 学过组合数学的同学就会知道, 其实没学的也知道, 这样一来子类的生长速度可是相当客观的. 而使用装饰器的话, 新增一个辅料,我只需新增一个装饰器类即可, 省心啊...看起来程序员的生活又美好了不是么?

ok, 我保证这是最后一句啰嗦:
所谓模式,其实就是最佳实践的总结. 所以要学透模式,一定要联系现实生活,先弄清楚什么情况下需要使用这个模式,否则凭空想象的话,很容易混淆不说,还抓不住精要.

 

posted on 2014-09-09 22:36  狂野的小怪兽  阅读(830)  评论(0编辑  收藏  举报