设计模式初探 --结构型模式

结构型模式是为解决怎样组装现有的类，设计它们的交互方式，从而达到实现一定的功能目的。结构型模式包容了对很多问题的解决。例如：扩展性（外观、组成、代理、装饰）、封装（适配器、桥接）。

在解决了对象的创建问题之后，对象的组成以及对象之间的依赖关系就成了开发人员关注的焦点，因为如何设计对象的结构、继承和依赖关系会影响到后续程序的维护性、代码的健壮性、耦合性等。

适配器模式

适配器模式（Adapter Pattern）是一种结构型设计模式，其核心作用是将一个类的接口转换为客户端期望的另一种接口，从而解决因接口不兼容而无法协同工作的类之间的协作问题。它类似于现实中的 “电源转换器”，通过中间层接口转换，让原本不匹配的组件能够协同工作。

主要组成部分

目标接口（Target）：客户端期望的接口，定义了需要调用的方法。

被适配者（Adaptee）：需要适配的接口，已存在的类或接口，但其接口与目标不兼容。

适配器（Adapter）：连接目标与被适配者的中间层，通过继承或组合实现接口转换。

实现

类适配器（继承方式）：适配器继承被适配者类，并实现目标接口。灵活性较低

Copy
// 目标接口
interface Target { void request(); }
// 被适配者
class Adaptee { void specificRequest() { /*...*/ } }
// 适配器
class Adapter extends Adaptee implements Target {
@Override void request() { specificRequest(); }
}

对象适配器（组合方式）：适配器持有被适配者的对象实例，通过委托调用方法。

Copy
class Adapter implements Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) { this.adaptee = adaptee; }
@Override void request() { adaptee.specificRequest(); }
}

接口适配器（缺省适配器）：解决目标接口方法过多而客户端仅需部分实现的问题。它通过抽象类作为中间层，为接口方法提供默认实现（通常为空或简单逻辑），允许子类按需重写特定方法，从而避免强制实现所有接口方法的冗余。

Copy
// 目标接口
public interface MultiFunction {
void print();
void scan();
void copy();
}

// 抽象适配器类
public abstract class DefaultAdapter implements MultiFunction {
@Override
public void print() {} // 默认空实现
@Override
public void scan() {}
@Override
public void copy() {}
}

// 具体适配器类
public class PrinterAdapter extends DefaultAdapter {
@Override
public void print() {
System.out.println("打印功能已启用");
}
// scan()和copy()无需重写，直接使用默认空逻辑
}

// 调用
public class Client {
public static void main(String[] args) {
MultiFunction device = new PrinterAdapter();
device.print(); // 输出：打印功能已启用
}
}

外观模式

外观模式（Facade Pattern）是一种结构型设计模式，其核心目标是为复杂的子系统提供一个统一的、简化的接口，从而隐藏系统内部的复杂性，降低客户端与子系统之间的耦合度，提升系统的易用性和可维护性。

主要组成部分

通过一个门面类（Facade） 封装多个子系统的交互逻辑，对外提供高层次的接口。客户端只需与门面类交互，无需直接调用复杂的子系统组件。

门面角色（Facade）：统一协调子系统的调用，对外提供简化接口。

子系统角色（Subsystem Classes）：实现具体功能的模块。

客户端（Client）、

桥接模式

2. 桥接模式 — Graphic Design Patterns

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

第一种设计方案是为每一种形状都提供一套各种颜色的版本。

第二种设计方案是根据实际需要对形状和颜色进行组合

桥接模式将继承关系转换为关联关系，从而降低了类与类之间的耦合，减少了代码编写量。将抽象部分与它的实现部分分离，使它们都可以独立地变化。

桥接模式的结构

桥接设计模式

实现案例

桥接模式（Bridge Pattern）-（最通俗易懂的案例）_桥接器模式实例 - CSDN 博客

组合模式

将这颗树理解成一个大的容器，容器里面包含很多的成员对象，这些对象即可以是容器对象也可以是叶子对象。但是由于容器对象和叶子对象在功能上的区别，使得我们在使用的过程中必须要区分容器对象和叶子对象，但是这样就会给客户带来不必要的麻烦，作为客户，它希望能够一致的对待容器对象和叶子对象。这就是组合模式的设计动机：组合模式定义了如何将容器对象和叶子对象进行递归组合，使得客户在使用的过程中无需区分，可以对它们进行一致的处理。

组合模式（Composite Pattern） 也称为 整体 - 部分（Part-Whole）模式，它的宗旨是通过将单个对象（叶子节点）和组合对象（树枝节点）用相同的接口进行表示，使得客户对单个对象和组合对象的使用具有一致性。

