设计模式扫荡-结构型模式-适配器、桥接、装饰器、享元、代理

本文介绍了设计模式中的五种结构型模式:适配器模式用于连接不兼容的接口,桥接模式实现抽象与实现的解耦,组合模式组合对象形成树形结构,装饰器模式动态扩展对象功能,享元模式减少对象创建提高性能。文章详细阐述了每种模式的概念、应用场景、优缺点,并提供了示例代码。
摘要由CSDN通过智能技术生成

设计模式扫荡-结构型模式-适配器、桥接、装饰器、享元、代理

" 结构型设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。"

适配器模式(Adapter Pattern)

适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁,它结合了两个独立接口的功能。

适配器模式意图将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

例如

充电器与某个接口不适配,加一个适配器,使得充电头可以插入并正常使用该接口

在Java中有个典型的应用

Reader作为输入流InputStreamBufferReader之间的适配器

  
		//读取文件 返回文件输入流
    	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 Pattern)

桥接(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)

组合模式(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)

装饰器模式(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)

享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。

享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。

典型的应用有:

  • Java中的String,放在运行时常量池,相同的值共用一个String对象

  • 数据库的连接池,某个连接断开的时候可以回到池中复用,如果连接池中所有连接都被占用,则创建一个新的连接

  • Java线程池

代理模式(Proxy 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开发框架的基础-代理模式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值