设计模式-结构型模式(详解)
适配器模式
将一个类的接口转换成客户端期望的另一个接口,解决接口不兼容问题。
适配器模式由四部分组成:
-
客户端:即需要使用目标接口的类
-
目标接口
-
需要适配的类,也就是已经存在好的功能,但客户端通过目标接口没办法使用这个类
-
适配器,会实现目标接口,然后耦合需要适配的类,调用类的功能
分为类适配器和对象适配器
// 目标接口
public interface Target {void request();
}
// 适配者类
public class Adaptee {public void specificRequest() {System.out.println("Adaptee's specificRequest");}
}
// 类适配器
public class Adapter extends Adaptee implements Target {@Overridepublic void request() {specificRequest();}
}// 客户端代码
public class Client {public static void main(String[] args) {Target target = new Adapter();target.request(); // Output: Adaptee's specificRequest}
}
对象适配器: 通过组合,让适配器类持有现有类的实例,并实现目标接口**。对象适配器使用组合关系来实现接口的适配**,较为常用。
// 目标接口
public interface Target {void request();
}
// 适配者类
public class Adaptee {public void specificRequest() {System.out.println("Adaptee's specificRequest");}
}
// 对象适配器
public class Adapter implements Target { //这里不再是和类适配器一样继承适配器,而是在类中装配适配器对象private Adaptee adaptee;public Adapter(Adaptee adaptee) {this.adaptee = adaptee;}@Overridepublic void request() {adaptee.specificRequest();}
}// 客户端代码
public class Client {public static void main(String[] args) {Adaptee adaptee = new Adaptee();Target target = new Adapter(adaptee);target.request(); // Output: Adaptee's specificRequest}
}
Java 日志中的 slf4j 其实就是使用了适配器模式来统一不同日志框架接口,使得我们不需要修改代码就可以替换不同的底层日志实现。
桥接模式
主要的作用是将抽象和实现解耦,使它们可以独立地变化。
我们熟知的JDBC 就使用了桥接模式,JDBC定义了抽象的规范,不同的数据库厂商遵循这些规范,但是它们各自又会有不同的实现。
在使用中,如果我们数据库是 mysql,则传入 com.mysql.jdbc.Driver 驱动实现类,如果要换成 oracle替换实现类为oracle.jdbc.driver.OracleDriver 即可,这就是典型的抽象与实现解耦。
代码实例
实现形状颜色解耦
-
实现化(Implementor):定义颜色接口
public interface Color {void applyColor(); }
-
具体实现化(Concrete Implementor):实现具体颜色
public class RedColor implements Color {@Overridepublic void applyColor() { System.out.println("Applying red color"); //红色} }public class BlueColor implements Color { //蓝色@Overridepublic void applyColor() {System.out.println("Applying blue color");} }
-
抽象化(Abstraction):定义形状接口
public abstract class Shape {protected Color color;public Shape(Color color) {this.color = color;}public abstract void draw(); }
-
细化抽象化(Refined Abstraction):实现具体形状
public class Circle extends Shape {public Circle(Color color) {super(color);}@Overridepublic void draw() {System.out.print("Drawing a circle with ");color.applyColor();} }public class Rectangle extends Shape {public Rectangle(Color color) {super(color);}@Overridepublic void draw() {System.out.print("Drawing a rectangle with ");color.applyColor();} }
-
客户端代码
public class Client {public static void main(String[] args) {// 创建红色圆形Shape redCircle = new Circle(new RedColor());redCircle.draw(); // 输出: Drawing a circle with Applying red color// 创建蓝色矩形Shape blueRectangle = new Rectangle(new BlueColor());blueRectangle.draw(); // 输出: Drawing a rectangle with Applying blue color} }
组合模式
将对象组合成树状结构以表示“整体——部分”的层次关系
以部门为示例,展示组织内的部门和人员信息
// 抽象组织
interface OrganizationComponent { void showDetails();
}// 组织人员
class Employee implements OrganizationComponent {private String name;private String position;public Employee(String name, String position) {this.name = name;this.position = position;}@Overridepublic void showDetails() {System.out.println("Employee: " + name + ", Position: " + position);}
}// 组织部门
class Department implements OrganizationComponent {private String name;private List<OrganizationComponent> components = new ArrayList<>();public Department(String name) {this.name = name;}@Overridepublic void showDetails() {System.out.println("Department: " + name);for (OrganizationComponent component : components) {component.showDetails();}}public void add(OrganizationComponent component) { //往部门添加人员 或 部门components.add(component);}public void remove(OrganizationComponent component) {components.remove(component);}
}// 使用代码
public class Client {public static void main(String[] args) {// 创建人员OrganizationComponent employee1 = new Employee("John Doe", "Developer");OrganizationComponent employee2 = new Employee("Jane Smith", "Designer");OrganizationComponent employee3 = new Employee("Emily Davis", "Manager");// 创建部门Department engineeringDepartment = new Department("Engineering Department");Department designDepartment = new Department("Design Department");Department headDepartment = new Department("Head Department");// 添加人员到部门engineeringDepartment.add(employee1);designDepartment.add(employee2);headDepartment.add(employee3);// 添加子部门到上级部门headDepartment.add(engineeringDepartment);headDepartment.add(designDepartment);// 显示整个组织结构的详情headDepartment.showDetails();// 输出// Department: Head Department// Employee: Emily Davis, Position: Manager// Department: Engineering Department// Employee: John Doe, Position: Developer// Department: Design Department// Employee: Jane Smith, Position: Designer}
}
通过组合模式,提供统一的接口,简化对层次结构的处理,使得使用方代码更加简洁与灵活。
装饰器模式
主要作用是通过创建包装类来实现功能的增强,而不是修改原始类。
通过创建一个装饰器类,该类实现了与原始对象相同的接口,并持有一个原始对象的引用。通过将原始对象传递给装饰器
代理模式中,代理类附加的是跟原始类无关的功能,而在装饰器模式中,装饰器类附加的是跟原始类相关的增强功能。
最典型的装饰器实现就是 Java 中的I/O类库,示例代码如下:
import java.io.*;
public class IOExample {public static void main(String[] args) throws IOException {File file = new File("test.txt");FileInputStream fis = new FileInputStream(file); //读取文件流BufferedInputStream bis = new BufferedInputStream(fis); //fis被BufferedlnputStream 装饰,提供了缓存的功能DataInputStream dis = new DataInputStream(bis); //被 DatalnputStream 装饰,提供了按数据类型读取的功能while (dis.available() > 0) {System.out.println(dis.readLine());}dis.close();}
}
文件流的代码比较复杂,这里就不展示了,我们简单看个多装饰器叠加的代码实现:
// 组件
interface Component {void operation();
}// 具体构件
class ConcreteComponent implements Component {@Overridepublic void operation() {System.out.println("ConcreteComponent operation");}
}// 装饰器
abstract class Decorator implements Component {protected Component component; public Decorator(Component component) {this.component = component;}@Overridepublic void operation() {component.operation();}
}
// 具体装饰器 A
class ConcreteDecoratorA extends Decorator {public ConcreteDecoratorA(Component component) {super(component);}@Overridepublic void operation() {super.operation();addedBehavior();}private void addedBehavior() {System.out.println("ConcreteDecoratorA added behavior");}
}
// 具体装饰器 B
class ConcreteDecoratorB extends Decorator {public ConcreteDecoratorB(Component component) {super(component);}@Overridepublic void operation() {super.operation();addedState();}private void addedState() {System.out.println("ConcreteDecoratorB added state");}
}// 客户端代码
public class Client {public static void main(String[] args) {Component component = new ConcreteComponent();Component decoratorA = new ConcreteDecoratorA(component);Component decoratorB = new ConcreteDecoratorB(decoratorA);decoratorB.operation();// Output:// ConcreteComponent operation// ConcreteDecoratorA added behavior// ConcreteDecoratorB added state}
}
外观模式
也叫门面模式,提供了一个简化的接口,用于访问复杂系统中的一组接口。将复杂系统的功能封装起来,让客户端可以更方便地使用系统功能而不需要了解其内部复杂结构。
举个例子就清晰了。例如 A系统有 a、b、c三个接口,B需要分别调用 a、b、c三个接口,这样比较麻烦,所以A将 a、b、c封装成一个接口给B调用,对B来说使用起来就方便了,这就是外观模式
class CPU { //CPUpublic void start() {System.out.println("CPU 启动");}public void shutdown() {System.out.println("CPU 关闭");}
}
class Memory { //内存public void start() {System.out.println("内存启动");}public void shutdown() {System.out.println("内存关闭");}
}class HardDrive { //硬盘public void start() {System.out.println("硬盘启动");}public void shutdown() {System.out.println("硬盘关闭");}}
class ComputerFacade { //外观类,封装了计算机系统的一组复杂接口,包括 CPU、内存和硬盘的启动和关闭private CPU cpu;private Memory memory;private HardDrive hardDrive;public ComputerFacade() {cpu = new CPU();memory = new Memory();hardDrive = new HardDrive();}public void start() { //启动计算机System.out.println("计算机启动开始");cpu.start();memory.start();hardDrive.start();System.out.println("计算机启动完成");}public void shutdown() { //关闭计算机System.out.println("计算机关闭开始");cpu.shutdown();memory.shutdown();hardDrive.shutdown();System.out.println("计算机关闭完成");}
}
ComputerFacade computerFacade = new ComputerFacade();
computerFacade.start();
// 输出:
// 计算机启动开始
// CPU 启动
// 内存启动
// 硬盘启动
// 计算机启动完成computerFacade.shutdown();
// 输出:
// 计算机关闭开始
// CPU 关闭
// 内存关闭
// 硬盘关闭
// 计算机关闭完成
享元模式
享元模式本质就是对象池,判断是否存在,若存在则取,不存在则添加。
Integer的缓存(-128~127)采用了享元模式。
具体原理可看 Java基础 29.什么是Java的Integer缓冲池?
示例:
假设我们要绘制棋盘上的棋子,棋子的种类有很多,但是每种棋子的形状和颜色是固定的。我们可以使用享元模式来共享相同种类的棋子对象,从而减少对象的创建数量。
// 棋子接口
interface ChessPiece {void setColor(String color);void display(int x, int y);
}
// 具体棋子类
class ConcreteChessPiece implements ChessPiece {private String color;public ConcreteChessPiece(String color) {this.color = color;}@Overridepublic void setColor(String color) {this.color = color;}@Overridepublic void display(int x, int y) {System.out.println("Chess Piece color: " + color + ", position: (" + x + "," + y + ")");}
}
// 享元工厂类
class ChessPieceFactory {private Map<String, ChessPiece> chessPieces;public ChessPieceFactory() {this.chessPieces = new HashMap<>(); //用集合存chessPieces}public ChessPiece getChessPiece(String color) { //返回单例ChessPiece chessPiece = chessPieces.get(color);if (chessPiece == null) {chessPiece = new ConcreteChessPiece(color);chessPieces.put(color, chessPiece);}return chessPiece;}
}
// 客户端代码
public class Client {public static void main(String[] args) {ChessPieceFactory chessPieceFactory = new ChessPieceFactory();ChessPiece blackPiece1 = chessPieceFactory.getChessPiece("black"); ChessPiece blackPiece2 = chessPieceFactory.getChessPiece("black");ChessPiece whitePiece1 = chessPieceFactory.getChessPiece("white");ChessPiece whitePiece2 = chessPieceFactory.getChessPiece("white");blackPiece1.display(1, 2); // 输出:Chess Piece color: black, position: (1,2)blackPiece2.display(3, 4); // 输出:Chess Piece color: black, position: (3,4)whitePiece1.display(5, 6); // 输出:Chess Piece color: white, position: (5,6)whitePiece2.display(7, 8); // 输出:Chess Piece color: white, position: (7,8)}
}
代理模式
作用:在不改变原始类的情况下,为其提供一个代理,以控制对这个对象的访问
(代理模式和装饰器模式很像,其主要不同是,前者是控制原对象的访问,后者是为原对象增强已有类的功能)
核心是创建一个代理类,该类实现了与原始对象相同的接口,并持有一个原始对象的引用。在代理类的方法中,我们可以添加额外的功能,然后将请求转发给原始对象进行处理。
// 图片接口
interface Image {void display();
}
// 具体图片类
class RealImage implements Image {private String filename;public RealImage(String filename) {this.filename = filename;loadImageFromDisk();}private void loadImageFromDisk() {System.out.println("Loading " + filename + " from disk.");}@Overridepublic void display() {System.out.println("Displaying " + filename);}
}
// 代理图片类
class ProxyImage implements Image {private RealImage realImage; //引入具体图片类private String filename; //实现了对原始图片对象的控制,而不需要修改原始图片类的代码public ProxyImage(String filename) {this.filename = filename;}@Overridepublic void display() {if (realImage == null) {realImage = new RealImage(filename);}beforeDisplay();realImage.display();}private void beforeDisplay() {System.out.println("Before displaying " + filename + ", do some pre-processing.");}
}
// 客户端代码
public class Client {public static void main(String[] args) {Image image1 = new ProxyImage("image1.jpg");Image image2 = new ProxyImage("image2.jpg");image1.display(); // 输出:Before displaying image1.jpg, do some pre-processing.// Loading image1.jpg from disk.// Displaying image1.jpgimage2.display(); // 输出:Before displaying image2.jpg, do some pre-processing.// Loading image2.jpg from disk.// Displaying image2.jpg}
}