zoukankan      html  css  js  c++  java
  • JAVA设计模式详解(三)----------装饰者模式

    今天LZ带给大家的是装饰者模式,提起这个设计模式,LZ心里一阵激动,这是LZ学习JAVA以来接触的第一个设计模式,也许也是各位接触的第一个设计模式。记得当初老师在讲IO的时候就提到过它:“是你还有你,一切拜托你。”没错,这就是装饰者模式最简洁的定义了。下面LZ引出标准的定义(出自百度百科):装饰模式是在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。

    ex:工厂制作上衣,白上衣是30,蓝上衣40,红上衣50,另外还有往衣服上绣的图案,花图案需10元,草图案需5元。现在工厂需要计算出成本价,要求设计出这个实现代码。

    我们初看这题有什么思路,难道要设计出5个类吗?这显然不行,如果工厂老板突然让加一个新的图案,岂不是“类爆炸”!这个时候,我们可能想到了用另一种实现方式,把CLothes做为基类,然后设置whiteClothes,

    blueClothes ,redClothes 为三个布尔类型变量,在基类里设置cost方法,然后图案设置为子类继承它,各自重写cost方法。这样就把原本的五个类缩小成了三个。
    //白上衣是30,蓝上衣40,红上衣50,另外还有往衣服上绣的图案,花图案需10元,草图案需5元
    public  class Clothes {
        private int cost;
        private boolean whiteClothes = false;
        private boolean blueClothes = false;
        private boolean redClothes = false;
        public  int cost(){
            if(whiteClothes){
                cost += 30;
            }
            
            if(blueClothes){
                cost += 40;
            }
            
            if(redClothes){
                cost += 50;
            }
            return cost;
        }
        
        public int getCost() {
            return cost;
        }
        
        public void setCost(int cost) {
            this.cost = cost;
        }
        
        public boolean isWhiteClothes() {
            return whiteClothes;
        }
        
        public void setWhiteClothes(boolean whiteClothes) {
            this.whiteClothes = whiteClothes;
        }
        
        public boolean isBlueClothes() {
            return blueClothes;
        }
        
        public void setBlueClothes(boolean blueClothes) {
            this.blueClothes = blueClothes;
        }
        
        public boolean isRedClothes() {
            return redClothes;
        }
        
        public void setRedClothes(boolean redClothes) {
            this.redClothes = redClothes;
        }
    }
    
    class FlowerPattern extends Clothes{
        public  int cost(){
            return 10+super.cost();
        }
    }
    
    class GrassPattern extends Clothes{
        public  int cost(){
            return 5+super.cost();
        }
    }

    测试方法

    public class TestDemo1 {
        public static void main(String[] args) {
            FlowerPattern flowerPattern = new FlowerPattern();
            flowerPattern.setBlueClothes(true);
            System.out.println("蓝上衣花图案一共花费:"+flowerPattern.cost());
            GrassPattern grassPattern = new GrassPattern();
            grassPattern.setRedClothes(true);
            System.out.println("红上衣草图案一共花费:"+grassPattern.cost());
        }
    }

     

    这种写法,相信看过LZ前两篇文章的人一眼就看出了问题,我们虽然写出了实现,但是由于类之间过于耦合,导致不利于维护,当我们添加一个新的颜色的上衣时,需要更改Clothes代码,这就违反了我们的设计原则:类应该对扩展开发,对修改关闭,也就是我们常说的开闭原则。在这里,LZ带着大家一起认识装饰者模式,我们以上衣为主体,以图案来“装饰”上衣。比方说,如果商家要蓝上衣花草图案,那么,我们要做的是:

    ①拿一个蓝色上衣对象

    ②以花图案对象装饰它

    ③以草图案对象装饰它

    ④调用cost()方法,并依赖委托将调料的价钱加上去

    下面我们画个图更加详细了解如何以装饰者构造衣服订单

    ①以BlueClothes对象开始,别忘了它继承自clothes,且有一个用来计算上衣价钱的cost()方法

                         

    ②建立一个花图案对象,并用它将BlueClothes对象包(wrap)起来。FlowerPattern对象是一个装饰者,它的类型"反映"了它所装饰的对象(本例中,就是Clothes)。所谓的"反映",指的是两者类型一致。所以FlowerPattern也有一个cost()方法。通过多态,也可以把FlowerPattern所包裹的任何clothes当成是clothes(因为FlowerPattern是clothes的子类)

     

    ③接下来还要建立草图案GrassPattern装饰者,并用它将FlowerPattern对象包起来。别忘了,BlueClothes继承自Clothes,且有一个cost()方法,用来计算衣服价钱

    ④这样,计算总价钱通过调用最外圈装饰者(GrassPattern)的cost()就可以办得到。GrassPatternd cost()方法会先委托它装饰的对象(也就是FlowerPattern)计算出价钱,然后再加上上衣的价钱。

    现在,LZ来总结一下我们目前所指的的一切:

    =。=装饰者和被装饰者对象有相同的基类。

    =。=我们可以用一个或多个装饰者包装一个对象。

    =。=既然装饰者和被装饰对象有相同的基类,所以在任何需要原始对象(被包装的)的场合,可以用装饰过的对象代替它。

    =。=装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定的目的,

    =。=对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象

    接下来我们先来看看装饰者模式的说明:装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

    下面我们来看下类图:

    到这里,大家可能会有些混淆:这里使用的是继承不是组合?。其实,这么做的重点在于,装饰者和被装饰者必须是一样的类型,也就是有共同的基类,这是相当关键的地方。在这里我们利用继承达到“类型匹配”,而不是利用继承获得“行为”。那么这个行为又来自哪里?当我们将装饰者与组件组合时,就是再加入新的行为,所得到的新行为,并不是继承自超累,而是由组合对象得来的。

    分支

    这里,为了防止各位看不懂图的结构,我们先切出一个分支来描述一下这张图,当然如果各位已经看懂,也可以直接跳过这段分支,直接看下面LZ对一开始那个问题的分析。

     1,Component接口可以是接口也可以是抽象类,甚至是一个普通的父类(这个强烈不推荐,普通的类作为继承体系的超级父类不易于维护)。

     2,装饰器的抽象父类Decorator并不是必须的。

                     那么我们将上述标准的装饰器模式,用我们熟悉的JAVA代码给诠释一下。首先是待装饰的接口Component。

    public interface Component {
    
        void method();
        
    }

     接下来便是我们的一个具体的接口实现类,也就是俗称的原始对象,或者说待装饰对象。

    public class ConcreteComponent implements Component{
    
        public void method() {
            System.out.println("原来的方法");
        }
    
    }

    下面便是我们的抽象装饰器父类,它主要是为装饰器定义了我们需要装饰的目标是什么,并对Component进行了基础的装饰。

    public abstract class Decorator implements Component{
    
        protected Component component;
    
        public Decorator(Component component) {
            super();
            this.component = component;
        }
    
        public void method() {
            component.method();
        }
        
    }

    再来便是我们具体的装饰器A和装饰器B。

    public class ConcreteDecoratorA extends Decorator{
    
        public ConcreteDecoratorA(Component component) {
            super(component);
        }
        
        public void methodA(){
            System.out.println("被装饰器A扩展的功能");
        }
    
        public void method(){
            System.out.println("针对该方法加一层A包装");
            super.method();
            System.out.println("A包装结束");
        }
    }
    public class ConcreteDecoratorB extends Decorator{
    
        public ConcreteDecoratorB(Component component) {
            super(component);
        }
        
        public void methodB(){
            System.out.println("被装饰器B扩展的功能");
        }
    
        public void method(){
            System.out.println("针对该方法加一层B包装");
            super.method();
            System.out.println("B包装结束");
        }
    }

    下面给出我们的测试类。我们针对多种情况进行包装。

    public class Main {
    
        public static void main(String[] args) {
            Component component =new ConcreteComponent();//原来的对象
            System.out.println("------------------------------");
            component.method();//原来的方法
            ConcreteDecoratorA concreteDecoratorA = new ConcreteDecoratorA(component);//装饰成A
            System.out.println("------------------------------");
            concreteDecoratorA.method();//原来的方法
            concreteDecoratorA.methodA();//装饰成A以后新增的方法
            ConcreteDecoratorB concreteDecoratorB = new ConcreteDecoratorB(component);//装饰成B
            System.out.println("------------------------------");
            concreteDecoratorB.method();//原来的方法
            concreteDecoratorB.methodB();//装饰成B以后新增的方法
            concreteDecoratorB = new ConcreteDecoratorB(concreteDecoratorA);//装饰成A以后再装饰成B
            System.out.println("------------------------------");
            concreteDecoratorB.method();//原来的方法
            concreteDecoratorB.methodB();//装饰成B以后新增的方法
        }
    }

    从这个例子中,各位应该对装饰者模式的结构有了一定的了解,那么接下来我们解决之前的那个问题。

    分支结束

    接下来我们开始看最开始的问题代码:

    先从clothes下手,这里我们加一个description 用来描述的更详细一下:

    public abstract class Clothes {
        String description = "unknown Clothes";
        
        public String getDescription(){
            return description;
        }
        
        public abstract double cost();
    }

    接下来是装饰者类,我们成为图案Pattern类,也继承自Clothes

     abstract class Pattern extends Clothes{
        public abstract String getDescription();
    }

    有了两个基类,我们为实现一些衣服,这里我们为了描述清楚,每个都写构造方法:

    class WhiteClothes extends Clothes{
         public WhiteClothes(){
             description = "WhiteClothes";
         }
        @Override
        public double cost() {
            
            return 30;
        }
         
     }
    
    class BlueClothes extends Clothes{
         public BlueClothes(){
             description = "BlueClothes";
         }
        @Override
        public double cost() {
            
            return 40;
        }
         
    }
    
    class RedClothes extends Clothes{
         public RedClothes(){
             description = "RedClothes";
         }
        @Override
        public double cost() {
            
            return 50;
        }
         
    }

    接下来我们开始写图案代码,也就是具体装饰者类,为了能够跟上思路,LZ标了注释

    class FlowerPattern extends Pattern{//花图案是一个装饰者,让它扩展自Pattern,而Pattern又扩展自Clothes
        //我们为了能让FlowerPattern能够引用clothes,用一个实例变量记录衣服,也就是被装饰者,然后在构造器中将其记录在实例变量里
        private Clothes clothes;
        public FlowerPattern(Clothes clothes){
            this.clothes = clothes;
        }
        @Override
        public String getDescription() {
            
            return clothes.getDescription() + ",FlowerPattern";//这里我们把图案也描述出来
        }
    
        @Override
        public double cost() {
            
            return 10+clothes.cost();//我们把调用委托给被装饰对象,以计算价钱,然后再加上FlowerPattern的价钱,得到最终结果
        }
        
    }

    同理,我们写出草图案的具体实现:

    class GrassPattern extends Pattern{//草图案是一个装饰者,让它扩展自Pattern,而Pattern又扩展自Clothes
        //我们为了能让FlowerPattern能够引用clothes,用一个实例变量记录衣服,也就是被装饰者,然后在构造器中将其记录在实例变量里
        private Clothes clothes;
        public GrassPattern(Clothes clothes){
            this.clothes = clothes;
        }
        @Override
        public String getDescription() {
            
            return clothes.getDescription() + ",GrassPattern";//这里我们把图案也描述出来
        }
    
        @Override
        public double cost() {
            
            return 5+clothes.cost();//我们把调用委托给被装饰对象,以计算价钱,然后再加上GrassPattern的价钱,得到最终结果
        }
        
    }

    最后,测试一下:

    public class TestDemo2 {
        public static void main(String[] args) {
            Clothes clothes = new BlueClothes();
            clothes = new FlowerPattern(clothes);
            clothes = new GrassPattern(clothes);
            System.out.println(clothes.getDescription()+"$"+clothes.cost());
        }
    }

    看到这里,LZ相信大家一定有些熟悉,记得我们在学习IO的时候曾学过包装流,

    其中FileInputStream是被装饰的“组件”。

    BufferedInputStream是一个具体的“装饰者”,它加入两种行为:利用缓冲输入来改进性能利用一个readLine()方法(用来一次读取一行文本输入数据)来增强接口。

    LineNumberInputStream也是一个具体的“装饰者”。它加上了计算行数的能力

    这里也引出了装饰者模式的一个缺点,常常造成设计中有大量的小类,数量实在太多,可能会造成使用此API程序员的困扰。但是如果我们已经了解了装饰者的工作原理,以后当使用别人的大量装饰者的API时,就可以很容易辨别出他们的装饰者类是如何组织的,以方便用包装方式去的想要的行为。

     装饰者模式就到此结束了,感谢大家的收看。

    下期预告,单例模式。

     

     

     

     

  • 相关阅读:
    Class文件和JVM的恩怨情仇
    详解及对比创建线程的三种方式
    浅析Java中线程组(ThreadGroup类)
    简单定义多线程!
    五分钟看懂UML类图与类的关系详解
    LeetCode刷题--14.最长公共前缀(简单)
    LeetCode刷题--13.罗马数字转整数(简答)
    动态规划算法详解及经典例题
    LeetCode--9.回文数(简单)
    LeetCode刷题--7.整数反转(简单)
  • 原文地址:https://www.cnblogs.com/ZhangHaoShuaiGe/p/7866610.html
Copyright © 2011-2022 走看看