[19/04/26-星期五] GOF23_结构型模式(桥接模式、组合模式)
一、桥接模式(bridge)
场景:商城系统中常见的商品分类,以电脑为例,首先想到使用多层继承结构。
—— 台式机(联想台式机、戴尔台式机、神舟台式机)
电脑 ——笔记本(联想笔记本、戴尔笔记本、神舟笔记本)
——平板电脑(联想pad、戴尔pad、神舟pad)
问题:(1)如果要增加一个新的电脑类型,则要增加各个品牌下的类。
(2)如果要增加一个新的品牌,也要增加各种电脑类型的类
把另一个类的结构作为属性放在这个类中。
核心:处理多层继承结构,处理多维度变化的场景,将各个维度设计成独立的继承结构,使各个维度可以独立的扩展在抽象层建立关联
好处:桥接模式可以取代多层继承的方案。 多层继承违背了单一职责原则,复用性较差,类的个数也非常多。桥接模式可以极大的减少子类的个数,从而降低管理和维护的成本。
桥接模式极大的提高了系统可扩展性,在两个变化维度中任意扩展一个维度,都不需要修改原有的系统,符合开闭原则。
应用场景:
– JDBC驱动程序
– AWT中的Peer架构
– 银行日志管理:
• 格式分类:操作日志、交易日志、异常日志
• 距离分类:本地记录日志、异地记录日志
– 人力资源系统中的奖金计算模块:
• 奖金分类:个人奖金、团体奖金、激励奖金。
• 部门分类:人事部门、销售部门、研发部门。
– OA系统中的消息处理:
• 业务类型:普通消息、加急消息、特急消息
• 发送消息方式:系统内消息、手机短信、邮件
【不用桥接模式UML类图】
【代码】
/*** * "电脑"接口 不用桥接模式 */ package cn.sxt.bridge; public interface Computer { void sale(); } class Desktop implements Computer{ public void sale() { System.out.println("销售台式机!"); } } class Laptop implements Computer{ public void sale() { System.out.println("销售笔记本!"); } } class Pad implements Computer{ public void sale() { System.out.println("销售平板电脑!"); } } //联想系列 class LenovoDesktop extends Desktop{ public void sale() { System.out.println("销售联想台式机!"); } } class LenovoLaptop extends Laptop{ public void sale() { System.out.println("销售联想笔记本!"); } } class LenovoPad extends Pad{ public void sale() { System.out.println("销售联想平板!"); } } //神舟系列 class ShenZhouDesktop extends Desktop{ public void sale() { System.out.println("销售神舟台式机!"); } } class ShenZhouLaptop extends Laptop{ public void sale() { System.out.println("销售神舟笔记本!"); } } class ShenZhouPad extends Pad{ public void sale() { System.out.println("销售神舟平板!"); } } //戴尔系列 class DellDesktop extends Desktop{ public void sale() { System.out.println("销售戴尔台式机!"); } } class DellLaptop extends Laptop{ public void sale() { System.out.println("销售戴尔笔记本!"); } } class DellPad extends Pad{ public void sale() { System.out.println("销售戴尔平板!"); } }
【使用桥接模式UML类图】
【代码】
/*** * "品牌"接口 和各种具体品牌类,又一个维度,2个维度相互独立,x轴 */ package cn.sxt.bridge; public interface Brand { void sale(); } class Lenovo implements Brand{ @Override public void sale() { System.out.println("销售联想牌电脑"); } } class Dell implements Brand{ @Override public void sale() { System.out.println("销售戴尔牌电脑"); } } //在这里,新加品牌“神舟” class ShenZhou implements Brand{ @Override public void sale() { System.out.println("销售神舟牌电脑"); } }
/** * 电脑类型,一个维度,y轴 */ package cn.sxt.bridge; public class Computer2 { protected Brand brand;//持有Brand类的引用,使电脑类Computer2 天然具有品牌类(Brand)的属性 public Computer2(Brand b) {//构造器 this.brand=b; } public void sale() { brand.sale();//调用的是Brand类中brand对象的sale方法 } } class Desktop2 extends Computer2{ public Desktop2 (Brand b) { super(b); } public void sale() { super.sale(); System.out.println("销售台式机"); } } class Laptop2 extends Computer2{ public Laptop2 (Brand b) { super(b); } public void sale() { super.sale(); System.out.println("销售笔记本"); } }
【客户】
/*** * 客户端 */ package cn.sxt.bridge; public class Client { public static void main(String[] args) { //销售联想牌的笔记本电脑,用组合来代替继承,后期扩展很方便 Computer2 c= new Laptop2(new Lenovo()); c.sale(); Computer2 c1= new Desktop2(new ShenZhou()); c1.sale(); } }
二、组合模式(Composite、复合物)
场景:把部分和整体的关系用树形结构来表示,从而使客户端可以用统一的方式处理部分对象和整体对象。
核心:
– 抽象构件(Component)角色: 定义了叶子和容器构件的共同点
– 叶子(Leaf)构件角色:无子节点,叶子节点。
– 容器(Composite)构件角色: 有容器特征,可以包含子节点。非叶子节点
组合模式工作流程分析:
– 组合模式为处理树形结构提供了完美的解决方案,描述了如何将容器和叶子进行递归组合,使得用户在使用时可以一致性的对待容器和叶子。
– 当容器对象的指定方法被调用时,将遍历整个树形结构,寻找也包含这个方法的成员,并调用执行。其中,使用了递归调用的机制对整个结构进行处理。
开发中的应用场景:
– 操作系统的资源管理器
– GUI中的容器层次图
– XML文件解析
– OA系统中,组织结构的处理
– Junit单元测试框架:底层设计就是典型的组合模式,TestCase(叶子)、TestUnite(容器)、Test接口(抽象)
【类图】
【概念】
/*** * 抽象组件,叶子节点和非叶节点的共同点。Component:组成部分、成分、部件 */ package cn.sxt.composite; public interface Component { void operation(); } //叶子组件节点 interface Leaf extends Component{ } //容器组件,非叶节点 interface Composite extends Component{ void add (Component c);//增加节点 void remove(Component c);//删除节点 Component getChild(int index);//获得孩子节点 }
【叶子节点和非叶子节点】
/*** * 抽象文件(AbstractFile)接口,相当于Component接口(抽象组件) */ package cn.sxt.composite; import java.util.ArrayList; import java.util.List; public interface AbstractFile { void killVirus(); } class ImageFile implements AbstractFile{//相当于叶子节点Leaf,单个文本中不能再包含其他文件 private String name; public ImageFile(String name) { this.name=name; } @Override public void killVirus() { System.out.println("查杀图片文件:"+name); } } class TextFile implements AbstractFile{ private String name; public TextFile(String name) { this.name=name; } @Override public void killVirus() { System.out.println("查杀文本文件:"+name); } } class VideoFile implements AbstractFile{ private String name; public VideoFile(String name) { this.name=name; } @Override public void killVirus() { System.out.println("查杀视频文件:"+name); } } class Folder implements AbstractFile{//Folder:文件夹 ,容器组件,非叶子节点 private String name; //定义容器,用来存储子节点 private List<AbstractFile> list =new ArrayList<AbstractFile>(); public Folder(String name) { this.name=name; } public void add(AbstractFile file) { list.add(file); } public void remove(AbstractFile file) { list.remove(file); } public AbstractFile getChild(int index) { return list.get(index); } //杀毒 public void killVirus() {//递归:自己调用自己 System.out.println("对文件夹:"+name+",进行查杀。"); for (AbstractFile absFile : list) { absFile.killVirus(); } } }
【客户端】
/** * */ package cn.sxt.composite; public class CLient { public static void main(String[] args) { AbstractFile f2,f3,f4,f5,f6; Folder f1=new Folder("古装剧收藏"); f2=new ImageFile("SongYi.jpg"); f3=new TextFile("SheDiao.txt"); f4=new VideoFile("zhifou.mp4"); f2.killVirus(); f1.add(f2); f1.add(f3); f1.add(f4); Folder f11=new Folder("胡歌的古装剧"); f5=new ImageFile("xianjian1.avi"); f6=new TextFile("Shenhua.mp4"); f11.add(f5); f11.add(f6); f1.add(f11);//文件夹套文件夹 f1.killVirus();//无论是文件f2还是文件夹f1都是继承同一个AbstractFile接口中的killVirus()方法,只调用这个方法即可 } }