您现在的位置是:首页 >技术杂谈 >设计模式之结构性设计模式网站首页技术杂谈
设计模式之结构性设计模式
1.结构型设计模式的概述和分类
结构型设计模式是指用于设计对象和类之间关系的一组设计模式。它们通常涉及如何组织代码以达到最佳的灵活性和可维护性。
结构型设计模式可以分为以下几类:
- 适配器模式(Adapter Pattern):用于将一个类的接口转换为客户端希望的另一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的类可以协同工作。
- 桥接模式(Bridge Pattern):用于将抽象部分与实现部分分离,使得它们可以独立变化而互不影响。
- 组合模式(Composite Pattern):用于将对象组合成树形结构,以表示“整体-部分”的层次结构。组合模式使得客户端可以将单个对象和组合对象一视同仁。
- 装饰器模式(Decorator Pattern):用于在不改变对象接口的前提下,动态地为对象添加额外的职责。装饰器模式相比于生成子类更加灵活,也更加容易扩展。
- 外观模式(Facade Pattern):为复杂的子系统提供一个简单的接口,以便于客户端使用。
- 享元模式(Flyweight Pattern):通过共享大量的细粒度对象来减少内存使用和对象创建的开销。
- 代理模式(Proxy Pattern):用于为其他对象提供一种代理,以控制对这个对象的访问。
这些模式可以结合使用,例如我们可以在适配器模式和代理模式之间进行选择,以便在需要时动态地添加或删除代理。或者我们可以在装饰器模式和外观模式之间进行选择,以便在不需要的情况下隐藏一些复杂性。
2.适配器模式
适配器模式是一种结构型设计模式,它允许将一个接口转换为另一个客户端所期望的接口。它通常用于解决两个不兼容接口之间的问题,或者将旧接口适配为新接口。
在适配器模式中,适配器作为一个中间层,将客户端的请求传递给被适配对象,然后将被适配对象的响应传递回客户端。适配器本质上是一个实现了客户端所需接口的包装器,它将客户端的请求转换为被适配对象所能理解的格式。
适配器模式的常见实现方式有两种:类适配器和对象适配器。类适配器使用多重继承来适配两个不同的接口,而对象适配器则通过包含一个被适配对象的实例来实现接口转换。
适配器模式在实际应用中非常广泛。例如,许多软件包含了一些基于旧版API的代码,而这些API在新版中已被淘汰。在这种情况下,可以使用适配器模式将旧版API适配为新版API,以保持代码的兼容性。另外,适配器模式也经常用于将不同的数据库API适配为统一的接口,从而方便开发人员在不同的数据库中切换。
下面是一个适配器模式的示例代码,假设我们有一个外部库的类,但它的接口不符合我们的需求。我们可以创建一个适配器类,使其实现我们需要的接口,并将外部库类的对象作为其成员变量。适配器类的方法将调用外部库类的方法,并将其结果转换为适合我们的接口。
// 外部库的类,其接口不符合我们的需求
public class ExternalLibraryClass {
public void doSomethingComplicated() {
// ...
}
}
// 我们需要一个能够调用doSomethingComplicated()方法的接口
public interface ComplicatedInterface {
void doSomething();
}
// 适配器类,将ExternalLibraryClass适配为ComplicatedInterface
public class Adapter implements ComplicatedInterface {
private ExternalLibraryClass externalLibrary;
public Adapter(ExternalLibraryClass externalLibrary) {
this.externalLibrary = externalLibrary;
}
@Override
public void doSomething() {
externalLibrary.doSomethingComplicated();
}
}
// 使用适配器来调用外部库的方法
public class Client {
public static void main(String[] args) {
ExternalLibraryClass externalLibrary = new ExternalLibraryClass();
Adapter adapter = new Adapter(externalLibrary);
adapter.doSomething();
}
}
3.桥接模式
桥接模式(Bridge Pattern)是一种结构型设计模式,它可以将一个大类或一系列紧密相关的类拆分为抽象和实现两个独立的层次结构,从而能在两个层次中独立地变化。
桥接模式的核心思想是将抽象和实现分离,使它们可以独立地变化,而不会相互影响。在桥接模式中,抽象部分通常定义了一些操作,实现部分实现了这些操作。两个部分可以独立地扩展,而不会影响到对方。
下面是一个简单的桥接模式示例代码,实现了一个 Shape 接口和 DrawAPI 接口,其中 Shape 接口是抽象部分,DrawAPI 接口是实现部分。具体的图形类,如 Circle 和 Rectangle,都继承了 Shape 接口,可以调用 DrawAPI 接口中的方法进行绘制。
interface Shape {
void draw();
}
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Drawing Rectangle");
}
}
class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing Circle");
}
}
abstract class ShapeColor {
protected Shape shape;
public ShapeColor(Shape shape) {
this.shape = shape;
}
abstract public void drawWithColor(String color);
}
class RedShapeColor extends ShapeColor {
public RedShapeColor(Shape shape) {
super(shape);
}
public void drawWithColor(String color) {
System.out.println(color + " ");
shape.draw();
}
}
public class Main {
public static void main(String[] args) {
Shape rectangle = new Rectangle();
Shape circle = new Circle();
ShapeColor redRectangle = new RedShapeColor(rectangle);
ShapeColor redCircle = new RedShapeColor(circle);
redRectangle.drawWithColor("Red");
redCircle.drawWithColor("Red");
}
}
在这个示例中,我们有一个接口 Shape
和两个实现类 Rectangle
和 Circle
。接着,我们创建了一个抽象类 ShapeColor
,它持有一个 Shape
对象,并定义了一个抽象方法 drawWithColor
。我们还定义了一个实现类 RedShapeColor
,它继承自 ShapeColor
,并覆盖了 drawWithColor
方法。在这个方法中,我们打印颜色并调用传递进来的 Shape
对象的 draw
方法。
在 Main
类中,我们创建了一个 Rectangle
对象和一个 Circle
对象,然后使用这两个对象创建了一个 RedShapeColor
对象。最后,我们调用了 drawWithColor
方法并传递了一个颜色参数。
在实际应用中,桥接模式可以用来将抽象部分与其实现部分分离,从而使它们可以独立地变化。一个常见的例子是用来支持多种不同类型的数据库(如 Oracle、MySQL 等)的数据访问框架。在这种情况下,桥接模式可以用来将数据库的访问接口(如 JDBC)与不同类型的数据库的实现分离开来。这使得我们可以通过改变实现部分来支持新的数据库类型,而不必改变客户端代码。
4.组合模式
组合模式是一种结构型设计模式,它允许您将对象组织成树形结构,以表示整体-部分层次结构。组合模式使得客户端可以统一地处理单个对象和对象组合,从而简化了客户端的代码。
组合模式包含两种类型的对象:组合对象和叶子对象。组合对象可以包含其他组合对象和/或叶子对象,而叶子对象是最基本的对象,不能包含其他对象。这些对象可以形成树状结构,其中组合对象是非叶子节点,叶子对象是树的末端节点。
下面是一个简单的组合模式的示例:
// 定义抽象组件类
abstract class Component {
public abstract function operation();
}
// 定义叶子节点类
class Leaf extends Component {
public function operation() {
// 叶子节点操作
}
}
// 定义组合节点类
class Composite extends Component {
private $children = array();
public function add(Component $component) {
$this->children[] = $component;
}
public function remove(Component $component) {
// 从 $children 数组中删除指定组件
}
public function operation() {
// 组合节点操作
foreach ($this->children as $child) {
$child->operation();
}
}
}
在上面的示例中,Component
类是组合模式中的抽象组件类,它定义了一个操作 operation()
。Leaf
类是叶子节点类,它实现了 operation()
方法。Composite
类是组合节点类,它包含了一个数组 $children
,用于存储子节点,以及 add()
和 remove()
方法,用于添加和删除子节点。Composite
类也实现了 operation()
方法,该方法会递归调用所有子节点的 operation()
方法。
组合模式可以应用于许多场景,例如:
- 图形用户界面 (GUI) 组件,如窗口、按钮和文本框等,它们可以嵌套组合以创建更复杂的 GUI 元素。
- 文件系统,其中目录和文件可以被组织成树形结构。
- 组织结构,其中公司可以被组织成一个树形结构,包括部门和员工等。
总之,组合模式可以简化处理对象组合的代码,并提供更灵活的设计,以适应不断变化的需求。
5.装饰器模式
装饰器模式是一种结构型设计模式,它允许在不改变原始对象的情况下,动态地添加对象的行为。这种模式是通过创建一个包装对象来实现的,这个包装对象将一个或多个装饰器对象包装在原始对象的周围。通过这种方式,可以在不改变原始对象的基础上,动态地添加行为、修改行为或移除行为。
在装饰器模式中,有四个核心角色:
-
抽象构件(Component):定义一个对象接口,可以给这些对象动态地添加职责或行为。
-
具体构件(Concrete Component):实现抽象构件接口,即具体要被添加行为的对象。
-
抽象装饰器(Decorator):继承或实现抽象构件接口,并包含一个对抽象构件的引用。
-
具体装饰器(Concrete Decorator):实现抽象装饰器接口,具体实现要添加的行为。
以下是一个简单的装饰器模式示例,以实现一杯咖啡的例子:
// 抽象构件
interface Coffee {
String getDescription();
double getCost();
}
// 具体构件
class Espresso implements Coffee {
@Override
public String getDescription() {
return "Espresso";
}
@Override
public double getCost() {
return 1.99;
}
}
// 抽象装饰器
abstract class CoffeeDecorator implements Coffee {
private final Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
@Override
public String getDescription() {
return coffee.getDescription();
}
@Override
public double getCost() {
return coffee.getCost();
}
}
// 具体装饰器
class Mocha extends CoffeeDecorator {
public Mocha(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + ", Mocha";
}
@Override
public double getCost() {
return super.getCost() + 0.50;
}
}
class Whip extends CoffeeDecorator {
public Whip(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + ", Whip";
}
@Override
public double getCost() {
return super.getCost() + 0.30;
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Coffee espresso = new Espresso();
System.out.println(espresso.getDescription() + " $" + espresso
应用场景:
-
日志记录:在不修改原有业务逻辑的前提下,对系统中的某些操作进行日志记录,方便后续问题排查和数据统计。
-
动态代理:在不改变原有代码结构的情况下,通过代理类来控制访问,比如Spring中的AOP(面向切面编程)就是通过动态代理实现的。
-
缓存:在系统中某些操作被频繁调用时,可以通过装饰器模式来实现缓存,提高系统性能。
-
权限控制:通过装饰器模式来实现权限控制,可以在不修改原有代码结构的情况下,对某些操作进行权限控制。
6.代理模式
代理模式是一种结构型设计模式,它允许通过提供一个代理对象来控制对另一个对象的访问。代理模式可以用来限制对对象的访问、为对象提供额外的功能或者隐藏对象的实现细节。
在代理模式中,有三个主要角色:客户端、代理对象和真实对象。客户端通过代理对象来访问真实对象,代理对象负责处理客户端请求并在必要时将请求传递给真实对象。
以下是一个代理模式的简单示例:
interface Image {
void display();
}
class RealImage implements Image {
private String fileName;
public RealImage(String fileName) {
this.fileName = fileName;
loadFromDisk(fileName);
}
@Override
public void display() {
System.out.println("Displaying " + fileName);
}
private void loadFromDisk(String fileName) {
System.out.println("Loading " + fileName);
}
}
class ImageProxy implements Image {
private RealImage realImage;
private String fileName;
public ImageProxy(String fileName) {
this.fileName = fileName;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(fileName);
}
realImage.display();
}
}
public class Main {
public static void main(String[] args) {
Image image = new ImageProxy("test.jpg");
image.display();
}
}
在上面的示例中,我们定义了一个 Image
接口和它的两个实现类:RealImage
和 ImageProxy
。RealImage
表示实际存在的图像,而 ImageProxy
则是 RealImage
的代理。当客户端通过 ImageProxy
访问 RealImage
时,ImageProxy
将首先检查 RealImage
是否已经存在,如果不存在则创建它,然后调用 RealImage
的 display
方法来显示图像。
代理模式在实际应用中有很多用途,其中一些包括:
- 远程代理:将远程对象封装在代理对象中,以便客户端可以像访问本地对象一样访问它。
- 虚拟代理:延迟加载一个对象的开销,直到它被真正需要。
- 安全代理:控制对对象的访问,以确保只有授权用户可以访问它。
在Java中,代理模式得到了广泛的应用。例如,在Spring框架中,AOP(面向切面编程)就是一种基于代理模式的实现方式。AOP可以通过代理来拦截方法调用并执行特定的操作,例如日志记录、性能监视、安全检查等。