zoukankan      html  css  js  c++  java
  • 设计模式:装饰者模式与组合模式

    装饰者模式

    概念

    动态地给一个对象添加一些额外的职责。与继承的模式对比,装饰者模式更为灵活。

    类图

    img

    以上共有四个角色:

    抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。

    具体构件(ConcreteComponent)角色:定义一个将要接收附加责任的类。

    装饰(Decorator)角色:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。

    具体装饰(ConcreteDecorator)角色:负责给构件对象“贴上”附加的责任。

    实例介绍

    火锅包括锅底与配菜,有很多的组合方式,适合用装饰者模式实现。

    1.定义被装饰对象基类GuoDi(可以是抽象类也可以是接口) 相当于Component

    package com.example.demo;
    
    public interface GuoDi {
    
        /**
         * 锅底的价格
         * @return
         */
        public float cost();
    
        /**
         * 锅底的名字
         * @return
         */
        public String name();
    }
    
    
    

    2.定义具体被装饰对象(也就是各种锅底,这里有两种) 相当于ConcreteComponent

    package com.example.demo;
    
    public class YuanYang implements GuoDi {
    
        /**
         * 锅底的价格
         *
         * @return
         */
        @Override
        public float cost() {
            return 50.0f;
        }
    
        /**
         * 锅底的名字
         *
         * @return
         */
        @Override
        public String name() {
            return "鸳鸯锅底";
        }
    }
    
    package com.example.demo;
    
    public class XiangLa implements GuoDi {
    
        /**
         * 锅底的价格
         *
         * @return
         */
        @Override
        public float cost() {
            return 40.0f;
        }
    
        /**
         * 锅底的名字
         *
         * @return
         */
        @Override
        public String name() {
            return "香辣锅底";
        }
    }
    
    

    3.定义装饰者抽象类PeiCai,相当于Decorator

    package com.example.demo;
    
    public class PeiCai implements GuoDi {
    
        private GuoDi guodi;
    
        public PeiCai(GuoDi guodi) {
            super();
            this.guodi = guodi;
        }
    
        /**
         * 锅底的价格
         *
         * @return
         */
        @Override
        public float cost() {
            return guodi.cost();
        }
    
        /**
         * 锅底的名字
         *
         * @return
         */
        @Override
        public String name() {
            return guodi.name();
        }
    }
    
    

    4.定义具体的装饰者对象(这里也是定义两个),相当于ConcreteDecorator

    package com.example.demo;
    
    public class MaLaNiuRou extends PeiCai {
    
        public MaLaNiuRou(GuoDi guodi) {
            super(guodi);
        }
        @Override
        public float cost() {
            return super.cost()+60f;
        }
        @Override
        public String name() {
            return super.name()+"+麻辣牛肉";
        }
    }
    
    
    package com.example.demo;
    
    public class DouFuPi extends PeiCai{
    
        public DouFuPi(GuoDi guodi) {
            super(guodi);
        }
    
        @Override
        public float cost() {
            return super.cost()+20.0f;
        }
        @Override
        public String name() {
            return super.name()+"+豆腐皮";
        }
    }
    
    

    5.创建一个测试类Test

    package com.example.demo;
    
    public class Test {
    
        public static void main(String[] args) {
            //点个香辣锅底
            GuoDi guodi = new XiangLa ();
            //来个麻辣牛肉
            MaLaNiuRou niurou = new MaLaNiuRou(guodi);
            //再来一个豆腐皮
            DouFuPi douFuPi = new DouFuPi(niurou);
            System.out.println("您点了"+douFuPi.name());
            System.out.println("共消费"+douFuPi.cost());
        }
    }
    
    

    基本思想

    1.编写一个类,实现与被装饰类相同的接口,目的是他们有相同的行为。

    我们做了MaLaNiuRou这个类,实现与被装饰类XiangLa 相同的接口GuoDi,目的是他们有相同的行为:cost(),name()。

    2.定义一个实例变量,引用被装饰的对象,目的与原来的老对象进行交接。

    也就是

    //点个香辣锅底
    GuoDi guodi = new XiangLa ();
    //来个麻辣牛肉
    MaLaNiuRou niurou = new MaLaNiuRou(guodi);
    

    3,定义构造方法。把被装饰的对象注入进来。

    public MaLaNiuRou(GuoDi guodi) {
            super(guodi);
    }
    

    4.对于不需要改写的方法,调用被装饰对象的方法。

    5.对于要改写的方法,改写即可。

    @Override
    public float cost() {
    return super.cost()+60f;
    }
    @Override
    public String name() {
    return super.name()+"+麻辣牛肉";
    }
    

    优点

    (1)装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。装饰模式允许系统动态决定“贴上”一个需要的“装饰”,或者除掉一个不需要的“装饰”。继承关系则不同,继承关系是静态的,它在系统运行前就决定了。
    (2)通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。

    缺点

    被装饰者一旦身份增加,作为装饰类,也需要相应的扩展,这必然造成编码的负担。

    这个个人觉得其实不算是缺点,与继承相比,装饰功能的细化必然会导致产生很多的装饰对象。鱼与熊掌不可兼得。

    混淆点

    与代理模式的区别

    对于装饰者模式来说,装饰者与被装饰者都实现同一个接口,对于代理模式来说,代理类与真实处理的类都实现同一个接口。而且都要把后者注入到前者。实现方式差别不大。

    但是其中的关注点是不一样的。

    代理模式 偏重因自己无法完成或自己无需关心,需要他人干涉事件流程,更多的是对对象的控制。
    装饰者模式 偏重对原对象功能的扩展,扩展后的对象仍是是对象本身。

    也就是思想的侧重点不一样。

    在我们实际业务中装饰模式使用的案例就是IO类,经常可以看到一个流包装之后再包装。而代理模式的使用就有Spring AOP和Spring 事务。不过它们使用的是动态代理。因为普通的代理例如上面的例子称为静态代理,只能代理某一类对象,但在我们实际业务中不可能为每个类都去写一个代理类,所以这个时候就出现了动态代理。动态代理是使用反射的方法实现的,只用传入类的类型就可以达到增强类的方法。

    应用场景

    IO类(上文提到),或者遇到需要动态添加职能的场景。

    组合模式

    概念

    组合模式对多个对象形成树形结构以表示具有“整体部分”关系的层次结构。组合模式对叶子对象和组合对象使用具有一致性。

    类图

    说明:

    Component:为叶子与容器的抽象。定义操作叶子的方法,比如增删改,获取叶子等另外还要定义叶子的行为。

    Leaf:叶子结点,没有子节点。实现叶子具体的行为。

    Composite:容器节点对象,也就是非叶子节点。内部有集合存储子节点。可以实现访问子节点的方法。在组合模式中,由于容器仍然包含容器容器,所以容器需要使用递归方法对子元素进行处理。

    示例介绍

    文件夹杀毒业务:

    常规实现

    package demo1;//为了突出核心框架代码,我们对杀毒过程的实现进行了大量简化
    
    import java.util.ArrayList;
    
    //图像文件类   
    class ImageFile {  
        private String name;  
      
        public ImageFile(String name) {  
            this.name = name;  
        }  
      
        public void killVirus() {  
            //简化代码,模拟杀毒   
            System.out.println("----对图像文件'" + name + "'进行杀毒");  
        }  
    }
    
    //文本文件类   
    class TextFile {  
        private String name;  
      
        public TextFile(String name) {  
            this.name = name;  
        }  
      
        public void killVirus() {  
            //简化代码,模拟杀毒   
            System.out.println("----对文本文件'" + name + "'进行杀毒");  
        }  
    }
    
    //文件夹类   
    class Folder {  
        private String name;  
        //定义集合folderList,用于存储Folder类型的成员   
        private ArrayList<Folder> folderList = new ArrayList<Folder>();  
        //定义集合imageList,用于存储ImageFile类型的成员   
        private ArrayList<ImageFile> imageList = new ArrayList<ImageFile>();  
        //定义集合textList,用于存储TextFile类型的成员   
        private ArrayList<TextFile> textList = new ArrayList<TextFile>();  
          
        public Folder(String name) {  
            this.name = name;  
        }  
          
        //增加新的Folder类型的成员   
        public void addFolder(Folder f) {  
            folderList.add(f);  
        }  
          
        //增加新的ImageFile类型的成员   
        public void addImageFile(ImageFile image) {  
            imageList.add(image);  
        }  
          
        //增加新的TextFile类型的成员   
        public void addTextFile(TextFile text) {  
            textList.add(text);  
        }  
              
    
      
        public void killVirus() {  
            System.out.println("****对文件夹'" + name + "'进行杀毒");  //模拟杀毒   
              
            //如果是Folder类型的成员,递归调用Folder的killVirus()方法   
            for(Object obj : folderList) {  
                ((Folder)obj).killVirus();  
            }  
              
            //如果是ImageFile类型的成员,调用ImageFile的killVirus()方法   
            for(Object obj : imageList) {  
                ((ImageFile)obj).killVirus();  
            }  
              
            //如果是TextFile类型的成员,调用TextFile的killVirus()方法   
            for(Object obj : textList) {  
                ((TextFile)obj).killVirus();  
            }  
        }   
    }
    

    测试类

    class Client {  
        public static void main(String args[]) {  
            Folder folder1,folder2,folder3;  
            folder1 = new Folder("Sunny的资料");  
            folder2 = new Folder("图像文件");  
            folder3 = new Folder("文本文件");  
              
            ImageFile image1,image2;  
            image1 = new ImageFile("小龙女.jpg");  
            image2 = new ImageFile("张无忌.gif");  
              
            TextFile text1,text2;  
            text1 = new TextFile("九阴真经.txt");  
            text2 = new TextFile("葵花宝典.doc");  
              
            folder2.addImageFile(image1);  
            folder2.addImageFile(image2);  
            folder3.addTextFile(text1);  
            folder3.addTextFile(text2);  
            folder1.addFolder(folder2);  
            folder1.addFolder(folder3);  
              
            folder1.killVirus();  
        }  
    } 
    
    

    该实现的弊端:

    1.文件夹类Folder在进行多个集合存储需要针对不同的成员进行增删改方法,比较冗余。
    2.客户端无法对容器和叶子进行统一的处理,因为没有进行抽象。

    3.如果增加新的叶子类型,比如视频文件,需要修改Folder类。

    用组合模式进行解决:

    import java.util.*;  
      
    //抽象文件类:抽象构件   
    abstract class AbstractFile {  
        public abstract void add(AbstractFile file);  
        public abstract void remove(AbstractFile file);  
        public abstract AbstractFile getChild(int i);  
        public abstract void killVirus();  
    }  
      
    //图像文件类:叶子构件   
    class ImageFile extends AbstractFile {  
        private String name;  
          
        public ImageFile(String name) {  
            this.name = name;  
        }  
          
        public void add(AbstractFile file) {  
           System.out.println("对不起,不支持该方法!");  
        }  
          
        public void remove(AbstractFile file) {  
            System.out.println("对不起,不支持该方法!");  
        }  
          
        public AbstractFile getChild(int i) {  
            System.out.println("对不起,不支持该方法!");  
            return null;  
        }  
          
        public void killVirus() {  
            //模拟杀毒   
            System.out.println("----对图像文件'" + name + "'进行杀毒");  
        }  
    }  
      
    //文本文件类:叶子构件   
    class TextFile extends AbstractFile {  
        private String name;  
          
        public TextFile(String name) {  
            this.name = name;  
        }  
          
        public void add(AbstractFile file) {  
           System.out.println("对不起,不支持该方法!");  
        }  
          
        public void remove(AbstractFile file) {  
            System.out.println("对不起,不支持该方法!");  
        }  
          
        public AbstractFile getChild(int i) {  
            System.out.println("对不起,不支持该方法!");  
            return null;  
        }  
          
        public void killVirus() {  
            //模拟杀毒   
            System.out.println("----对文本文件'" + name + "'进行杀毒");  
        }  
    }  
      
    //视频文件类:叶子构件   
    class VideoFile extends AbstractFile {  
        private String name;  
          
        public VideoFile(String name) {  
            this.name = name;  
        }  
          
        public void add(AbstractFile file) {  
           System.out.println("对不起,不支持该方法!");  
        }  
          
        public void remove(AbstractFile file) {  
            System.out.println("对不起,不支持该方法!");  
        }  
          
        public AbstractFile getChild(int i) {  
            System.out.println("对不起,不支持该方法!");  
            return null;  
        }  
          
        public void killVirus() {  
            //模拟杀毒   
            System.out.println("----对视频文件'" + name + "'进行杀毒");  
        }  
    }  
      
    //文件夹类:容器构件   
    class Folder extends AbstractFile {  
        //定义集合fileList,用于存储AbstractFile类型的成员   
        private ArrayList<AbstractFile> fileList=new ArrayList<AbstractFile>();  
        private String name;  
              
        public Folder(String name) {  
            this.name = name;  
        }  
          
        public void add(AbstractFile file) {  
           fileList.add(file);    
        }  
          
        public void remove(AbstractFile file) {  
            fileList.remove(file);  
        }  
          
        public AbstractFile getChild(int i) {  
            return (AbstractFile)fileList.get(i);  
        }  
          
        public void killVirus() {  
            System.out.println("****对文件夹'" + name + "'进行杀毒");  //模拟杀毒   
              
            //递归调用成员构件的killVirus()方法   
            for(Object obj : fileList) {  
                ((AbstractFile)obj).killVirus();  
            }  
        }  
    }
    

    客户端测试方法:

    class Client {  
        public static void main(String args[]) {  
            //针对抽象构件编程   
            AbstractFile file1,file2,file3,file4,file5,folder1,folder2,folder3,folder4;  
              
            folder1 = new Folder("Sunny的资料");  
            folder2 = new Folder("图像文件");  
            folder3 = new Folder("文本文件");  
            folder4 = new Folder("视频文件");  
              
            file1 = new ImageFile("小龙女.jpg");  
            file2 = new ImageFile("张无忌.gif");  
            file3 = new TextFile("九阴真经.txt");  
            file4 = new TextFile("葵花宝典.doc");  
            file5 = new VideoFile("笑傲江湖.rmvb");  
      
            folder2.add(file1);  
            folder2.add(file2);  
            folder3.add(file3);  
            folder3.add(file4);  
            folder4.add(file5);  
            folder1.add(folder2);  
            folder1.add(folder3);  
            folder1.add(folder4);  
              
            //从“Sunny的资料”节点开始进行杀毒操作   
            folder1.killVirus();  
        }  
    } 
    

    改进的效果

    由于1557889791317

    进行了抽象,所以能够针对不同的成员进行增删改方法。

    而且如果增加新的叶子类型,比如视频文件,不需要修改Folder类,只需要写一个实现AbstractFile接口的视频文件类就行了。而且各个容器相对独立,符合开闭原则,也方便管理。

    实现的基本思想

    1.定义抽象类或者接口。定义操作叶子的方法,比如增删改,获取叶子等另外还要定义具体的业务行为。

    2.分别实现叶子类与容器类,叶子类主要实现业务的行为。容器类内部维护一个抽象类的集合,并且通过这个集合实现增删改叶子。然后通过递归的方式便利集合实现业务行为。

    优点

    1.组合模式可以无差别的使用容器对象与叶子对象。

    2.新增容器与叶子都很友好。

    3.与常规的操作树的方式比,组合模式通过递归的方式,可以形成复杂的树形结构,但对树形结构的控制却非常简单。

    缺点

    新增构件只要实现抽象就能进入父构件中,不能进行约束,如果非要约束的话,需要在运行中进行类型检查来实现。

    应用场景

    业务场景中具有树形结构或者层次结构中,比如文件夹,企业的组织构成业务。

  • 相关阅读:
    [bzoj1113][Poi2008]海报PLA
    [CF1111D]Destroy the Colony
    [CF1111E]Tree
    [CF1111C]Creative Snap
    [洛谷P5136]sequence
    [洛谷P5190][COCI 2010] PROGRAM
    [洛谷P5137]polynomial
    US Open 2016 Contest
    【hackerrank】Week of Code 26
    usaco中遇到的问题
  • 原文地址:https://www.cnblogs.com/caobojia/p/10868435.html
Copyright © 2011-2022 走看看