组合模式结构

组合模式分为透明式的组合模式和安全式的组合模式。在该方式中，由于抽象构件声明了所有子类中的全部方法，所以客户端无须区别树叶对象和树枝对象，对客户端来说是透明的。但其缺点是：树叶构件本来没有 Add ()、Remove () 及 GetChild () 方法，却要实现它们（空实现或抛异常），这样会带来一些安全性问题。

透明模式的实现

27 设计模式 —— 组合模式（详解版） - 知乎

Copy
public class CompositePattern {
public static void main(String[] args) {
Component c0 = new Composite();
Component c1 = new Composite();
Component leaf1 = new Leaf("1");
Component leaf2 = new Leaf("2");
Component leaf3 = new Leaf("3");
c0.add(leaf1);
c0.add(c1);
c1.add(leaf2);
c1.add(leaf3);
c0.operation();
}
}
//抽象构件
interface Component {
public void add(Component c);
public void remove(Component c);
public Component getChild(int i);
public void operation();
}
//树叶构件
class Leaf implements Component {
private String name;
public Leaf(String name) {
this.name = name;
}
public void add(Component c) {
}
public void remove(Component c) {
}
public Component getChild(int i) {
return null;
}
public void operation() {
System.out.println("树叶" + name + "：被访问！");
}
}
//树枝构件
class Composite implements Component {
private ArrayList<Component> children = new ArrayList<Component>();
public void add(Component c) {
children.add(c);
}
public void remove(Component c) {
children.remove(c);
}
public Component getChild(int i) {
return children.get(i);
}
public void operation() {
for (Object obj : children) {
((Component) obj).operation();
}
}
}

安全模式实现

由于叶子和分支有不同的接口，客户端在调用时要知道树叶对象和树枝对象的存在，所以失去了透明性。

Leaf 类不需要实现那些对于它来说无用的方法，如 add、remove 和 getChild，这使得设计更加合理且易于理解。同时，这样的设计也避免了潜在的错误

代理模式

控制对一个对象的访问，通过引入一个代理对象来管理这个访问。代理模式允许在不改变原对象接口的情况下，增加额外的功能或控制访问方式。

主要组成部分

Subject（目标接口）：

定义了代理类和真实主题类的共同接口。

这使得客户端可以在不改变代码的情况下，使用代理或者真实主题对象。

Real Subject（真实主题）：

实现了 Subject 接口的具体业务逻辑。

这是客户端真正需要访问的对象。

Proxy（代理）：

也实现了 Subject 接口，并且维护一个对 Real Subject 的引用。

代理类负责在客户端和真实主题之间进行协调，可能在请求前后执行一些额外的操作。

静态代理

一个代理只能服务于一个特定的业务实现类

1. 定义目标接口

首先，定义一个接口，该接口声明了需要被代理的方法。

Copy
JAVApublic interface Subject {
void request();
}

2. 实现真实主题类

创建一个实现了上述接口的具体类，该类包含实际的业务逻辑。

Copy
JAVApublic class RealSubject implements Subject {
@Override
public void request() {
System.out.println("RealSubject: 处理请求");
}
}

3. 创建代理类

代理类也实现相同的接口，并持有一个对真实主题对象的引用。代理类可以在调用真实主题的方法之前或之后添加额外的功能。

Copy
JAVApublic class Proxy implements Subject {
private RealSubject realSubject;

public Proxy(RealSubject realSubject) {
this.realSubject = realSubject;
}

@Override
public void request() {
// 执行预处理操作
System.out.println("Proxy: 执行预处理操作");
realSubject.request();
// 执行后处理操作
System.out.println("Proxy: 执行后处理操作");
}
}

4. 编写客户端代码

客户端通过接口与代理类或真实主题对象交互，无需知道是直接调用还是通过代理。

Copy
JAVApublic class Client {
public static void main(String[] args) {
// 创建真实主题对象
Subject realSubject = new RealSubject();

// 创建代理对象，并将真实主题传递给代理
Subject proxy = new Proxy(realSubject);

// 调用代理的方法
proxy.request();
}
}

动态代理

动态代理采用反射的机制，在运行时创建一个接口类的实例。

