0011 设计模式之结构型模式

Posted on Sun, Jun 5, 2022 设计模式 JAVA

本节将介绍设计模式中的另一个大类——结构性模式。

1、适配器模式

1.1 定义

🥝

适配器模式(Adapter Pattern) :将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。

适配器模式包含如下角色:

适配器模式有对象适配器和类适配器两种实现:

1.2 实践

我们来看一下代码实现。

首先编写 Target 接口(这是客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口)代码如下:

package adapter;
/**
*@Desc: 目标类接口
*/
public interface Target {
    public void request();
}

需要适配的类Adaptee代码如下:

package adapter;

/**
 * 需要适配的类
 */
public class Adaptee {
    public void specialRequest() {
        System.out.println("specialRequest() | this is a real request from adaptee!");
    }
}

Adapter(通过在内部包装一个Adaptee对象,把源对象接口转换成目标接口)代码如下:

package adapter;

/**
 * 对象适配器
 */
public class Adapter implements Target {
    private Adaptee adaptee = new Adaptee();

    @Override
    public void request() {
        // 通过specialRequest 对 request 进行适配
        adaptee.specialRequest();
    }
}

因此我们使用的时候如下:

package adapter;

import org.junit.Test;

public class TestEntrance {
    @Test
    public void test() {
        Target target = new Adapter();
        target.request();
    }
}

我们来测试一下:

package adapter;

import org.junit.Test;

public class TestEntrance {
    @Test
    public void test() {
        Target target = new Adapter();
        target.request();
    }
}

输出:

specialRequest() | this is a real request from adaptee!

另一种实现方式是使用类适配器,与对象适配器模式不同的是,类适配器模式是使用继承关系连接到Adaptee类,而不是使用委派关系连接到Adaptee类。

package adapter;

/**
*@Desc: 类适配器
*/
public class Adapter2 extends Adaptee implements Target {
    @Override
    public void request() {
        this.specialRequest();
    }
}

测试一下:

package adapter;

import org.junit.Test;

public class TestEntrance {
    @Test
    public void testAdapter2() {
        Target target = new Adapter2();
        target.request();
    }
}

输出与对象适配器完全一致。

1.3 优点

类适配器模式还具有如下优点:

由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。

对象适配器模式还具有如下优点:一个对象适配器可以把多个不同的适配者适配到同一个目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口。

1.4 缺点

类适配器模式的缺点如下:

对于Java、C#等不支持多重继承的语言,一次最多只能适配一个适配者类,而且目标抽象类只能为抽象类,不能为具体类,其使用有一定的局限性,不能将一个适配者类和它的子类都适配到目标接口。

对象适配器模式的缺点如下:与类适配器模式相比,要想置换适配者类的方法就不容易。如果一定要置换掉适配者类的一个或多个方法,就只好先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。

2. 桥接模式

考虑这样的场景:

要绘制矩形、圆形、椭圆、正方形,我们至少需要4个形状类,但是如果绘制的图形需要具有不同的颜色,如红色、绿色、蓝色等,此时至少有如下两种设计方案:

对于有两个变化维度(即两个变化的原因)的系统,采用方案二来进行设计系统中类的个数更少,且系统扩展更为方便。设计方案二即是桥接模式的应用。桥接模式将继承关系转换为关联关系,从而降低了类与类之间的耦合,减少了代码编写量。

2.1 定义

🥝

桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。

桥接模式包含如下角色:

2.2 实践

我们用桥接模式来实现开头中提到的案例:

如何用一个桥接模式来实现呢?

package bridgepattern;

/**
*@Desc: 对传入的形状上色接口
*/
public interface Color {
    public void paint(String shape);
}

package bridgepattern;

/**
*@Desc: 形状抽象类
*/
public abstract class Shape {
    // 颜色
    Color color;

    // 绘制颜色的抽象方法
    public abstract void draw();

    public void setColor(Color color) {
        this.color = color;
    }

    public Color getColor() {
        return color;
    }
}

下面分别实现不同颜色:

package bridgepattern.color;

import bridgepattern.Color;

public class Green implements Color {
    @Override
    public void paint(String shape) {
        System.out.println("绿色的" + shape);
    }
}
package bridgepattern.color;

import bridgepattern.Color;

public class Red implements Color {
    @Override
    public void paint(String shape) {
        System.out.println("红色的" + shape);
    }
}
package bridgepattern.color;

import bridgepattern.Color;

public class Blue implements Color {
    @Override
    public void paint(String shape) {
        System.out.println("蓝色的" + shape);
    }
}

下面分别实现不同形状:

package bridgepattern.shape;

import bridgepattern.Shape;

public class Circle extends Shape {
    @Override
    public void draw() {
        getColor().paint("圆");
    }
}
package bridgepattern.shape;

import bridgepattern.Shape;

public class Ellipse extends Shape {
    @Override
    public void draw() {
        getColor().paint("椭圆");
    }
}
package bridgepattern.shape;

import bridgepattern.Shape;

