结构型模式
前言:什么是设计模式
-
设计模式是一套解决软件开发过程中某些常见问题的通用解决方案,是已被反复使用且证明其有效性的设计经验的总结
-
分类如下:
-
创建型模式,共5种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
-
结构型模式,共7种:适配器模式、装饰模式、代理模式、外观模式、桥接模式、组合模式、享元模式
-
行为型模式,共11种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
-
结构型设计模式
- 定义:
结构模式描述如何将类或者对象组织在一起形成特定的结构,以实现更为复杂、强大的功能
结构模式可以分为- 类的结构模式:使用继承来把类、接口等组合起来,以形成更大的结构。类的结构模式是静态的
- 对象的结构模式:描述怎样把各种不同类型的对象组合在一起,以实现新的功能的方法。对象的结构模式是动态的
适配器模式(Adapter Pattern)$\star\star\star$
-
定义 / 目的:
- 把一个类的接口变换成客户端所需要的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作;适配器模式的目的是将接口不同而功能相同或相近的接口加以转换,这里面包括适配器角色补充了一个源角色没有的方法
- 把一个类的接口变换成客户端所需要的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作;适配器模式的目的是将接口不同而功能相同或相近的接口加以转换,这里面包括适配器角色补充了一个源角色没有的方法
-
结构:
- 类的适配器模式:
- 对象的适配器模式:
- 组成
- 目标(Target)角色:客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口
- 源(Adaptee)角色:需要适配的类或适配者类
- 适配器(Adapter)角色:通过包装一个需要适配的对象,把原接口转换成目标接口,必须是具体类
- 好处:
- 可以让任何两个没有关联的类一起运行;
- 可以在不修改原有代码的基础上来复用现有类,很好地符合 “ 开闭原则 ”;
- 增加了类的透明度和灵活性
- 缺点
- 如果目标类/接口比较庞大,需要适配的方法太多,适配器也会变得相当庞大,难以维护
- 如果目标类/接口比较庞大,需要适配的方法太多,适配器也会变得相当庞大,难以维护
- 使用场景
- 系统需要使用现有的类,而此类的接口不符合系统的需要
- 想要建立一个可以重复使用的类.用于一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有很复杂的接口
- 例子
- 类的结构模式(用继承)
package code; interface Target{ public void request1(); public void request2(); } class ConcreteTarget implements Target{ public void request1() { System.out.println("普通请求1\n"); } public void request2() { System.out.println("普通请求2\n"); } } class Adaptee{ public void request1() { System.out.println("特殊请求1\n"); } } class Adapter extends Adaptee implements Target{ // 补上没有的方法,有的方法可以通过继承来复用 public void request2() { System.out.println("特殊请求2\n"); } } class test { public static void main(String[] args) { Target concrete_Target = new ConcreteTarget(); concrete_Target.request1(); concrete_Target.request2(); Target adapter = new Adapter(); adapter.request1(); adapter.request2(); } }- 对象的结构模式(用委派关系连接到 Adaptee 类)
package code; interface Target{ public void request1(); public void request2(); } class ConcreteTarget implements Target{ public void request1() { System.out.println("普通请求1\n"); } public void request2() { System.out.println("普通请求2\n"); } } class Adaptee{ public void request1() { System.out.println("特殊请求1\n"); } } class Adapter implements Target{ // 直接包含一个被适配类的对象 private Adaptee adaptee; public Adapter(Adaptee adaptee) { super(); this.adaptee = adaptee; } public void request1() { adaptee.request1(); } public void request2() { System.out.println("特殊请求2\n"); } } class test { public static void main(String[] args) { Target concrete_Target = new ConcreteTarget(); concrete_Target.request1(); concrete_Target.request2(); Target adapter = new Adapter(new Adaptee()); adapter.request1(); adapter.request2(); } }
合成(Composite)模式(部分-整体(Part-Whole)模式)
-
定义:
- 将对象组织到树结构中,可以用来描述整体与部分的关系,可以使客户端将整体对象与部分对象同等看待(即无差别)
- 将对象组织到树结构中,可以用来描述整体与部分的关系,可以使客户端将整体对象与部分对象同等看待(即无差别)
-
目的:
- 使得用户对单个对象和组合对象的使用具有一致性,无差别对待(举个例子:理发办卡后,无论在总店、分店、加盟店,对于积分卡的使用是一样的,无差别的,而三种店就是三个部件,总店是抽象部件,分店是树枝角色,因为下面还可以有加盟店,而加盟店就是树叶角色)
- 使得用户对单个对象和组合对象的使用具有一致性,无差别对待(举个例子:理发办卡后,无论在总店、分店、加盟店,对于积分卡的使用是一样的,无差别的,而三种店就是三个部件,总店是抽象部件,分店是树枝角色,因为下面还可以有加盟店,而加盟店就是树叶角色)
-
结构
- 透明式
- 安全式
-
组成:
- 抽象构件(Component)角色:这是一个抽象角色,它给参加组合的对象规定一个接口。这个角色给出共有的方法
- 树叶构件(Leaf)角色:在组合中表示叶节点,叶节点没有子节点,定义对象的基本行为
- 树枝构件(Composite)角色:参加组合的有子对象的对象,定义出这样对象的行为
-
好处:
- 使客户端调用简单,无论对单个对象还是组合对象,调用方法都是一样的
- 使客户端调用简单,无论对单个对象还是组合对象,调用方法都是一样的
-
缺点:
- 不容易限制组合中的构件
- 不容易限制组合中的构件
-
使用场景
- 需要描述对象的部分和整体有等级结构
- 需要客户端忽略掉整体对象和部分对象的区别
-
例子
- 透明式
package code; import java.util.Vector; abstract class Graphics { public abstract void draw(); public abstract void add(Graphics g); public abstract void remove(Graphics g); public abstract Graphics getChild(int i); } // 树枝 class Picture extends Graphics { private Vector list = new Vector(10); public void draw() { for (int i = 0; i < list.size(); i++) { // g 为一个小部件 Graphics g = (Graphics)list.get(i); g.draw(); } } /* 聚集管理方法, 增加一个子构件 */ public void add(Graphics g) { list.add(g); } /* 聚集管理方法, 删除一个子构件 */ public void remove(Graphics g) { list.remove(g); } /* 返还一个子构件 */ public Graphics getChild(int i) { return (Graphics)list.get(i); } } class Line extends Graphics { public void draw() { System.out.println("Line is painting...\n"); } public void add(Graphics g) { System.out.println("doing nothing...\n"); } public void remove(Graphics g) { System.out.println("doing nothing...\n"); } public Graphics getChild(int i) {return null;} } class Rectangle extends Graphics { public void draw() { System.out.println("Rectangle is painting...\n"); } public void add(Graphics g) { System.out.println("doing nothing...\n"); } public void remove(Graphics g) { System.out.println("doing nothing...\n"); } public Graphics getChild(int i) {return null;} } class Circle extends Graphics { public void draw() { System.out.println("Circle is painting...\n"); } public void add(Graphics g) { System.out.println("doing nothing...\n"); } public void remove(Graphics g) { System.out.println("doing nothing...\n"); } public Graphics getChild(int i) {return null;} } class test { public static void main(String[] args) { Graphics picture = new Picture(); Graphics line = new Line(); Graphics r = new Rectangle(); picture.add(line); picture.add(r); // 这里对树叶和树枝都是透明的,picture 可以声明为 Graphics 类,树叶树枝有共同接口 // 不好的地方是,树叶中不得不有管理子类的函数 r.draw(); r.add(r); line.draw(); picture.draw(); } }- 安全式
package code; import java.util.Vector; abstract class Graphics { public abstract void draw(); } // 树枝 class Picture extends Graphics { private Vector list = new Vector(10); public void draw() { for (int i = 0; i < list.size(); i++) { // g 为一个小部件 Graphics g = (Graphics)list.get(i); g.draw(); } } /* 聚集管理方法, 增加一个子构件 */ public void add(Graphics g) { list.add(g); } /* 聚集管理方法, 删除一个子构件 */ public void remove(Graphics g) { list.remove(g); } /* 返还一个子构件 */ public Graphics getChild(int i) { return (Graphics)list.get(i); } } class Line extends Graphics { public void draw() { System.out.println("Line is painting...\n"); } } class Rectangle extends Graphics { public void draw() { System.out.println("Rectangle is painting...\n"); } } class Circle extends Graphics { public void draw() { System.out.println("Circle is painting...\n"); } } class test { public static void main(String[] args) { Picture picture = new Picture(); Graphics line = new Line(); Graphics r = new Rectangle(); picture.add(line); picture.add(r); // 这就是不透明不好的地方,picture 不能是 Graphics 的变量,否则没办法调用 add 等函数 // 从而导致没办法用统一接口调用,但好处是树叶类不能调用管理子类的函数 r.draw(); line.draw(); picture.draw(); } }
装饰(Decoration)模式 / 包装(Wrapper)模式
- 定义 / 目的:
- 以对客户端透明的方式扩展对象的功能,是继承关系的一种替代方案
- 以对客户端透明的方式扩展对象的功能,是继承关系的一种替代方案
- 结构:
-
组成:
- 抽象构件(Component)角色:给出一个接口,以规范准备接收附加责任(扩展功能)的对象
- 具体构件(Concrete Component)角色:定义一个准备接收附加责任的类
- 装饰(Decorator)角色:持有一个构件的实例,并定义与抽象构件接口一致的接口
- 具体装饰(Concrete Decorator)角色:负责给构件对象“贴上”附加的责任
-
优点:
- 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
---装饰模式允许系统动态地决定“贴上”一个需要的“装饰”,或者除掉一个不需要的“装饰”。继承关系则不同,继承关系是静态的,它在系统运行前就决定了 - 通过使用不同的具体装饰类以及这些装饰类的排列组合, 可以创造出很多不同的行为的组合
- 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
-
缺点:
- 产生出较多的对象
- 比继承更易出错
-
使用场景
- 需要扩展一个类的功能或给一个类增加附加责任
- 需要动态地给一个对象增加功能,这些功能可以再动态地撤销
- 需要增加由一些基本功能的排列组合而产生的非常大量的功能
-
例子:
- 发票
package code; import java.util.Vector; class OrderLine{ private String ItemName; private int units; private int price; public void printLine() { System.out.println("商品名:" + ItemName + " 数量:" + units + " 价格:" + price + "\n"); } public void setName(String s) { this.ItemName = s; } public void setUnits(int n) { this.units = n; } public void setPrice(int n) { this.price = n; } } // 抽象发票类 abstract class Order { private Vector<OrderLine> items = new Vector(); public void print() { for(int i = 0; i < items.size(); i++) { OrderLine item = (OrderLine)items.get(i); item.printLine(); } } public void addItem(OrderLine o) { items.add(o); } } // 具体发票主部类 class SalesOrder extends Order { public void print() { super.print(); } } // 抽象装饰类 abstract class OrderDecorator extends Order { // 核心就是要有一个发票对象 protected Order order; public OrderDecorator(Order order) { this.order = order; } public abstract void print(); } // 具体装饰类—发票头部 class HeaderDecorator extends OrderDecorator { private String cusName = "Jack"; // 对抽象装饰中发票的对象进行初始化 public HeaderDecorator(Order order) { super(order); } public void print() { // 拓展功能 printHeader(); order.print(); } private void printHeader() { System.out.println("发票头.."); System.out.println("顾客名:" + cusName + " 购物日期:xxx\n"); } } // 具体装饰类—发票尾部 class FooterDecorator extends OrderDecorator { public FooterDecorator(Order order) { super(order); } public void print() { order.print(); // 拓展功能 printFooter(); } private void printFooter() { System.out.println("发票尾.."); } } class test { public static void main(String[] args) { Order order = new SalesOrder(); OrderLine line1 = new OrderLine(); line1.setName("毛巾"); line1.setUnits(1); line1.setPrice(10); order.addItem(line1); order = new HeaderDecorator(new FooterDecorator(order)); order.print(); } }
代理(Proxy)模式
- 定义:
- 给某一个对象提供一个代理,并由代理对象控制对原对象的引用。增加和更换代理类无须修改源代码,符合开闭原则,系统具有较好的灵活性和可扩展性
- 代理模式和装饰模式的异同
-
代理模式和装饰模式的代码实现方式很相同,主要不同点是代理模式关注对被代理对象行为的控制,然而装饰模式关注于在一个对象上动态的添加方法。
-
两种模式中具体实现对客户端来说都是透明的
-
代理模式强调的是限制,装饰模式强调的是增强
-
- 结构:
- 组成:
- 抽象主体角色(Subject):声明了真实主体和代理主体的共同接口,这样一来在任何使用真实主体的地方都可以使用代理主体
- 代理主体(Proxy)角色:代理主体角色内部含有对真实主体的引用,从而可以在任何时候操作真实主体对象;代理主体角色提供一个与真实主体角色相同的接口,以便可以在任何时候都可以替代真实主体;控制真实主体的应用,负责在需要的时候创建真实主体对象(和删除真实主体对象);代理角色通常在将客户端调用传递给真实的主体之前或之后,都要执行某个操作,而不是单纯的将调用传递给真实主体对象
- 真实主体角色(RealSubject)角色:定义了代理角色所代表的真实对象
- 优点:
- 增加和更换代理类无须修改源代码,符合开闭原则,系统具有较好的灵活性和可扩展性
- 增加和更换代理类无须修改源代码,符合开闭原则,系统具有较好的灵活性和可扩展性
- 缺点
- 会产生额外开销
- 会产生额外开销
- 例子:
package code; abstract class Subject{ abstract public void request(); } class realSubject extends Subject{ // 真正做事的类 public void request() { System.out.println("This is realSubject\n"); } } class proxySubject extends Subject{ private realSubject r = null; public void request() { // 代理只是接活儿的中介,真正做事还是要靠 realSubject、 PreAction(); if(r == null) r = new realSubject(); r.request(); PostAction(); } public void PreAction() { System.out.println("前置控制\n"); } public void PostAction() { System.out.println("后置控制\n"); } } class test { public static void main(String[] args) { Subject s = new proxySubject(); s.request(); } }
享元模式(Flyweight)模式
- 定义:
- 享元模式以共享的方式高效地支持大量的细粒度对象的维护与管理
- 单纯享元 + 复合享元
门面(Facade)模式
- 定义:
- 外部与一个子系统的通信必需通过一个统一的门面(facade)对象进行
- 外部与一个子系统的通信必需通过一个统一的门面(facade)对象进行
- 优点:
- 提高子系统的复用性、独立性和可移植性
- 提高子系统的复用性、独立性和可移植性
- 缺点
- 不符合开闭原则,如果要修改某一个子系统的功能,通常门面对象也要一起修改
- 不符合开闭原则,如果要修改某一个子系统的功能,通常门面对象也要一起修改
- 例子:
+
考察一个保安系统的例子,以说明门面模式的应用。首先给出这个系统不使用门面模式的解决方案,然后再将门面模式引入,并对源代码进行改组重构。
一个保安系统由两个录象机、三个电灯、一个遥感器和一个警报器组成。保安系统的操作人员需要经常将这些仪器启动和关闭。首先,在不使用门面模式的情况下,操作这个保安系统的操作员必须直接操作所有的这些部件- 不使用
+ 使用
桥梁(Bridge)模式
- 定义 / 目的:
- 将抽象部分与它的实现部分分离,使它们都可以独立地变化
- 将抽象部分与它的实现部分分离,使它们都可以独立地变化
- 结构:
-
组成:
- 抽象化(Abstraction)角色:抽象化给出的定义,并保存一个对实现化对象的引用,抽象化角色的接口比实现化的接口宽
- 修正抽象化(Refined Abstraction)角色:扩展抽象化角色
- 实现化(Implementor)角色:这个角色给出实现化角色的接口,但不给出具体的实现。必须指出的是,这个接口不一定和抽象化角色的接口定义相同,实际上,这两个接口可以完全不同。实现化角色应当只给出底层操作,而抽象化角色应当只给出基于底层操作的更高一层的操作
- 具体实现化(Concrete Implementor)角色:这个角色给出实现化角色接口的具体实现
-
优点:
- 桥接模式提高了系统的可扩展性,符合 “开-闭原则”
- 桥接模式提高了系统的可扩展性,符合 “开-闭原则”
-
缺点
- 增加系统的理解与设计难度
- 增加系统的理解与设计难度
-
例子:
- 由于这个新的系统设计适当地使用了继承关系来构建两个等级结构内部的结构,并且使用聚合关系来构建两个等级结构之间的关系,因此,这是一个通过使用“组合/聚合复用原则(CARP)来达到”开-闭“原则要求的范例
package code; abstract class Airplane { abstract public void fly(); protected AirplaneMaker airplaneMaker; } // 载客飞机作为飞机种类,属于修正抽象化角色。它的示意性源代码如以下所示。 class PassengerPlane extends Airplane { public void fly() { System.out.println("这是客机,制造商是"); airplaneMaker.move(); System.out.println("\n"); } public PassengerPlane(AirplaneMaker airplaneMaker) { this.airplaneMaker = airplaneMaker; } } // 载货飞机作为飞机的另一个种类,也属于修正抽象化角色。它的示意性源代码如以下所示 class CargoPlane extends Airplane { public void fly() { System.out.println("这是货机,制造商是"); airplaneMaker.move(); System.out.println("\n"); } public CargoPlane(AirplaneMaker airplaneMaker) { this.airplaneMaker = airplaneMaker; } } abstract class AirplaneMaker { abstract public void move(); } class Airbus extends AirplaneMaker { public void move() { System.out.println("空客"); } } class Boeing extends AirplaneMaker { public void move() { System.out.println("波音"); } } class test { public static void main(String[] args) { Airplane a = new PassengerPlane(new Airbus()); a.fly(); Airplane b = new CargoPlane(new Boeing()); b.fly(); } }

浙公网安备 33010602011771号