Copy
//业务接口
interface DateService {
void add();
void del();
}
// 具体业务类
class DateServiceImplA implements DateService {
@Override
public void add() {
System.out.println("成功添加！");
}

@Override
public void del() {
System.out.println("成功删除！");
}
}
// 代理
class ProxyInvocationHandler implements InvocationHandler {
private DateService service;

public ProxyInvocationHandler(DateService service) {
this.service = service;
}

// this.getClass().getClassLoader() 第一个是类加载器，用来加载代理类。
// service.getClass().getInterfaces() 被代理类实现的所有接口，使得代理对象能够提供与目标对象相同的接口视图。
// this 当前实例（即实现了 InvocationHandler 接口的对象），用于处理对代理对象的方法调用。
public Object getDateServiceProxy() {
return Proxy.newProxyInstance(this.getClass().getClassLoader(), service.getClass().getInterfaces(), this);
}

/* invoke 方法：
重写的 invoke 方法会在代理对象上调用任何方法时触发。它接收三个参数：
Object proxy：代表代理对象本身。
Method method：表示正在被调用的目标方法。
Object[] args：传递给目标方法的参数列表。
在方法体内，首先通过 method.invoke(service, args); 调用了目标对象的实际方法，并将结果保存到 result 变量中。
然后打印出一条日志信息，表明代理对象执行了某个方法，并记录了方法名和返回值。
最后返回目标方法的执行结果。
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
var result = method.invoke(service, args); // 让service调用方法，方法返回值
System.out.println(proxy.getClass().getName() + "代理类执行" + method.getName() + "方法，返回" + result + "，记录日志！");
return result;
}
}

//客户端
public class Test {
public static void main(String[] args) {
DateService serviceA = new DateServiceImplA();
DateService serviceProxy = (DateService) new ProxyInvocationHandler(serviceA).getDateServiceProxy();
serviceProxy.add();
serviceProxy.del();
}
}

装饰模式

装饰模式（Decorator Pattern）-（最通俗易懂的案例）_装饰模式使用例子 - CSDN 博客

装饰模式（Decorator）, 动态地给一个对象添加一些额外的职责，就增加功能来说，装饰模式比生成子类更为灵活。装饰模式属于结构型模式，它是作为现有的类的一个包装。

装饰模式的主要角色包括：

Component（抽象组件）：定义了被装饰对象和装饰器共同的接口。定义一个对象接口，可以给这些对象动态地添加职责。

ConcreteComponent（具体组件）：实现了 Component 接口，是被装饰的对象。定义了一个具体的对象，也可以给这个对象添加一些职责。

Decorator（抽象装饰器）：持有一个 Component 对象的引用，并定义了一个与 Component 具有相同接口的方法，以保持一致性。

ConcreteDecorator（具体装饰器）：实现了 Decorator 定义的方法，在调用被装饰对象的方法前后可以执行一些额外的操作。

具体实现

Copy
// 抽象组件
public interface INoodles {
public void cook();
}

// 具体组件
public class Noodles implements INoodles {

@Override
public void cook() {
System.out.println("的面条");
}
}

// 抽象装饰类
public abstract class NoodlesDecorator implements INoodles {

private INoodles noodles; //添加一个INoodles 的引用

public NoodlesDecorator(INoodles noodles){ //通过构造器来设置INoodles
this.noodles = noodles;
}

@Override
public void cook() {
if (noodles!=null){
noodles.cook();
}
}

}

// 具体装饰类
public class EggDecorator extends NoodlesDecorator {

public EggDecorator(INoodles noodles) {
super(noodles);
}

/**
* 重写父类的cook方法，并添加自己的实现，调用父类的cook方法，此cook方法是通过本类的构造器
* EggDecorator(INoodles noodles)传入的noodles的cook操作
*/
@Override
public void cook() {
System.out.println("加了一个荷包蛋");
super.cook();
}
}

// 具体装饰类
public class BeefDecorator extends NoodlesDecorator {

public BeefDecorator(INoodles noodles) {
super(noodles);
}

@Override
public void cook() {
System.out.println("加了一斤牛肉");
super.cook();
}
}

public class Client {

public static void main(String[] args) {

INoodles noodles1 = new Noodles();
INoodles noodles1WithEgg = new EggDecorator(noodles1);
noodles1WithEgg.cook();
}
}

装饰模式和代理模式的区别

意图不同：装饰模式关注的是增强对象的功能，而代理模式更注重于控制对对象的访问。

实现细节：在装饰模式中，装饰器和被装饰的对象必须实现相同的接口，这样才能保证装饰链中的对象能够相互替换。而在代理模式中，代理类和目标对象也常常实现相同的接口，但这主要是为了保持一致性，并不是必须的条件。

设计考量：使用装饰模式时，设计者更多考虑的是如何在不修改现有代码的情况下扩展功能；而使用代理模式时，则更多地考虑如何保护、管理对资源的访问。