public class Rectangle extends Shape {
    @Override
    public void draw() {
        getColor().paint("矩形");
    }
}

我们要绘制一个绿色的圆和椭圆:

package bridgepattern;

import bridgepattern.color.Green;
import bridgepattern.shape.Circle;
import bridgepattern.shape.Ellipse;
import org.junit.Test;

public class TestEntrance {
    @Test
    public void testGreen() {
        // 创建一个绿色
        Color green = new Green();
        // 创建一个圆形/椭圆
        Shape ellipse = new Ellipse();
        Shape circle = new Circle();
        // 上色
        ellipse.setColor(green);
        circle.setColor(green);

        // 打印
        ellipse.draw();
        circle.draw();
    }
}

执行结果:

绿色的椭圆 绿色的圆

如果要绘制蓝色的矩形:

package bridgepattern;

import bridgepattern.color.Blue;
import bridgepattern.shape.Rectangle;
import org.junit.Test;

public class TestEntrance {
    @Test
    public void testBlue() {
        // 创建一个蓝色
        Color blue = new Blue();
        // 创建一个矩形
        Shape rectangle = new Rectangle();
        // 上色
        rectangle.setColor(blue);
        
        // 打印
        rectangle.draw();
    }
}

执行结果:

蓝色的矩形

2.3 使用场景举例

🥝

如果需要开发一个跨平台视频播放器,可以在不同操作系统平台(如Windows、Linux、Unix等)上播放多种格式的视频文件,常见的视频格式包括MPEG、RMVB、AVI、WMV等。就可以使用桥接模式设计该播放器。

2.4 优点

桥接模式的优点:

2.5 缺点

桥接模式的缺点:

3、装饰者模式

一般有两种方式可以实现给一个类或对象增加行为:

3.1 定义

🥝

装饰模式(Decorator Pattern) :动态地给一个对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰模式比生成子类实现更为灵活。其别名也可以称为包装器(Wrapper),与适配器模式的别名相同,但它们适用于不同的场合。根据翻译的不同,装饰模式也有人称之为“油漆工模式”,它是一种对象结构型模式。

装饰模式包含如下角色:

3.2 实践

实例:变形金刚

变形金刚在变形之前是一辆汽车,它可以在陆地上移动。当它变成机器人之后除了能够在陆地上移动之外,还可以说话;如果需要,它还可以变成飞机,除了在陆地上移动还可以在天空中飞翔

package decorator;

public abstract class Transform {
    public abstract void move();
}
package decorator;

public class Car extends Transform {
    @Override
    public void move() {
        System.out.println("car move");
    }
}
package decorator;

public abstract class Changer extends Transform{
    public abstract void move();
}
package decorator;

public class Robot extends Changer {
    private Transform transform;

    public Robot(Transform transform) {
        this.transform = transform;
    }

    @Override
    public void move() {
        System.out.println("robot move.");
    }

    // 扩展方法
    public void say() {
        System.out.println("I'm a robot now.");
    }


}
package decorator;

public class AirPlane extends Changer {
    private Transform transform;

    public AirPlane(Transform transform) {
        this.transform = transform;
    }

    @Override
    public void move() {
        System.out.println("airplane move");
    }

    // 扩展方法
    public void fly() {
        System.out.println("I can fly.");
    }
}

测试一下:

package decorator;

import org.junit.Test;

public class TestEntrance {
    @Test
    public void test() {
        Transform transform = new Car();
        transform.move();

        Robot robot = new Robot(transform);
        robot.say();
        robot.move();

        AirPlane airPlane = new AirPlane(transform);
        airPlane.fly();
        airPlane.move();
    }
}

执行结果:

car move I'm a robot now. robot move. I can fly. airplane move

JDK中的装饰者模式:

BufferedReader input = new BufferedReader(new InputStreamReader(System.in));

3.3 优点

装饰模式的优点:

3.4 缺点

装饰模式的缺点:

4、外观模式

4.1 定义

🥝

外观模式(Facade Pattern):外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。外观模式又称为门面模式,它是一种对象结构型模式。

外观模式包含如下角色:

4.2 实践

假设一台电脑,它包含了 CPU(处理器),Memory(内存) ,Disk(硬盘)这几个部件,若想要启动电脑,则先后必须启动 CPU、Memory、Disk。关闭也是如此。

SubSystem 子系统角色:

package facadepattern.subsystem;

public class CPU {
    public void startup() {
        System.out.println("cpu startup!");
    }

    public void shutdown() {
        System.out.println("cpu shutdown!");
    }
}
package facadepattern.subsystem;

public class Disk {
    public void startup() {
        System.out.println("disk startup!");
    }

    public void shutdown() {
        System.out.println("disk shutdown!");
    }
}
package facadepattern.subsystem;

public class Memory {
    public void startup() {
        System.out.println("memory startup!");
    }

    public void shutdown() {
        System.out.println("memory shutdown!");
    }
}

Facade 外观角色:

package facadepattern.facade;

import facadepattern.subsystem.CPU;
import facadepattern.subsystem.Disk;
import facadepattern.subsystem.Memory;

