" 结构型设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。"
适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁,它结合了两个独立接口的功能。
适配器模式意图将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
例如
充电器与某个接口不适配,加一个适配器,使得充电头可以插入并正常使用该接口
在Java中有个典型的应用
Reader
作为输入流InputStream
和BufferReader
之间的适配器//读取文件 返回文件输入流 FileInputStream fis = new FileInputStream("c:/test.text"); //InputStreamReader作为适配器 接收fis InputStreamReader isr = new InputStreamReader(fis); //BufferedReader通过InputStreamReader接收FileInputStream BufferedReader br = new BufferedReader(isr); //标准使用 String line = br.readLine(); while (line != null && !line.equals("")) { System.out.println(line); } br.close();
桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化
它通过提供抽象化和实现化之间的桥接结构,使用聚合来实现二者的解耦
需求:男孩给女孩送礼物
礼物可以分为温暖的礼物、狂野的礼物… [抽象化表示]
具体礼物有书、花、戒指…
具体礼物就可以分为温暖的书、狂野的书… [具体化表示]
礼物作为抽象,花、书…作为具体
抽象和具体都有无穷的类型…
如何优雅的设计礼物的结构?
如果纯使用继承,会造成类爆炸问题。
采用桥接设计模式
//礼物抽象类 一比一聚合GiftImpl类
public abstract class Gift {
GiftImpl impl;
}
//礼物具体类
public class GiftImpl {
}
//不同种礼物都继承Gift抽象类,并接收GiftImpl类,即接收具体礼物作为参数
public class WarmGift extends Gift {
public WarmGift(GiftImpl impl) {
//Gift抽象类的impl属性
this.impl = impl;
}
}
public class WildGift extends Gift {
public WildGift(GiftImpl impl) {
this.impl = impl;
}
}
//具体礼物 都继承GiftImpl类
public class Book extends GiftImpl {
}
public class Flower extends GiftImpl {
}
调用的时候:
//追妹妹 送礼物
public void chase(MM mm) {
//Gift g = new xxxGift(new GiftImpl)
Gift g = new WarmGift(new Flower());
//送礼物
give(mm, g);
}
public void give(MM mm, Gift g) {
System.out.println(g + "gived!");
}
UML设计图:
说明:Gift类一比一聚合一个GiftImpl(礼物实现类)
即抽象与具体通过聚合进行桥接
优点: 1、抽象和实现的分离。 2、优秀的扩展能力。 3、实现细节对客户透明。
缺点:桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次,一般用于树状结构的封装。
示例 :书、章节、小节
任何树状结构都可以抽象为树干与树叶
//抽象类、可以表示树干也可以表示树叶
abstract class Node {
abstract public void p();
}
//叶子节点
class LeafNode extends Node {
String content; //属性-内容
public LeafNode(String content) {this.content = content;}
@Override
public void p() {
System.out.println(content);
}
}
//树干节点
class BranchNode extends Node {
//一对多聚合
List<Node> nodes = new ArrayList<>();
//或一比一聚合
//Node node ;
String name;
public BranchNode(String name) {this.name = name;}
@Override
public void p() {
System.out.println(name);
}
//树干可以添加树叶
public void add(Node n) {
nodes.add(n);
}
}
UML表示为:
优点: 1、高层模块调用简单。 2、节点自由增加。
缺点:在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则,如在Branch中我们额外定义了add()
方法,而在Node接口中无声明,这明显违反依赖倒置原则
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能
一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
示例
图形分圆形和椭圆形,现在要对具体的图形上个红色,如何优雅的设计这个结构?
示例代码:
接口与实现:
//图形接口
public interface Shape {
void draw();
}
//实现类
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Shape: Rectangle");
}
}
//实现类
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Shape: Circle");
}
}
抽象装饰类:
public abstract class ShapeDecorator implements Shape {
//受保护的 装饰图形
protected Shape decoratedShape;
//构造器
public ShapeDecorator(Shape decoratedShape){
this.decoratedShape = decoratedShape;
}
// 覆写Shape方法
public void draw(){
decoratedShape.draw();
}
}
扩展了 ShapeDecorator
类的实体装饰类 - 用来完成上色目的:
public class RedShapeDecorator extends ShapeDecorator {
//接收要进行装饰的对象 并传给抽象父类进行初始化
public RedShapeDecorator(Shape decoratedShape) {
super(decoratedShape);
}
@Override
public void draw() {
decoratedShape.draw();
//进行装饰(上色)
setRedBorder(decoratedShape);
}
//上色
private void setRedBorder(Shape decoratedShape){
System.out.println("Border Color: Red");
}
}
主方法调用:
Shape circle = new Circle();
//红色装饰器(要装饰的对象)
Shape redCircle = new RedShapeDecorator(new Circle());
Shape redRectangle = new RedShapeDecorator(new Rectangle());
circle.draw();
redCircle.draw();
redRectangle.draw();
UML表示为
优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
缺点:多层装饰比较复杂。
享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。
享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。
典型的应用有:
在代理模式(Proxy Pattern)中,一个类代表另一个类的功能
代理模式意图为其他对象提供一种代理以控制对这个对象的访问。
示例:
现有一台坦克,我想记录该坦克的移动时间,现在我只知道该坦克对象的move()方法并且知道Tank类实现了Movable接口,并且我无法修改Tank类的源码
如何做?
很容易想到通过继承的方式
interface Movable {
void move();
}
public class Tank implements Movable {
@Override
public void move() {
System.out.println("Tank moving claclacla...");
//模拟坦克运行时间 -随机10秒内
try {
Thread.sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//通过继承Tank类的方式测试其move方法执行了多久,即可完成我们的目的
class Tank2 extends Tank {
@Override
public void move() {
long start = System.currentTimeMillis();
//调用父类Tank的move()方法
super.move();
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
在设计模式中,我们强调慎用继承,因为继承带来的耦合度太高。
我们可以通过聚合来替代继承,以降低耦合度
创建时间测试代理类:
//代理类同样实现Moveble接口
class TankTimeProxy implements Movable {
Tank tank; //一比一聚合一个要代理的对象
public TankTimeProxy(Tank tank) {
this.tank = tank;
}
//重写Moveble的move方法,并添加自己的代理逻辑
@Override
public void move() {
long start = System.currentTimeMillis();
tank.move();
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
如果我还要在Tank的move()方法执行过程中添加一些其他业务,如日志记录,怎么做?
很简单,仿照上面的做法即可
class TankLogProxy implements Movable {
Tank tank;
//含参构造器略...
@Override
public void move() {
System.out.println("start moving...");
tank.move();
System.out.println("stopped!");
}
}
那么问题来了,如果我要记录TankTimeProxy
类呢?或者我要计算TankLogProxy
的运行时间呢?… 如果tank类有更多的方法与附加类…那么这样做的感觉就有点排列组合的感觉了…
如何实现代理的各种组合?
把代理的对象改成Movable类型!
class TankTimeProxy implements Movable {
Movable m;
public TankTimeProxy(Movable m) {
this.m = m;
}
@Override
public void move() {
//...同上
}
}
class TankLogProxy implements Movable {
//聚合的属性改成Movable类型
Movable m;
public TankLogProxy(Movable m) {
this.m = m;
}
@Override
public void move() {
//....同上
}
}
通过主方法调用实现这种排列组合的感觉
Tank t = new Tank();
TankTimeProxy ttp = new TankTimeProxy(t); //坦克时间记录
TankLogProxy tlp = new TankLogProxy(ttp); //实现ttp的日志业务
tlp.move();
到这里,我们的静态代理模式就产生了,所有代理类可以代理任何实现了Movable接口的类
其UML表示为
优点: 1、职责清晰。 2、高扩展性。 3、智能化。
缺点:
1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
以上是静态代理模式
事实上,我们的代理可以扩展到动态代理,它比静态代理强大的多
示例,我们的日志代理、运行时间代理可以不局限于代理Movable,而是所有的对象
可以参考Java的动态代理!
JavaEE-Java开发框架的基础-代理模式