精华内容
下载资源
问答
  • 主要为大家详细介绍了java设计模式组合模式Composite,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • 主要介绍了Java设计模式之责任链模式(Chain of Responsibility模式)介绍,本文讲解了如何使用责任链模式,并给出了4种使用实例,需要的朋友可以参考下
  • Java 设计模式——组合模式

    万次阅读 多人点赞 2016-05-17 17:39:45
    分类 >> 设计模式 目录 文章目录 概述 版权说明 目录 @[toc] 组合模式 定义 情境分析 一般组合模式 模式分析 逻辑实现 模式小结 组合与迭代 深搜的试水之行 迭代的逆转 Ref GitHub 源码下载 征集 组合模式...

    概述

    有时我们可能会被要求处理一个层级结构明显的对象,比如上下级的公司员工、比如层级嵌套的文件夹,还有丰富多彩的美食菜单。可是,我们可能要屡试不爽地编写深度搜索代码、要小心翼翼地编写递归逻辑。现在你可以忘掉这些,学习一些新的技能,让你秒刷副本。当然,这句有些夸张,你可以忽略。只是它单纯地表达我对本文要说的这个模式的喜欢(也有可能只是因为我工作中恰好遇到这个问题)。


    版权说明

    著作权归作者所有。
    商业转载请联系作者获得授权,非商业转载请注明出处。
    本文作者:Q-WHai
    发表日期: 2016年5月17日
    本文链接:https://qwhai.blog.csdn.net/article/details/51437883
    来源:CSDN
    更多内容:分类 >> 设计模式


    目录

    组合模式

    定义

    将对象组合成树形结构以表示**“部分-整体”**的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

    情境分析

    看到组合的模式的定义,或许你就知道了这里的组合与我们平常讨论的*“继承-组合”*并不是同一个概念。因为在学习组合模式之前,我就是把这两个弄得有点混淆,所以一直没有认真地学习过它,以为组合也就是那么回事了嘛。可是,当我开始真的学习它的时候,才知道,这是两回事。
    定义上说的是部分与整体的层次结构,可能就这前半句还不能说明什么,不过我们可以从后半句中找到突破点,那就是单个对象与组合对象。也就是在组合对象中组合了一个部分的集合,这个集合中的元素也就是单个元素所具有的对象类型。
    当然,你不要理解成组合模式里也可以只有这个集合了。如果是这样,那么一个 List 完全可以可以搞定,又何必搞出个模式来突显逼格呢?
    现在我们就来“举个栗子”,对,是栗子。哈哈~
    我现在是一家中式餐厅的老板了。在我的店里你可以享用早餐、中餐、晚餐。早餐就简单点,只提供包子、油条跟豆浆吧(是的,这个不是主业)。午餐会丰盛一些,在午餐里你们选择北方菜或是南方菜。北方菜主要有:锅包肉、猪肉炖粉条、辣子鸡、干炸里脊、糖醋里脊、板栗烧鸡、地三鲜、红烧肉、回锅肉、 口水鸡、宫保鸡丁、可乐鸡翅;南方菜主要有:炒米粉、南瓜饼、南焖羊肉、蒸排骨、肉片炒青椒、水果皮萨、北炒鱼香茄子、糯米糍团、芥菜煲。晚餐上可以吃得精致一些,主要有地道小吃和甜点。地道小吃:肉夹馍、羊肉泡馍、乌冬面、章鱼小丸子、葱油饼、老婆饼;甜点:冰淇淋、鲜奶蛋糕还有蜜汁藕。
    把上面的描述转换成图片菜单就像下面这样的:

    这里写图片描述

    当然,这里并不够齐全,只是为了照顾图片的大小(当然也不可否认是博主偷懒了),只绘制了其中的一部分,不过足够我们说明问题了。

    一般组合模式

    模式分析

    在一般组合模式里,我们只做了一件事,那就是模糊了简单元素与复杂元素。怎么说明这一点呢?针对上面举的例子来说,我们的每一种菜都是一个简单元素,而每一种菜系(南方/北方/小说/甜点)或是餐饮的类型(早餐/午餐/晚餐)都是一个复杂元素,因为这里又包含若干的简单元素。我们把菜系定义为 Menu,而每一道具体的菜则定义成 MenuItem。这样我们就可以绘制出一般组合模式的类图,如下:

    这里写图片描述

    这里面我们的菜单(Menu)和菜单项(MenuItem)都继承自一个 MenuComponent。MenuComponent 是一个抽象的类。当 Menu 和 MenuItem 继承自同一个类时,我们就可以实现模糊简单元素与复杂元素了,因为我们可以按照处理 MenuComponent 的方式处理 Menu 和 MenuItem。这里我们只是模糊了这两个的分界,却不能真的等同看待。很简单,上面的的 MenuItem 最起码有一个价格的属性、而 Menu 就不存在这个属性;Menu 可以有一个 add MenuComponent 的方法,而 MenuItem 则不可能会有。
    说到这里,可能你会说组合模式并不完美。是的,我也这么觉得。它让这件事情模糊了,让 MenuComponent 的使用产生了歧义。比如我们在使用它的时候,根本不知道它的某一个方法是可以正常使用。比如,一个 MenuComponent(可实际的类型可能是 Menu,而我们不知道),这时可能会调用它的 getPrice() 方法,这是有问题的,逻辑上是走不通的。那么,我们就必须要为 Menu 的这个方法抛出一个异常(异常是一个心机 boy,我们都不太喜欢它)。当然,也可以在外部使用 instanceof 关键字处理。可是,这样的处理总让我有一种非面向对象的处理过程,所以还是抛出异常吧。
    另外,Menu 中组合了 MenuItem,从这一点来看,倒是有几分“继承-组合”的意味。

    逻辑实现

    先来看看抽象类,这个是基础:
    MenuComponent.java

    public abstract class MenuComponent {
    
        public String getName() {
            throw new UnsupportedOperationException("暂不支持此操作");
        }
    
        public String getDescription() {
            throw new UnsupportedOperationException("暂不支持此操作");
        }
    
        public double getPrice() {
            throw new UnsupportedOperationException("暂不支持此操作");
        }
    
        public boolean isVegetarian() {
            throw new UnsupportedOperationException("暂不支持此操作");
        }
    
        public void print() {
            throw new UnsupportedOperationException("暂不支持此操作");
        }
    
        public void add(MenuComponent menuComponent) {
            throw new UnsupportedOperationException("暂不支持此操作");
        }
    
        public void remove(MenuComponent menuComponent) {
            throw new UnsupportedOperationException("暂不支持此操作");
        }
    
        public MenuComponent getChild(int childIndex) {
            throw new UnsupportedOperationException("暂不支持此操作");
        }
    }
    

    而在 Menu 的具体类中,虽然是继承了 MenuComponent,可是它的抽象方法又不能全部重写。原因上面也说了,这里不赘述了。可是,由于 Java 语法的客观存在,所以这里我们抛出了一个异常。
    Menu.java

    public class Menu extends MenuComponent {
    
        private String name = null;
        private String desc = null;
        private List<MenuComponent> menuComponents = null;
        
        public Menu(String _name, String _desc) {
            name = _name;
            desc = _desc;
        }
        
        @Override
        public String getName() {
            return name;
        }
    
        @Override
        public String getDescription() {
            return desc;
        }
    
        @Override
        public void print() {
            System.out.println("\nMenu: { " + name + ", " + desc + " }");
            if (menuComponents == null) {
                return;
            }
            System.out.println("-------------------------");
            
            for (MenuComponent menuComponent : menuComponents) {
                menuComponent.print();
            }
        }
    
        @Override
        public void add(MenuComponent menuComponent) {
            if (menuComponents == null) {
                menuComponents = new ArrayList<MenuComponent>();
            }
            menuComponents.add(menuComponent);
        }
    
        @Override
        public MenuComponent getChild(int childIndex) {
            if (menuComponents == null || menuComponents.size() <= childIndex) {
                return null;
            }
            return menuComponents.get(childIndex);
        }
    }
    

    基于上面对 Menu 类的说明,这里的 MenuItem 类的实现过程也是一样:只重写能够重写的部分,不能重写的地方抛出一个异常等待上层处理。
    MenuItem.java

    public class MenuItem extends MenuComponent {
    
        private String name = null;
        private String desc = null;
        private boolean vegetarian = false;
        private double price = 0.0d;
        
        public MenuItem(String _name, String _desc, boolean _vegetarian, double _price) {
            this.name = _name;
            this.desc = _desc;
            this.vegetarian = _vegetarian;
            this.price = _price;
        }
        
        @Override
        public String getName() {
            return name;
        }
    
        @Override
        public String getDescription() {
            return desc;
        }
    
        @Override
        public double getPrice() {
            return price;
        }
    
        @Override
        public boolean isVegetarian() {
            return vegetarian;
        }
    
        @Override
        public void print() {
            System.out.println("MenuItem: { " + name + ", " + desc + ", " + vegetarian + ", " + price + " }");
        }
    }
    

    上面的代码是整个一般组合模式的关键部分,这是需要注意的是它们的 print() 方法。对于 MenuItem 的 print() 来说,是很常规的打印,而 Menu 的打印则需要作处理。因为我们正常的理解里一个菜单因为会包含很多菜单项,所以,这里我们就把当前菜单下的所以菜单打印一遍。不过,这也不是什么难事,因为在每份 Menu 中都有一个 MenuItem 的列表。好了,问题解决。详情参见上面的代码部分。

    模式小结

    从上面的例子也可以看出,组合模式在解决有层级关系时,有着得天独厚的优势。思路清晰、代码优雅。唯一的不足是我们要针对不同的情况抛出相应的异常。

    组合与迭代

    对于组合模式息息相关的另一种模式——迭代模式,它在组合模式中可以说有着重要的地位。在上面的代码中,有点编程逻辑的人应该都可以发现,它们的 print() 方法是对象内部的操作。也就是说,如果我想要通过一个 Menu 操作一个 MenuItem 就必须在 Menu 内部进行实现。这是不现实的,因为需求变化的速度,可能隔了几秒连它的亲妈也不认识了。正因为如此,所以我们就必须想办法从外部拿到 Menu 中的 MenuItem。
    比如现在我想知道餐厅里所有的素食有哪些,如果我们不去改动原有代码,那么就可以添加一个外部的迭代逻辑。

    深搜的试水之行

    或许你又会说,这里根本不需要使用迭代,用一次深搜就 OK 了。是的没错,而且对于一个数据结构基本功还可以的同学,可以马上写出一个深搜的解决方案。这里给出我的深搜方案:

    public void showVegetarMenu(MenuComponent menu) {
            List<MenuComponent> visited = new ArrayList<>();
            showVegetarMenu(menu, visited);
        }
        
        private void showVegetarMenu(MenuComponent menu, List<MenuComponent> visited) {
            if (visited.contains(menu)) {
                return;
            }
            
            if (menu instanceof MenuItem) {
                if (menu.isVegetarian()) {
                    System.out.println(menu);
                }
                return;
            }
            
            List<MenuComponent> children = ((Menu) menu).getChildren();
            for (int i = 0; i < children.size(); i++) {
                showVegetarMenu(children.get(i), visited);
            }
        }
    
    素食菜单(深搜)
    MenuItem: { 包子, bun, true, 1.5 }
    MenuItem: { 油条, fritters, true, 1.2 }
    MenuItem: { 豆浆, milk, true, 2.0 }
    MenuItem: { 炒米粉, Fried noodles, true, 8.0 }
    MenuItem: { 冰淇淋, ice cream, true, 5.0 }
    

    结果不出意外。能够使用深搜,已然是逼格满满了。不过,使用深搜让我有一种面向过程编程的感觉,不够优雅。下面就让我用迭代器来实现一次华丽的逆转吧。

    迭代的逆转

    首先我们为 MenuComponent 添加一个 createIterator() 方法。就像下面这样:
    MenuComponent.java

    public abstract class MenuComponent {
    
        ( ... 省略重复的 N 条  ... )
        
        public abstract Iterator<MenuComponent> createIterator();
    }
    

    由于这里添加的是一个抽象的方法,那么在 Menu 和 MenuItem 中就要必须重写这个 createIterator() 方法。
    Menu.java

    public class Menu extends MenuComponent {
    
        private CompositeIterator iterator = null;
        
        ( ... 省略重复的 N 条  ... )
    
        @Override
        public Iterator<MenuComponent> createIterator() {
            if (iterator == null) {
                iterator = new CompositeIterator(menuComponents.iterator());
            }
            
            return iterator;
        }
    }
    

    MenuItem.java

    public class MenuItem extends MenuComponent {
    
        ( ... 省略重复的 N 条  ... )
    
        @Override
        public Iterator<MenuComponent> createIterator() {
            return new NullIterator();
        }
    }
    

    在上面两段代码中提到了两个迭代器类:CompositeIterator、NullIterator。这里有参照书本上的逻辑,不过也有改动,因为书本的迭代器没有通用性,下面会对这一点进行说明的。
    CompositeIterator.java

    public class CompositeIterator implements Iterator<MenuComponent> {
    
        private Stack<Iterator> stack = new Stack<>();
        
        public CompositeIterator(Iterator iterator) {
            stack.push(iterator);
        }
        
        @Override
        public boolean hasNext() {
            if (stack.empty()) {
                return false;
            }
            
            Iterator iterator = stack.peek();
            if (!iterator.hasNext()) {
                stack.pop();
                return hasNext();
            }
            
            return true;
        }
    
        @Override
        public MenuComponent next() {
            if (hasNext()) {
                Iterator iterator = stack.peek();
                MenuComponent component = (MenuComponent) iterator.next();
                if (component instanceof Menu) {
                    Iterator menuIterator = component.createIterator();
                    if (!stack.contains(menuIterator)) {
                        stack.push(menuIterator);
                    }
                }
                return component;
            }
            
            return null;
        }
    
        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }
    }
    

    这里的栈结构使用得很巧妙,因为这个栈的使用让我想到在 LeetCode 上的一道算法题,也是使用栈来实现,而且比一般的算法复杂度低很多,如果我不犯懒的话,应该会写那一篇博客的。咳咳,扯远了,回到正题。有关于栈的使用是一些数据结构和 Java api 的基础,这里不多说什么了。还有这里的 hasNext() 和 next() 方法,这里要求你对数据结构和 Java api(主要是 Stack 这一块)比较熟悉。所以,如果你看到这个地方有什么不太理解的,可以留言,也可以自行复习一下这两块内容。

    NullIterator.java

    public class NullIterator implements Iterator<MenuComponent> {
    
        @Override
        public boolean hasNext() {
            return false;
        }
    
        @Override
        public MenuComponent next() {
            return null;
        }
    
        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }
    }
    

    因为每个菜单项都不可能什么子菜单项,也就不存在什么迭代器了,所以在 MenuItem 中就可以返回一个 Null 的迭代器。当然,这是理想的做法。你也可以直接返回 null,只是这样一来,在上层就要多一次判空处理,相比较而言,这样的实现更优雅。
    程序的结果自然不出所料:

    素食菜单(迭代)
    MenuItem: { 包子, bun, true, 1.5 }
    MenuItem: { 油条, fritters, true, 1.2 }
    MenuItem: { 豆浆, milk, true, 2.0 }
    MenuItem: { 炒米粉, Fried noodles, true, 8.0 }
    MenuItem: { 冰淇淋, ice cream, true, 5.0 }
    

    只是,如果你只采用书本上的迭代器来实现,就会出现多级菜单下的菜单项被 show 了 N 遍。而你只能一脸懵逼。


    Ref

    • 《Head First 设计模式》

    GitHub 源码下载


    征集

    如果你也需要使用ProcessOn这款在线绘图工具,可以使用如下邀请链接进行注册:
    https://www.processon.com/i/56205c2ee4b0f6ed10838a6d

    展开全文
  • 设计模式--组合模式java例子
  • 主要介绍了JAVA设计模式组合模式,简单说明了组合模式的原理,并结合实例分析了java组合模式的具体用法,需要的朋友可以参考下
  • Java设计模式组合模式

    千次阅读 2016-09-10 19:31:22
    ... 目录 ...组合模式 个人理解: 定义如下: 通用类图如下: 组合模式的包含角色: ● Component 抽象构件角色 ● Leaf 叶子构件 ● Composite 树枝构件 通用源代码: 组合模式的优点: ...

     

    (尊重劳动成果,转载请注明出处:https://blog.csdn.net/qq_25827845/article/details/52496522冷血之心的博客)

    目录

    组合模式

    个人理解:

    定义如下:

    通用类图如下:

    组合模式的包含角色:

    ● Component 抽象构件角色

    ● Leaf 叶子构件

    ● Composite 树枝构件

    通用源代码:

    组合模式的优点:

    组合模式的缺点:

    使用场景:

    注意事项:


    组合模式

    是一种常见的设计模式(但我感觉有点复杂)也叫合成模式,有时又叫做部分-整体模式,主要是用来描述部分与整体的关系。

    个人理解:

    组合模式就是将部分组装成整体。

    定义如下:

    将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。

    通用类图如下:

    组合模式的包含角色:

    ● Component 抽象构件角色

    定义参加组合对象的共有方法和属性,可以定义一些默认的行为或属性。

    ● Leaf 叶子构件

    叶子对象,其下再也没有其他的分支,也就是遍历的最小单位。

    ● Composite 树枝构件

    树枝对象,它的作用是组合树枝节点和叶子节点形成一个树形结构。

    通用源代码:

    抽象构件
    public abstract class Component {
         //个体和整体都具有的共享
         public void doSomething(){
                 //编写业务逻辑
         }
    }
    

     

    树枝构件
    public class Composite extends Component {
         //构件容器
         private ArrayList<Component> componentArrayList = new ArrayList<Component>()
         //增加一个叶子构件或树枝构件
         public void add(Component component){
                 this.componentArrayList.add(component);
         }
         //删除一个叶子构件或树枝构件
         public void remove(Component component){
                 this.componentArrayList.remove(component);
         }    
     //获得分支下的所有叶子构件和树枝构件
         public ArrayList<Component> getChildren(){
                 return this.componentArrayList;
         }
    }
    

    树叶节点是没有子下级对象的对象,定义参加组合的原始对象行为。

    树叶构件
    public class Leaf extends Component {
         /*
          * 
    可以覆写父类方法
          * public void doSomething(){
          * 
          * }
          */
    }
    

    测试类负责树状结构的建立,并可以通过递归方式遍历整个树。

    public class Client {
         public static void main(String[] args) {
                //创建一个根节点
                 Composite root = new Composite();
                 root.doSomething();
                 //创建一个树枝构件
                 Composite branch = new Composite();
                 //创建一个叶子节点
                 Leaf leaf = new Leaf();
                 //建立整体
                 root.add(branch);
                 branch.add(leaf);          
         }
         //通过递归遍历树
         public static void display(Composite root){
                 for(Component c:root.getChildren()){
                      if(c instanceof Leaf){ //叶子节点
                              c.doSomething();
                      }else{ //树枝节点
                              display((Composite)c);
                      }
                 }
         }
    }
    

    组合模式的优点:

    ● 高层模块调用简单
    一棵树形机构中的所有节点都是Component,局部和整体对调用者来说没有任何区别,也就是说,高层模块不必关心自己处理的是单个对象还是整个组合结构,简化了高层模块的代码。
    ● 节点自由增加
    使用了组合模式后,我们可以看看,如果想增加一个树枝节点、树叶节点是不是都很容易,只要找到它的父节点就成,非常容易扩展,符合开闭原则,对以后的维护非常有利。
     

    组合模式的缺点:

           组合模式有一个非常明显的缺点,看到我们在场景类中的定义,提到树叶和树枝使用时的定义了吗?直接使用了实现类!这在面向接口编程上是很不恰当的,与依赖倒置原则冲突,读者在使用的时候要考虑清楚,它限制了你接口的影响范围。

    使用场景:

    ● 维护和展示部分-整体关系的场景,如树形菜单、文件和文件夹管理。
    ● 从一个整体中能够独立出部分模块或功能的场景。

    注意事项:

    只要是树形结构,就要考虑使用组合模式,这个一定要记住,只要是要体现局部和整体的关系的时候,而且这种关系还可能比较深,考虑一下组合模式吧。

     

    如果对你有帮助,记得点赞哦~欢迎大家关注我的博客,我会持续更新,如果有什么问题,可以进群366533258一起交流学习哦~

    展开全文
  • 主要介绍了Java设计模式组合模式(Composite模式)介绍,Composite定义:将对象以树形结构组织起来,以达成“部分-整体” 的层次结构,使得客户端对单个对象和组合对象的使用具有一致性,需要的朋友可以参考下
  • JAVA设计模式--组合模式

    千次阅读 2019-02-28 08:49:36
    一、什么是组合模式 组合(Composite)模式是一种对象的行为模式。将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。 组合模式的本质:统一叶子对象和组合...

    一、什么是组合模式

    组合(Composite)模式是一种对象的行为模式。将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
    组合模式的本质:统一叶子对象和组合对象。
    组合模式的目的:让客户端不再区分操作的是组合对象还是叶子对象,而是以一个统一的方式来操作。


    二、组合模式的适用性

    在开发中, 我们经常可能要递归构建树状的组合结构,比如以下的商品类别树:

    仔细观察上面的商品类别树,有以下几个明显的特点。
    • 有一个根节点,比如“服装”,它没有父节点,它可以包含其他的节点。
    • 树枝节点,有一类节点可以包含其他的节点,称之为树枝节点,比如“男装”、“女装”和“母婴”。
    • 叶子节点,有一类节点没有子节点,称之为叶子节点,比如“衬衣”、“夹克”、“裙子”、“套装”等。
    如果碰到类似上面这种,需使用对象树来描述或实现的功能,都可以考虑使用组合模式,比如读取XML文件,或是对语句进行语法解析等。


    三、组合模式的结构

    组合模式涉及的角色及其职责如下:

    抽象组件(Component)角色:为组合对象和叶子对象声明公共的接口,让客户端可以通过这个接口来访问和管理整个对象树,并可以为这些定义的接口提供缺省的实现。
    组合对象(Composite)角色:通常会存储子组件(组合对象、叶子对象),定义包含子组件的那些组件的行为,并实现在抽象组件中定义的与子组件有关的操作,例如子组件的添加(addChild)和删除(removeChild)等。
    叶子对象(Leaf)角色:定义和实现叶子对象的行为,并且它不再包含其他的子节点对象。
    客户端(Client)角色:通过Component接口来统一操作组合对象和叶子对象,以创建出整个对象树结构。

    组合模式结构示意源代码如下:
    先看看抽象组件类的定义,示例代码如下。

    /**
     * 抽象的组件对象,为组合中的对象声明接口,实现接口的缺省行为
     */
    public abstract class Component {
     
        // 示意方法,子组件对象可能有的功能方法
        public abstract void someOperation(String preStr);
     
        public void addChild(Component child) {
            // 缺省的实现,抛出异常,因为叶子对象没有这个功能,或子类未实现这个功能
            throw new UnsupportedOperationException("对象不支持此功能");
        }
     
        public void removeChild(Component child) {
            // 缺省的实现,抛出异常,因为叶子对象没有这个功能,或子类未实现这个功能
            throw new UnsupportedOperationException("对象不支持此功能");
        }
     
        public Component getChildren(int index) {
            // 缺省的实现,抛出异常,因为叶子对象没有这个功能,或子类未实现这个功能
            throw new UnsupportedOperationException("对象不支持此功能");
        }
    }

    接下来看看组合类的定义,示意代码如下

    import java.util.ArrayList;
    import java.util.List;
     
    public class Composite extends Component {
     
        /**
         * 示意属性,组件的名字
         */
        private String name = "";
     
        public Composite(String name) {
            this.name = name;
        }
     
        /**
         * 用来存储组合对象中包含的子组件对象
         */
        private List<Component> childComponents = null;
     
        /**
         * 示意方法,此处用于输出组件的树形结构,通常在里面需要实现递归的调用
         */
        @Override
        public void someOperation(String preStr) {
            // 先把自己输出
            System.out.println(preStr + "+" + name);
            // 如果还包含其他子组件,那么就输出这些子组件对象
            if (null != childComponents) {
                // 添加一个空格,表示向后缩进一个空格
                preStr += "   ";
                // 输出当前对象的子组件对象
                for (Component component : childComponents) {
                    // 递归地进行子组件相应方法的调用,输出每个子组件对象
                    component.someOperation(preStr);
                }
            }
        }
     
        /**
         * 向组合对象中添加组件对象
         */
        public void addChild(Component child) {
            // 延迟初始化
            if (null == childComponents) {
                childComponents = new ArrayList<Component>();
            }
            childComponents.add(child);
        }
     
        /**
         * 从组合对象中移除组件对象
         */
        public void removeChild(Component child) {
            if (null != childComponents) {
                childComponents.remove(child);
            }
        }
     
        /**
         * 根据索引获取组合对象中对应的组件对象
         */
        public Component getChildren(int index) {
            if (null != childComponents) {
                if (index >= 0 && index < childComponents.size()) {
                    return childComponents.get(index);
                }
            }
            return null;
        }
    }

    再来看看叶子类的定义,示例代码如下。

    public class Leaf extends Component {
        /**
         * 示意属性,组件的名字
         */
        private String name = "";
     
        public Leaf(String name) {
            this.name = name;
        }
     
        /**
         * 示意方法,此处用于输出组件的树形结构
         */
        @Override
        public void someOperation(String preStr) {
            System.out.println(preStr + "-" + name);
        }
    }

    在客户端中使用Component接口来操作组合对象结构,示意代码如下。

    public class Client {
        public static void main(String[] args) {
            // 定义多个Composite组合对象
            Component root = new Composite("服装");
            Component c1 = new Composite("男装");
            Component c2 = new Composite("女装");
            Component c3 = new Composite("母婴");
     
            // 定义多个Leaf叶子对象
            Component leaf1 = new Leaf("西服");
            Component leaf2 = new Leaf("夹克");
            Component leaf3 = new Leaf("衬衫");
            Component leaf4 = new Leaf("裙子");
            Component leaf5 = new Leaf("套装");
            Component leaf6 = new Leaf("鞋袜");
            Component leaf7 = new Leaf("孕妇装");
            Component leaf8 = new Leaf("婴儿装");
     
            // 组合成为树形的对象结构
            root.addChild(c1);
            root.addChild(c2);
            root.addChild(leaf6);
            c1.addChild(leaf1);
            c1.addChild(leaf2);
            c1.addChild(leaf3);
            c2.addChild(leaf4);
            c2.addChild(leaf5);
            c2.addChild(c3);
            c3.addChild(leaf7);
            c3.addChild(leaf8);
     
            // 调用根对象的输出功能输出整棵树
            root.someOperation("");
        }
    }
    运行程序打印结果如下:
    +服装
       +男装
          -西服
          -夹克
          -衬衫
       +女装
          -裙子
          -套装
          +母婴
             -孕妇装
             -婴儿装
       -鞋袜


    四、组合模式的安全性和透明性

    • 组合模式的安全性是指:从客户使用组合模式上看是否更安全。如果是安全的,那么就不会有发生误操作的可能,能访问的方法都是被支持的功能。
    • 组合模式的透明性是指:从客户使用组合模式上看是否需要区分到底是组合对象还是叶子对象。如果是透明的,那就不用再区分,对于客户而言,都是组件对象,具体的类型对于客户而言是透明的,是客户无须关心的。

    透明性的实现:
    如果把管理子组件的操作定义在Component中,那么客户端只需要面对Component,而无须关心具体的组件类型,这种实现方式就是透明性的实现。前面结构示意代码中就是采用的这一实现方式。
    但是透明性的实现是以安全性为代价的,因为在Component中定义的一些方法,对于叶子对象来说是没有意义的,比如增加、删除子组件对象。但这些方法对客户却是透明的,因此客户可能会对叶子对象调用这种增加或删除子组件的方法,这样的操作是不安全的。
    组合模式的透明性实现,通常的方式是:在Component中声明管理子组件的操作,并在Component中为这些方法提供默认的实现,对于叶子对象不支持的功能,可以直接抛出一个异常,来表示不支持这个功能。

    安全性的实现:

    如果把管理子组件的操作定义在Composite中,那么客户端在使用叶子对象的时候,就不会发生使用添加子组件或是删除子组件的操作了,因为压根就没有这样的功能,这种实现方式是安全的。

    但是这样一来,客户端在使用的时候,就必须区分到底使用的是Composite对象,还是叶子对象,不同对象的功能是不一样的。

    两种实现方式的选择:
    对于组合模式而言,在安全性和透明性上,会更看重透明性,毕竟组合模式的功能就是要让用户对叶子对象和组合对象的使用具有一致性。
    因此,在使用组合模式的时候,应多采用透明性的实现方式,少用安全性的实现方式。


    五、组合模式的优缺点

    使用组合模式的优点:
       1)  统一了组合对象和叶子对象。
       2)  简化了客户端调用,无须区分操作的是组合对象还是叶子对象。
       3)  更容易扩展,有了Component的约束,新定义的Composite或Leaf子类能够很容易地与已有的结构一起工作。

    使用组合模式的缺点:
       1)  很难限制组合中的组件类型。


    六、总结

    组合模式通过把叶子对象当成特殊的组合对象看待,从而对叶子对象和组合对象一视同仁,全部当成了Component对象,有机地统一了叶子对象和组合对象。
    正是因为统一了叶子对象和组合对象,在将对象构建成树形结构的时候,才不需要做区分,反正是组件对象里面包含其他的组件对象,如此递归下去:也才使得对于树形结构的操作变得简单,不管对象类型,统一操作。

    原文:https://blog.csdn.net/pengjunlee/article/details/54974452 
     

    展开全文
  • Java设计模式

    千次阅读 多人点赞 2019-09-03 23:20:31
    Java设计模式 1.工厂模式 工厂模式一般分为简单工厂、工厂、抽象工厂3钟情况,属于创建型设计模式。 2.生成器模式 3.观察者模式 4.桥接模式 5.代理模式 6.状态模式 7.访问者模式 ...

    Java设计模式

    1.工厂模式

    2.生成器模式

    3.观察者模式

    4.桥接模式

    5.代理模式

    6.状态模式

    7.访问者模式

    8.命令模式

    9.装饰器模式

    10.组合模式

    1.工厂模式

    工厂模式一般分为简单工厂、工厂、抽象工厂3种情况,属于创建型设计模式。

    简单工厂(静态工厂)

    //定义小汽车接口:ICar.java
    public interface ICar{
      //由于工厂模式仅关系对象的创建,为说明方便,无需定义方法
    }
    
    //下面定义高、中、低档具体的汽车
    //高档小汽车:TopCar.java
    public class TopCar implements ICar{
    }
    //中档小汽车:MidCar.java
    public class MidCar implements ICar {
    }
    //低档小汽车:LowCar.java
    public class LowCar implements ICar {
    }
    
    //简单工厂:CarSimpleFactory.java
    public class CarSimpleFactory {
        public static final String TOPTYPE = "toptype";
        public static final String MIDTYPE = "midtype";
        public static final String LOWTYPE = "lowtype";
        public static ICar create(String mark){
        ICar obj = null;
        if(mark.equals(TOPTYPE)){ //如果是高档类型
          obj = new TopCar();  //则创建高档车对象
        }
        else if(mark.equals(MIDTYPE)){
        obj = new MidCar();
      }
        else if(mark.equals(LOWTYPE)){
        obj = new LowCar();
      }
        return obj;    //返回选择的对象
      }
    }
    
    //测试程序:CarTest.java
    public class CarTest {
      public static void main(String[] args) {
      //从工厂中创建对象
        ICar obj = CarSimpleFactory.create("toptype");
      }
    }
    

    工厂

    //定义小汽车接口:ICar.java
    public interface ICar {
    //由于工厂模式仅关系对象的创建,为说明方便,无需定义方法
    }
    //定义高、中、低档具体的小汽车
    //高档小汽车:TopCar.java
    public class TopCar implements ICar {
    }
    //中档小汽车:MidCar.java
    public class MidCar implements ICar {
    }
    //低档小汽车:LowCar.java
    public class LowCar implements ICar {
    }
    //定义抽象工厂:AbstractFactory.java
    public abstract class AbstractFactory {
    public abstract ICar create();
    }
    //定义高档小汽车工厂:TopFactory.java
    public class TopFactory extends AbstractFactory {
    public ICar create() {
        return new TopCar(); //高档工厂生成高档小汽车对象
      }
    }
    //定义中档小汽车工厂:MidFactory.java
    public class MidFactory extends AbstractFactory {
    public ICar create() {
        return new MidCar(); //中档工厂生成中档小汽车对象
      }
    }
    //定义低档小汽车工厂:LowFactory.java
    public class LowFactory extends AbstractFactory {
    public ICar create() {
        return new LowCar(); //低档工厂生成低档小汽车对象
      }
    }
    //测试类:CarTest.java
    public class CarTest {
    public static void main(String []args){
        AbstractFactory obj = new TopFactory();//多态创建高档工厂
        ICar car = obj.create();    //获得高档工厂中的小汽车对象
      }
    }
    

    抽象工厂

    //小汽车接口
    public interface ICar { }
    public class TopCar implements ICar { }
    public class MidCar implements ICar { }
    public class LowCar implements ICar { }
    
    //定义公共汽车接口、高、中、低档公共汽车类
    public interface IBus { }
    public class UpBus implements IBus { }
    public class MidBus implements IBus { }
    public class DnBus implements IBus { }
    
    //定义抽象工厂:AbstractFactory.java
    public absttract class AbstractFactory {
    public abstract ICar createCar(); //产生小汽车对象
    public abstract IBus createBus(); //产生公共汽车对象
    }
    
    //定义高档工厂:TopFactory.java
    public class TopFactory extends AbstractFactory {
      public ICar createCar() {
        return new TopCar();  //高档工厂生成高档小汽车对象
      }
      public IBus createBus() {
        return new UpBus();  //高档工厂生成高档公共汽车对象
      }
    }
    
    //定义中档工厂:MidFactory.java
    public class MidFactory extends AbstractFactory {
      public ICar createCar() {
        return new MidCar();  //中档工厂生成中档小汽车对象
      }
      public IBus createBus() {
        return new MidBus();  //中档工厂生成中档公共汽车对象
      }
    }
    //定义低档工厂:LowFactory.java
    public class LowFactory extends AbstractFactory {
      public ICar createCar() {
        return new LowCar();  //低档工厂生成中档小汽车对象
      }
      public IBus createBus() {
        return new DnBus();  //低档工厂生成中档公共汽车对象
      }
    }
    

    2.生成器模式

    生成器模式也称为建造者模式。生成器模式的意图在于将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。在软件设计中,有时候面临着一个非常复杂的对象的创建工作。这个复杂的对象通常可以分成几个较小的部分,由各个子对象组合出这个复杂对象的过程相对来说比较稳定,但是子对象的创建过程各不相同并且可能面临变化。根据OOD中的OCP原则,应该对这些子对象的创建过程进行变化封装。

    生成器思路是产品类与创建产品的类相分离。产品类仅1个,创建产品的类有n个。

    生成器设计模式涉及4个关键角色:产品(Product)、抽象生成器(IBuild)、具体生成器(Builder)、指挥者(Director)。
    在这里插入图片描述

    (1)定义1个产品类。
    public class Unit1{……}
    public class Unit2{……}
    public class Unit3{……}
    public class Product {
    Unit1 u1;
    Unit2 u2;
    Unit3 u3;
    }
    由于不在该类完成Product类对象的创建,所以无需显示定义构造方法。
    
    (2)定义n个生成器Build类。
    根据语义,生成器是用来生成Product对象的,因此一般来说,Product是生成器类的一个成员变量;
    根据语义,每创建一个Product对象,本质上都需要先创建Unit1,Unit2,…, UnitN,再把它们组合成所需的Product对象,
    因此需要n个createUnit()方法及一个组合方法composite();由于createUnit()及composite()是共性,
    因此可定义共同的生成器类接口, n个生成器类均从此接口派生即可。代码如下。
    //定义生成器类接口IBuild
    public interface IBuild {
    public void createUnit1();
    public void createUnit2();
    public void createUnit3();
    public Product composite();    //返回值是Product对象
    }
    //定义3个生成器类
    public class BuildProduct implements IBuild { //生成第一种Product
    Product p = new Product();     //Product是成员变量
    public void createUnit1() {
    //p.u1= ...       //创建Unit1
    }
    public void createUnit2() {
    //p.u2 = ...       //创建Unit2
    }
    public void createUnit3() {
    //p.u3 =  //创建Unit3
    }
    public Product composite() {
    //...   //关联Unit,Unit2,Unit3
    return p;  //返回Product对象p
    }
    }
    public class BuildProduct2 implements IBuild {  //生成第2种Product
    Product p = new Product();//Product是成员变量
    public void createUnit() {/*p.u = ...... */} //创建Unit
    public void createUnit2(){/*p.u2 = ...... */} //创建Unit2
    public void createUnit3(){/*p.u3 = ...... */} //创建Unit3
    public Product composite() {
    //......    //关联Unit1,Unit2,Unit3
    return p;   //返回Product对象p
    }
    }
    public class BuildProduct3 implements IBuild {  //生成第3种Product
    Product p = new Product();//Product是成员变量
    public void createUnit1() {/*p.u1 = ...... */} //创建Unit1
    public void createUnit2(){/*p.u2 = ...... */} //创建Unit2
    public void createUnit3(){/*p.u3 = ...... */} //创建Unit3
    public Product composite() {
    //......  //关联Unit1,Unit2,Unit3
    return p;  //返回Product对象p
    }
    }
    通过上述代码可知,若需求分析发生变化,只需增加或删除相应的生成器类即可,无需修改已有的类代码。
    (3)定义1个统一调度类,也叫指挥者(Director)类,是对生成器接口IBuild的封装。该类及简单的测试代码如下。
    public class Director {
    private IBuild build;
    public Director(IBuild build){
    this.build = build;
    }
    public Product build(){
    build.createUnit1();
    build.createUnit2();
    build.createUnit3();
    return build.composite();
    }
    public static void main(String []args){
    IBuild build = new BuildProduct();
    Director direct = new Director(build);
    Product p = direct.build();
    }
    }
    

    3.观察者模式

    观察者设计模式适合解决多种对象跟踪一个对象数据变化的程序结构问题,有一个称作“主题”的对象和若干个称作“观察者”的对象。有一个主题数据——温度,3个观察者—温度日志、温度曲线、温度报警。因此观察者设计模式涉及两种角色:主题和观察者。
    观察者设计模式可以从以下递推中得出一些重要结论。
    ● 主题要知道有哪些观察者对其进行监测,因此主题类中一定有一个集合类成员变量,包含了观察者的对象集合。
    ● 既然包含了观察者的对象集合,那么,观察者一定是多态的,有共同的父类接口。
    ● 主题完成的主要功能是:可以添加观察者,可以撤销观察者,可以向观察者发消息,引起观察者响应。这三个功能是固定的,因此主题类可以从固定的接口派生。
    因此,编制观察者设计模式,要完成以下功能类的编制。
    ● 主题ISubject接口定义。
    ● 主题类编制。
    ● 观察者接口IObserver定义。
    ● 观察者类实现。
    在这里插入图片描述

    (1)观察者接口IObserver。
    
        public interface IObserver {
            public void refresh(String data);
        }
        
    (2)主题接口ISubject。
    
        public interface ISubject {
            public void register(IObserver obs);       //注册观察者
            public void unregister(IObserver obs);     //撤销观察者
            public void notifyObservers();             //通知所有观察者
        }
        
    (3)主题实现类Subject。
    
        public class Subject implements ISubject {
            private Vector<IObserver> vec = new Vector();  //观察者维护向量
            private String data;                           //主题中心数据
    
            public String getData() {
                return data;
            }
            public void setData(String data) {              //主题注册(添加)
                this.data = data;
            }
            public void register(IObserver obs) {           //主题注册(添加)观察者
             vec.add(obs);
            }
    
            public void unregister(IObserver obs) {         //主题撤销(删除)观察者
                if(vec.contains(obs))
                    vec.remove(obs);
            }
            public void notifyObservers(){             //主题通知所有观察者进行数据响应
                for(int i=0; i<vec.size(); i++){
                    IObserver obs = vec.get(i);
                    obs.refresh(data);
                }
            }
        }
        
        主题实现类Subject是观察者设计模式中最重要的一个类,包含了观察者对象的维护向量vec以及主题中心数据data变量与具体观察者对象的关联
        方法(通过nitofyObservers())。也就是说,从此类出发,可以更深刻地理解ISubject为什么定义了3个方法、IObserver接口为什么定义了1个方法。
        
    (4)一个具体观察者类Observer。
    
        public class Observer implements IObserver {
            public void refresh(String data) {
                System.out.println("I have received the data:" +data);
            }
        }
        
    (5)一个简单的测试类Test。
    
        public class Test {
            public static void main(String[] args) {
                IObserver obs = new Observer();    //定义观察者对象
                Subject subject = new Subject();
                //定义主题对象
                subject.register(obs);             //主题添加观察者
                subject.setData("hello");          //主题中心数据发生变动
                subject.notifyObservers();         //通知所有观察者进行数据响应
            }
        }
        
    该段代码的含义是:当主题中心数据变化(通过setData方法)后,主题类subject要调用notifyObservers()方法,
    通知所有观察者对象接收数据并进行数据响应。
    

    4.桥接模式

    桥接模式是关于怎样将抽象部分与它的实现部分分离,使它们都可以独立地变化的成熟模式。
    在这里插入图片描述

    (1)定义邮寄接口IPost。
    
        public interface IPost{   //邮局
            public void post();   //发送功能
        }
        
    (2)两个具体邮寄类SimplePost、MarkPost。
    
        //平信邮寄类SimplePost
        class SimplePost implements IPost{     //平信
            public void post(){                //发送
                System.out.println("This is Simple post");
            }
        }
        //挂号邮寄类
        class MarkPost implements IPost{       //挂号
            public void post(){                //发送
                System.out.println("This is Mark post");
            }
        }
        
      经过(1)、(2)的论述,完成了语义的前半部分定义:邮局有发送功能;发送有两种方式,平邮和挂号。
      
    (3)抽象事物类AbstractThing。
    
        abstract class AbstractThing{ //抽象事物
    
            private IPost obj;         //有抽象发送功能
            public AbstractThing(IPost obj){
                this.obj = obj;
            }
            public void post(){
                obj.post();
            }
        }
        
    该类是桥接模式的核心。分析语义“信件和包裹共享平邮与挂号功能”:信件、包裹是两个不同的事物,它们有共享的功能,
    也一定有相异的功能。共享的功能一定能封装到一个类中,又由于该类不能代表一个具体的事物,所以把它定义成abstract类是恰当的。
    该类共享的是多态成员obj,是IPost类型的,是抽象的、泛指的,用一条语句表明了事物共享平邮和发送功能。
    
    (4)具体事物类Letter、Parcel。
    
        //信件类Letter
        class Letter extends AbstractThing{
            public Letter(IPost obj){
                super(obj);
            }
            //其他独有变量和方法
        }
        //包裹类Parcel
        class Parcel extends AbstractThing{
            public Parcel(IPost obj){
                super(obj);
            }
            //其他独有变量和方法
        }
        
        //编制一个简单的测试类
        public class Test {
            public static void main(String[] args) {
                IPost p = new SimplePost();
    
                Letter letter = new Letter(p);
                letter.post();
            }
        }
        
    第一种情况:若增加了新的事物,则只需从Abstract派生一个类即可,其他无需改变。
    
        class NewThing extends AbstractThing{
            public NewThing(IPost obj){
                super(obj);
            }
            //其他独有变量和方法
        }
        
    第二种情况:若增加了新的邮寄类别,比如特快专递,则只需从IPost接口派生一个类即可,其他无需改变。
    
        class EMSPost implements IPost{        //特快专递
        public void post(){                //发送
                System.out.println("This is EMS post");
            }
        }
    

    5.代理模式

    代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不能看到的内容和服务或者添加客户需要的额外服务。代理模式则是一种可以很好实现客户对象与代理对象分离的策略。

    代理模式的定义如下:给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式的英文叫作Proxy或Surrogate,它是一种对象结构型模式。其抽象UML图如图所示
    在这里插入图片描述
    代理模式包含如下角色
    ● ISubject:抽象主题角色,是一个接口。该接口是对象和它的代理共用的接口。
    ● RealSubject:真实主题角色,是实现抽象主题接口的类。
    ● Proxy:代理角色,内部含有对真实对象RealSubject的引用,从而可以操作真实对象。代理对象提供与真实对象相同的接口,以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。以买电视为例,其代码如下。
    (1)定义抽象主题——买电视。

    interface ITV{
            public void buyTV();
        }
    

    (2)定义实际主题——买电视过程。

    class Buyer implements ITV{
        public void buyTV(){
            System.out.println("I have bought the TV by buyer proxy");
        }
    }
    

    真正的付费是由购买者完成的。
    (3)定义代理。

    class BuyerProxy implements ITV{
        private Buyer buyer;
        public BuyerProxy(Buyer buyer){
            this.buyer = buyer;
        }
        public void buyTV(){
            preProcess();
            buyer.buyTV();
            postProcess();
        }
        public void preProcess(){
            //询问客户需要电视类型、价位等信息
        }
        public void postProcess(){
            //负责把电视送到客户家
        }
    }
    

    电视代理商BuyerProxy与购买者Buyer都实现了相同的接口ITV,是对Buyer对象的进一步封装。着重理解buyTV()方法:首先代理商要通过preProcess()询问客户买电视的类型、价位等信息,然后购买者通过buyer.buyTV()自己付费完成电视购买,最后代理商通过postProcess()协商具体的送货服务、产品三包等。

    代理模式最突出的特点是:代理角色与实际主题角色有相同的父类接口。常用的代理方式有4类:虚拟代理、远程代理、计数代理、动态代理

    虚拟代理
    虚拟代理的关键思想是:如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。当用户请求一个“大”对象时,虚拟代理在该对象真正被创建出来之前扮演着替身的角色;当该对象被创建出来之后,虚拟代理就将用户的请求直接委托给该对象。

    远程代理
    远程代理的含义是:为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以在同一台主机中,也可在另一台主机中。也就是说,远程对象驻留于服务器上,客户机请求调用远程对象调用相应方法,执行完毕后,结果由服务器返回给客户端。
    在这里插入图片描述
    计数代理
    当客户程序需要在调用服务提供者对象的方法之前或之后执行日志或计数的额外功能时,就可以使用计数代理模式。计数代理模式并不是把这些额外操作的代码直接添加到源服务中,而是把它们封装成一个单独的对象,这就是计数代理。

    动态代理
    对代理模式而言,一个主题类与一个代理类一一对应,这也是静态代理模式的特点。
    静态代理模式简图
    但是,也常存在这样的情况,有n个主题类,但是代理类中的“前处理、后处理”都是一样的,仅调用主题不同,需要编制如图所示的程序框架。
    [动态代理模式简图]
    也就是说,多个主题类对应一个代理类,共享“前处理、后处理”功能,动态调用所需主题,大大减小了程序规模,这就是动态代理模式的特点。实现动态代理的关键技术是反射。

    6.状态模式

    状态模式为研究各种状态以及状态间相互转化的实现方式提出了一种较好的设计思路。

    ● 状态类有共同的父接口(或抽象类), n个不同的状态实现类。
    ● 事物类中包含状态类父接口成员变量声明,用以反映语义“事物有n个状态”。
    ● 事物类中一定有方法选择分支,判断事物当前处于何种状态。

    状态模式必须完成如下内容的编制:
    ● State:状态接口,封装特定状态所对应的行为。
    ● ConcreteState:具体实现状态处理的类。
    ● Context:事物类,也称上下文类,通常用来定义多态状态接口,同时维护一个用来具体处理当前状态的示例对象。

    状态模式的UML抽象类图如图所示
    在这里插入图片描述
    状态模式的具体抽象代码如下。
    (1)定义状态抽象接口IState。

    interface IState{
        public void goState();
    }
    

    (2)定义状态实现类。

    class ConcreteStateA implements IState{//定义状态A类
        public void goState(){
            System.out.println("This is ConcreteStateA");
        }
    }
    class ConcreteStateB implements IState{//定义状态B类
        public void goState(){
            System.out.println("This is ConcreteStateB");
        }
    }
    

    (3)定义状态上下文维护类Context。

    class Context{            //上下文有n种状态
        private IState state;
        public void setState(IState state){
            this.state = state;
        }
        public void manage(){
            //此处代码根据条件选择某种状态
            state.goState(); //执行某种状态功能
        }
    }
    

    Context类是实现状态模式的关键,本部分仅列出了状态模式的基本代码

    7.访问者模式

    访问者模式的目的是封装一些施加于某种数据结构元素之上的操作,一旦这些操作需要修改的话,接受这个操作的数据结构可以保持不变。为不同类型的元素提供多种访问操作方式,且可以在不修改原有系统的情况下增加新的操作方式,这就是访问者模式的模式动机。

    访问者模式抽象类图
    在这里插入图片描述
    访问者模式主要涉及以下四种角色:
    ● IElement:抽象的事物元素功能接口,定义了固定功能方法及可变功能方法接口。
    ● Element:具体功能的实现类。
    ● IVisitor:访问者接口,为所有访问者对象声明一个visit方法,用来代表为对象结构添加的功能,原则上可以代表任意的功能。
    ● Visitor:具体访问者实现类,实现要真正被添加到对象结构中的功能。

    考虑这样一个应用:已知三点坐标,编写功能类,求该三角形的面积和周长。

    如果采用访问者模式,应当这样思考:目前已确定的需求分析是求面积和周长功能,但有可能将来求三角形的重心、垂心坐标,内切、外界圆的半径等,因此,在设计时必须考虑如何屏蔽这些不确定情况。具体代码如下。

    1.定义抽象需求分析接口IShape

    interface IShape{
        float getArea();             //明确的需求分析
        float getLength();           //明确的需求分析
        Object accept(IVisitor v);   //可扩展的需求分析
    }
    

    着重理解可扩展的需求分析方法accept(),它在形式上仅是一个方法,但是按访问者模式而言,它却可以表示将来可以求重心、垂心坐标等功能,是一对多的关系,因此IVisitor一般来说是接口或抽象类,“多”项功能一定是由IVisitor的子类来实现的。那么为什么返回值是Object类型呢?可以这样理解,例如重心坐标由两个浮点数表示,外接圆半径由一个浮点数表示,为了屏蔽返回值差异,返回值定义成Object,表明可以返回任意对象类型。

    2.定义具体功能实现类Triangle

    class Triangle implements IShape{
        float x1, y1, x2, y2, x3, y3;           //三角形三点坐标
        public Triangle(float x1, float y1, float x2, float y2, float x3, float y3){
    
            this.x1=x1; this.y1=y1;
            this.x2=x2; this.y2=y2;
            this.x3=x3; this.y3=y3;
        }
        public float getDist(float u1, float v1, float u2, float v2){   //求任意两点距离
            return (float)Math.sqrt((u1-u2)*(u1-u2)+(v1-v2)*(v1-v2));
        }
        public float getArea(){            //固定需求分析求面积
            float a = getDist(x1, y1, x2, y2);
            float b = getDist(x1, y1, x3, y3);
            float c = getDist(x2, y2, x3, y3);
            float s = (a+b+c)/2;
            return (float)Math.sqrt(s*(s-a)*(s-b)*(s-c)); //海伦公式求面积
        }
        public float getLength(){          //固定需求分析求周长
            float a = getDist(x1, y1, x2, y2);
            float b = getDist(x1, y1, x3, y3);
            float c = getDist(x2, y2, x3, y3);
            return a+b+c;
        }
        public Object accept(IVisitor v){  //可扩展需求分析
            return v.visit(this);
        }
    }
    

    着重理解accept()方法,可以看出IVisitor接口中一定定义了多态方法visit(),那为什么把this引用传过去呢?可以这样理解:例如求三角形重心坐标,它的功能一定是在IVisitor的子类实现的,那么该子类一定得知道三角形的三个顶点坐标,因此把this引用传过去,相当于IVisitor的子类可访问Triangle类的成员变量,编制求重心坐标就容易了。

    3.定义访问者接口IVisitor

    interface IVisitor{
        Object visit(Triangle t);
    }
    

    至此为止,有了1、2、3的代码,访问者模式的代码框架就已经构建起来了。如果需求分析没有变化,那么程序一直应用即可;如果需求分析发生变化,则基础功能类不用变化,只要定义IVisitor接口的具体功能实现类就可以了,例如求三角形重心坐标代码如下。
    4.定义重心坐标实现类CenterVisitor

    class Point{
        float x, y;
    }
    class CenterVisitor implements IVisitor{
        public Object visit(Triangle t){
            Point pt = new Point();
            pt.x = (t.x1+t.x2+t.x3)/3;
            pt.y = (t.y1+t.y2+t.y3)/3;
            return pt;
        }
    }
    

    一个简单的测试类如下。

    public class Test3 {
        public static void main(String[] args) {
            IVisitor v = new CenterVisitor();        //定义求重心具体访问者对象
            Triangle t = new Triangle(0,0,2,0,0,2);  //定义三角形对象
            Point pt = (Point)t.accept(v);           //通过访问者对象求三角形重心坐标
            System.out.println(pt.x+"\t"+pt.y);
        }
    }
    

    可以知道,如果再想增加一个求三角形外接圆半径功能,只需再定义一个新类实现IVisitor接口,在该类中完成求外接圆半径功能即可。

    8.命令模式

    顾名思义,命令模式一定是有命令发送者、命令接收者。命令发送者负责发送命令,命令接收者负责接收命令并完成具体的工作。

    命令模式主要针对需要执行的任务或用户提出的请求进行封装与抽象。抽象的命令接口描述了任务或请求的共同特征,而实现则交由不同的具体命令对象完成。每个命令对象都是独立的,它负责完成需要执行的任务,却并不关心是谁调用它。

    命令模式抽象UML类图
    在这里插入图片描述
    命令模式一般有4种角色,如下所示。
    ● ICommander:抽象命令者,是一个接口,规定了用来封装请求的若干个方法。
    ● ConcreteCommander:具体命令发送者,即命令源。它是实现命令接口的类的示例,如上文中的Teacher类。
    ● Invoker:请求者,具体命令的管理与维护类。请求者是一个包含“命令接口”变量的类的示例。请求者中的“命令”接口的变量可以存放任何具体命令的引用,请求者负责调用具体命令,让具体命令执行那些封装了请求的方法。
    ● Receiver:命令接收者,是一个类的示例。该示例负责执行与请求相关的操作,如上文中的Student类。

    考虑老师通知学生打扫卫生的程序描述,具体代码如下。
    1.抽象命令接口ICommand

    interface ICommand{
        public void sweep();
    }
    

    2.命令接收者Student

    class Student{
        public void sweeping(){
            System.out.println("we are sweeping the floor");
        }
    }
    

    在命令模式中,具体工作一定是在接收者中完成的,这一点非常重要。示例中“清扫”工作是由sweeping()方法完成的。
    3.命令发送者Teacher

    class Teacher implements ICommand{
        private Student receiver = null;
        public Teacher(Student receiver){
            this.receiver = receiver;
        }
        public void sweep(){  //发送sweep清扫命令
            receiver.sweeping();
        }
    }
    

    命令发送者类中,一般来说包含命令接收者的引用,表明发送命令的目的地址。所以Teacher类中定义了接收者Student类对象的引用。而实现的抽象接口方法中表明发送命令的具体过程,sweep()中利用方法转发说明具体的清扫工作是由接收者Student对象完成的。
    4.命令请求者类Invoke

    class Invoke{
        ICommand command;
        public Invoke(ICommand command){
            this.command = command;
        }
        public void execute(){
            command.sweep();  //启动命令
        }
    }    
    

    在这里插入图片描述
    在这里插入图片描述
    普通思路是命令发送者直接作用命令接收者,而命令模式思路是在两者之间增加一个请求者类,命令发送者与请求者作用,请求者再与命令接收者作用,请求者起到了一个桥梁的作用

    5.一个简单的测试类

    public class Test {
        public static void main(String[] args)
        {
            Student s = new Student();         //定义接收者
            Teacher t = new Teacher(s);        //定义命令发送者
            Invoke invoke = new Invoke(t);     //将命令请求加到请求者对象中
            invoke.execute();                  //由请求者发送命令
        }
    }
    

    9.装饰器模式

    装饰器模式利用包含代替继承,动态地给一个对象添加一些额外的功能。以消息日志功能为例,其装饰器模式UML类图如图所示
    在这里插入图片描述
    装饰器模式主要有如下4种角色。
    ● 抽象构件角色(Component):
    它是一个接口,封装了将要实现的方法,如ILogger。
    ● 具体构件角色(ConcreteComponent):
    它是多个类,该类实现了Component接口,如FileLogger、ConsoleLogger。
    ● 装饰角色(Decorator):
    它是一个抽象类,该类也实现了Component接口,同时也必须持有接口Component的对象的引用,如事例中Decorator。
    ● 具体的装饰角色(Decorator类的子类,可以有一个,也可以有多个):
    这些类继承了类Decorator,实现了Component接口,描述了具体的装饰过程,如UpLogger、XMLLogger。

    1.抽象装饰器基类Decorator

    abstract class Decorator implements ILogger{
        protected ILogger logger;
        public Decorator(ILogger logger){
            this.logger = logger;
        }
    }
    

    2.具体装饰类

    //信息大写装饰类UpLogger
    class UpLogger extends Decorator{
        public UpLogger(ILogger logger){
            super(logger);
        }
        public void log(String msg) {
            msg = msg.toUpperCase();     //对字符串进行大写装饰
            logger.log(msg);             //再执行已有的日志功能
        }
    }
    
        //XML格式化装饰类XMLLogger
        class XMLLogger extends Decorator{
              public XMLLogger(ILogger logger){
                  super(logger);
              }
              public void log(String msg) {
                  String s = "<msg>\r\n" +
                           "<content>"+msg+"</content>\r\n"+
                            "<time>" + new Date().toString()+ "</time>\r\n"+
                            "</msg>\r\n";
                  logger.log(s);
             }
         }
    

    3.一个简单的测试类

    public class Test {
        public static void main(String[] args)throws Exception {
            ILogger existobj = new FileLogger();     //已有的日志功能
            ILogger newobj= new XMLLogger(existobj); //新的日志装饰类,对existobj装饰
            String s[] = {"how", "are", "you"};        //仿真传送的字符串信息数组
            for(int i=0; i<s.length; i++){
                newobj.log(s[i]);
                Thread.sleep(1000);                  //每隔1 s传送一个新的字符串
            }
            System.out.println("End");
        }
    }
    

    10.组合模式

    文件树型结构示例图
    在这里插入图片描述
    根目录是由两个子目录组成的,第一级子目录由两个文件组成,第二级子目录由两个文件组成,因此树型形式也可以称作组合形式。把叶子节点与目录节点都看成相同性质的节点,只不过目录节点后继节点不为空,而叶子节点后继节点为null。这样就能够对树型结构的所有节点执行相同的操作,这也是组合模式的最大特点。采用组合模式实现文件树型结构的功能,具体代码如下。

    1.定义抽象节点类Node

    abstract class Node{
        protected String name;
        public Node(String name){
            this.name = name;
        }
        public void addNode(Node node)throws Exception{
            throw new Exception("Invalid exception");
        }
        abstract void display();
    }
    

    该类是叶子节点与目录节点的父类,节点名称是name。其主要包括两类方法:一类方法是所有节点具有相同形式、不同内容的方法,这类方法要定义成抽象方法,如display();另一类方法是目录节点必须重写,叶子节点无需重写的方法,相当于为叶子节点提供了默认实现,如addNode()方法,因为叶子对象没有该功能,所以可以通过抛出异常防止叶子节点无效调用该方法。

    2.文件叶子节点类FileNode

    class FileNode extends Node{
        public FileNode(String name){
            super(name);
        }
        public void display(){
            System.out.println(name);
        }
    }
    

    该类是Node的派生类,仅重写display()方法即可。

    3.目录节点类DirectNode

    class DirectNode extends Node{
        ArrayList<Node> nodeList = new ArrayList();
        public DirectNode(String name){
            super(name);
        }
        public void addNode(Node node)throws Exception{
            nodeList.add(node);
        }
        public void display(){
    
            System.out.println(name);
            for(int i=0; i<nodeList.size(); i++){
                nodeList.get(i).display();
            }
        }
    }
    

    该类从Node抽象类派生后,与原DirectNode类相比,主要有以下不同:①由定义两个集合类成员变量转为定义一个集合类成员变量nodeList; ②由定义两个添加方法转为定义一个添加方法addNode(); ③display()方法中,由两个不同元素的循环转为一个对相同性质节点Node的循环。也就是说,原DirectNode中不论是定义成员变量、成员方法,还是方法内部的功能,都要实时考虑叶子节点、目录节点的不同性,因此它的各种定义一定是双份的。而组合模式中认为叶子节点、目录节点是同一性质的节点,因此与原DirectNode类对比,它的各种定义工作一定是减半的,也易于扩充。

    4.一个简单的测试类

    public class Test {
        public static void createTree(Node node)throws Exception{
            File f = new File(node.name);
            File f2[] = f.listFiles();
            for(int i=0; i<f2.length; i++){
                if(f2[i].isFile()){
                    Node node2 = new FileNode(f2[i].getAbsolutePath());
                    node.addNode(node2);
                }
                if(f2[i].isDirectory()){
                    Node node2 = new DirectNode(f2[i].getAbsolutePath());
                    node.addNode(node2);
                    createTree(node2);
                }
            }
        }
        public static void main(String[] args)throws Exception {
            Node start = new DirectNode("d://data");
            createTree(start);
            start.display();
        }
    }
    

    通过该示例,可得组合模式更一般的UML类图,如图所示。共包括以下三种角色。
    在这里插入图片描述
    ● 抽象节点:Node,是一个抽象类(或接口),定义了个体对象和组合对象需要实现的关于操作其子节点的方法,如add()、remove()、display()等。
    ● 叶节点:Leaf,从抽象节点Node派生,由于本身无后继节点,其add()等方法利用Node抽象类中相应的默认实现即可,只需实现与自身相关的remove()、display()等方法即可。
    ● 组合节点:Component,从抽象节点Node派生,包含其他Composite节点或Leaf节点的引用。
    总之,若某应用可形成树型结构,而且形成树型结构后可对叶节点及中间节点进行统一的操作,那么采用组合模式构建应用功能是一个比较好的选择。

    资料来自《Java设计模式深入研究》

    展开全文
  • 主要介绍了快速理解Java设计模式中的组合模式,具有一定参考价值,需要的朋友可以了解下。
  • java设计模式例题

    千次阅读 2019-01-06 00:01:32
    java设计模式例题 问题1:某软件公司要基于面向对象开发一套图表库,该图表库可以为应用系统提供多种不同外观的图表,例如柱状图(HistogramChart)、饼状图(PieChart)、折线图(LineChart)等。该软件公司图表库设计...
  • Java实现23种设计模式 1. 创建型模式 工厂模式(Factory Pattern) 抽象工厂模式(Abstract Factory Pattern) 单例模式(Singleton Pattern) 建造者模式(Builder Pattern) 原型模式(Prototype Pattern)...
  • java设计模式示例

    2018-10-19 17:38:16
    java设计模式示例 创建型模式(5种):工厂方法模式,抽象工厂模式,单例模式,建造者模式,原型模式。 结构型模式(7种):适配器模式,装饰器模式,代理模式,外观模式,桥接模式,组合模式,享元模式。 行为型...
  • java组合模式例子

    2015-01-07 13:49:49
    java组合模式例子
  • Java常见设计模式总结

    万次阅读 多人点赞 2021-09-18 17:18:54
    设计模式是一套经过反复使用的代码设计经验,目的是为了重用代码、让代码更容易被他人理解、保证代码可靠性。设计模式于己于人于系统都是多赢的,它使得代码编写真正工程化,它是软件工程的基石,如同大厦的一块块...
  • java常用23中设计模式

    2018-10-25 10:23:54
    总体来说设计模式分为三大类: 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元...
  • Java设计模式组合模式

    千次阅读 多人点赞 2019-07-11 11:30:48
    文章目录概述适用场景优点缺点组合-相关设计模式演示 概述 ◆ 定义:将对象组合成树形结构以表示”部分-整体”的层次结构 ◆ 组合模式使客户端对单个对象和组合对象保持一致的方式处理 ◆ 类型:结构型 适用场景 ◆...
  • 这是JAVA设计模式中属于结构模式的部分,包括Flyweight(共享模式)、Bridge(桥模式)、Decorator(装饰模式)、Composite(组合模式)、Adapter(适配器模式)、Proxy(代理模式)、Facade (外观模式)的源代码。其中有些模式中...
  • 33种JAVA设计模式DEMO

    2018-03-07 17:53:08
    组合模式(Composite Pattern) 装饰器模式(Decorator Pattern) 外观模式(Facade Pattern) 享元模式(Flyweight Pattern) 代理模式(Proxy Pattern) 3 行为型模式 这些设计模式特别关注对象之间的通信。 责任...
  • JAVA设计模式——组合模式

    千次阅读 2018-01-22 15:08:26
    组合模式(又称“部分-整体”模式)是一种结构型设计模式组合模式的思想是:将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。 组合模式涉及3个角色: 抽象...
  • 轻松掌握java组合模式

    2020-09-01 12:32:57
    主要帮助大家轻松掌握java组合模式,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • Java设计模式-组合模式

    万次阅读 2019-06-13 09:09:13
    文章目录背景实现总结 背景 ...用组合模式表示他们的树形结构如下。 实现 组织组件:可表示组织或成员 /** * 组织组件抽象类 */ public abstract class OrgComponent { public void add(OrgCom...
  • java23种设计模式.txt

    2020-04-29 16:18:05
    java23种设计模式,包含代码以及视频。创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式...
  • 主要为大家详细介绍了23种设计模式java组合模式,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • 主要介绍了Java运用设计模式中的建造者模式构建项目的实例解析,建造者模式对外隐藏创建过程的产品,使用组合的方式,由指挥者来决定建造的流程,需要的朋友可以参考下
  • 最近学了下组合设计模式,很有心得,于是看了一些java中的用到组合设计的源码。再此分享给大家。 java.awt.container #add(component) 是使用的组合设计模式。下面上两个类的代码。 public abstract class Component...
  • 主要介绍了遵循接口隔离原则的Java设计模式编程,针对Java编程中interface接口方面的编写进行约束,需要的朋友可以参考下
  • 主要介绍了Java经典设计模式之策略模式,简单说明了策略模式的概念、原理并结合实例形式分析了java策略模式的具有用法与相关注意事项,需要的朋友可以参考下
  • 本课程是使用Java来讲解设计模式,考虑到设计模式比较抽象,授课采用 图解+框架源码分析的方式 1) 内容包括: 设计模式七大原则(单一职责、接口隔离、依赖倒转、里氏替换、开闭原则、迪米特法则、合成复用)、UML...
  • NULL 博文链接:https://lvwenwen.iteye.com/blog/1549415

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 179,798
精华内容 71,919
关键字:

java设计模式组合模式

java 订阅