public class Computer {
    private CPU cpu;
    private Memory memory;
    private Disk disk;

    public Computer() {
        cpu = new CPU();
        memory = new Memory();
        disk = new Disk();
    }

    public void startup() {
        System.out.println("computer is starting...");
        cpu.startup();
        memory.startup();
        disk.startup();
        System.out.println("computer started!");
    }

    public void shutdown() {
        System.out.println("computer is shutting down...");
        cpu.shutdown();
        memory.shutdown();
        disk.shutdown();
        System.out.println("computer has shut down!");
    }
}

测试一下:

package facadepattern;

import facadepattern.facade.Computer;
import org.junit.Test;

public class TestEntrance {
    @Test
    public void test() {
        Computer computer = new Computer();
        computer.startup();
        System.out.println("------");
        computer.shutdown();
    }
}

执行结果:

computer is starting... cpu startup! memory startup! disk startup! computer started!
computer is shutting down... cpu shutdown! memory shutdown! disk shutdown! computer has shut down!

4.3 优点

外观模式的优点

4.4 缺点

外观模式的缺点

5、享元模式

5.1 定义

🥝

享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。

享元模式包含如下角色:

package flyweight;

public abstract class Flyweight {
    public abstract void operate();

}
package flyweight;

public class ConcreteFlyweight extends Flyweight{
    private String state;

    public ConcreteFlyweight(String state) {
        this.state = state;
    }
    @Override
    public void operate() {
        System.out.println("Flyweight[" + state + "] 的operate在工作");
    }

}

package flyweight;

import java.util.HashMap;
import java.util.Map;

public class FlyweightFactory {
    Map<String, Flyweight> flyweightMap = new HashMap<>();

    public Flyweight getFlyweight(String str) {
        if (flyweightMap.get(str) == null) {
            Flyweight fw = new ConcreteFlyweight(str);
            flyweightMap.put(str, fw);
            return fw;
        } else {
            System.out.println("享元池中已经存在,直接使用");
            return flyweightMap.get(str);
        }
    }
}

我们来测试一下:

package flyweight;

import org.junit.Test;

public class TestEntrance {
    @Test
    public void test() {
        FlyweightFactory flyweightFactory = new FlyweightFactory();

        Flyweight fw1 =  flyweightFactory.getFlyweight("one");
        fw1.operate();

        Flyweight fw2 = flyweightFactory.getFlyweight("two");
        fw2.operate();

        Flyweight fw3 = flyweightFactory.getFlyweight("one");
        fw3.operate();

    }
}

执行结果:

Flyweight[one] 的operate在工作 Flyweight[two] 的operate在工作 享元池中已经存在,直接使用 Flyweight[one] 的operate在工作

可见,享元模式中,我们实现了已经有的对象的复用——在享元池中直接获取已存在的对象。

5.2 优点

享元模式的优点

5.3 缺点

享元模式的缺点

5.4 模式应用

享元模式在编辑器软件中大量使用,如在一个文档中多次出现相同的图片,则只需要创建一个图片对象,通过在应用程序中设置该图片出现的位置,可以实现该图片在不同地方多次重复显示。

6、代理模式

在某些情况下,一个客户不想或者不能直接引用一个对 象,此时可以通过一个称之为“代理”的第三者来实现 间接引用。

代理对象可以在客户端和目标对象之间起到 中介的作用并且可以通过代理对象去掉客户不能看到 的内容和服务或者添加客户需要的额外服务(如特性增强)

通过引入一个新的对象(如小图片和远程代理 对象)来实现对真实对象的操作或者将新的对象作为真实对象的一个替身,这种实现机制即为代理模式,通过引入代理对象来间接访问一个对象,这就是代理模式的模式动机

6.1 定义

🥝

代理模式(Proxy Pattern) :给某一个对象提供一个代理,并由代理对象控制对原对象的引用。

代理模式包含如下角色:

6.2 实践

package proxy;

public class Subject {
    public void request() {
        System.out.println("这是真实发出的请求");
    }
}
package proxy;

public class Proxy extends Subject {
    @Override
    public void request() {
        preRequest();
        super.request();
        afterRequest();
    }

    private void preRequest() {
        System.out.println("preRequest:发送请求前先处理一下");
    }

    private void afterRequest() {
        System.out.println("afterRequest: 请求完再处理一下");
    }
}

测试一下:

package proxy;

import org.junit.Test;

public class TestEntrance {
    @Test
    public void test() {
        Proxy proxy = new Proxy();
        proxy.request();
    }
}

执行结果:

preRequest:发送请求前先处理一下 这是真实发出的请求 afterRequest: 请求完再处理一下

可见,我们对subject做了代理后,在代理中可以插入一些方法,实现其他目的。

6.3 优点

代理模式的优点

6.4 缺点

代理模式的缺点

6.5 适用环境

根据代理模式的使用目的,常见的代理模式有以下几种类型:

6.6 动态代理

以上就是6种结构型模式的内容。