设计模式笔记:中介者模式 (Mediator Pattern)一、一句话概括用一个中介对象来封装一系列对象之间的复杂交互,使得这些对象不再需要显式地相互引用,从而降低它们之间的耦合度。它将网状的通信结构转变为星形的通信结构。
二、为什么需要它 (Why - The Pain Point)想象一个复杂的 GUI 对话框,上面有多个控件:一个文本框(TextBox)、一个按钮(Button)、一个列表框(ListBox)、一个复选框(Checkbox)。它们之间存在复杂的交互逻辑:
当文本框内容为空时,按钮应被禁用。
当在列表框中选择一项时,该项的内容应显示在文本框中。
当复选框被选中时,列表框中的所有项都应被清除。
… 等等
没有中介者模式的糟糕设计:
每个控件都需要直接持有其他相关控件的引用,并直接调用它们的方法。
1234567891011121314151617181920212223// 痛点:每个组件都需要知道其他组件的存在和接口class TextBox { private Button button; private ListBox listBox ...
设计模式笔记:职责链模式 (Chain of Responsibility Pattern)一、一句话概括为请求创建了一个接收者对象的链,并让请求在链上传递,直到链上的某个接收者处理它为止。这避免了将请求的发送者和接收者耦合在一起。
二、为什么需要它 (Why - The Pain Point)想象一个公司的费用报销审批流程:
金额小于等于 500 元,项目经理(Team Leader) 可以直接审批。
金额大于 500 元但小于等于 5000 元,需要部门主管(Department Head) 审批。
金额大于 5000 元,需要 CEO 审批。
如果你用传统的 if-else 或 switch 语句来写这个逻辑:
1234567891011121314151617class ExpenseReport { double amount; }// 痛点:一个集中的处理类,充满了 if-else 逻辑class ApprovalSystem { public void approve(ExpenseReport report) { ...
设计模式笔记:代理模式 (Proxy Pattern)一、一句话概括为另一个对象提供一个替身或占位符,以控制对这个真实对象的访问。这个替身(代理)可以在将请求转发给真实对象的前后,执行一些附加操作。
二、为什么需要它 (Why - The Pain Point)在很多情况下,我们不希望或不能直接访问一个对象。原因可能有很多:
访问控制:我们希望根据用户的权限,决定他是否能调用对象的某些方法。例如,普通用户只能读取数据,而管理员可以修改数据。
延迟加载(懒加载):一个对象的创建成本非常高昂(比如,加载一张高清大图或初始化一个复杂的数据库连接)。我们希望只有在真正需要它的时候才去创建,而不是在一开始就加载。
远程访问:真实对象位于远程服务器上,客户端需要通过网络来调用它的方法。直接进行网络通信非常复杂。
日志记录/性能监控:我们想在不修改原始代码的情况下,记录下对象方法的调用时间、参数、返回值等信息。
缓存:对于一些计算成本高但结果不常变的方法,我们希望将结果缓存起来,下次同样的请求直接返回缓存结果,而不是重新计算。
核心问题:如何在不改变原始对象(或无法改变它)的前提下,为 ...
设计模式笔记:外观模式 (Facade Pattern)一、一句话概括为一整个复杂的子系统提供一个单一的、简化的接口。它就像一个“服务总台”,你只需要告诉它你要做什么,它会帮你协调内部所有部门来完成任务。
二、为什么需要它 (Why - The Pain Point)现代软件系统通常由许多相互协作的类和模块组成,形成一个复杂的子系统。例如,一个家庭影院系统可能包含以下组件:
投影仪 (Projector)
DVD 播放器 (DvdPlayer)
功放 (Amplifier)
灯光 (TheaterLights)
幕布 (Screen)
如果你想看一场电影,你需要执行一系列复杂的操作:
123456789101112131415161718// 痛点:客户端需要了解并操作子系统中的每一个组件Projector projector = new Projector();DvdPlayer dvdPlayer = new DvdPlayer();Amplifier amp = new Amplifier();TheaterLights lights = new TheaterLights( ...
设计模式笔记:桥接模式 (Bridge Pattern)一、一句话概括将抽象部分与它的实现部分相分离,使它们都可以独立地变化。它就像一座桥,连接了两个独立变化的“大陆”。
二、为什么需要它 (Why - The Pain Point)让我们回到之前装饰模式中提到的“类爆炸”问题,但这次情况更复杂。假设我们要开发一个可以绘制不同形状(Shape) 的程序,这些形状可以在不同的操作系统(OS) 上用不同的 API 来绘制。
第一个维度(抽象部分):形状。我们有 Circle, Square, Triangle 等。
第二个维度(实现部分):绘制 API。我们有 WindowsAPI, LinuxAPI, MacOSAPI 等。
如果使用传统的继承方式,我们会怎么做?
123456789101112// 继承方案,导致类爆炸abstract class Shape { abstract void draw(); }// Windows 平台的形状class WindowsCircle extends Shape { /* 使用 Windows API 绘制圆 ...
设计模式笔记:适配器模式 (Adapter Pattern)一、一句话概括将一个类的接口转换成客户端所期望的另一种接口,使得原本因接口不兼容而无法一起工作的类可以协同工作。
二、为什么需要它 (Why - The Pain Point)在软件开发中,我们经常会遇到这样的情况:
复用现有组件:你发现一个非常有用的、经过充分测试的第三方库或旧系统模块,但它的接口(API)与你当前系统所期望的接口完全不同。
系统集成:需要将两个或多个独立的系统集成在一起,但它们的通信接口不匹配。例如,一个系统使用 XML 格式交换数据,而另一个系统使用 JSON。
接口演化:随着系统迭代,某个类的接口可能需要重构,但为了不破坏依赖于旧接口的客户端代码,你需要提供一个过渡方案。
痛点:
直接修改现有组件的源代码来匹配新接口通常是不可行或不明智的,原因如下:
无法访问源码:对于第三方库,你可能没有源代码。
风险高:修改经过稳定运行的代码可能会引入新的 bug。
违反开闭原则:修改现有代码违背了对修改关闭、对扩展开放的原则。
核心问题:如何在不修改任何一方代码的前提下,让两个接口不兼容的类能够“对话”和协 ...
好的,我们来将关于 IndexBuffer 绑定的这个关键知识点,整合到之前那份详尽的笔记中,形成一篇更完整、更权威的参考指南。
学习笔记:OpenGL 顶点数据绑定核心流程与关系本文旨在彻底厘清现代 OpenGL 中 VertexArray (VAO), VertexBuffer (VBO), IndexBuffer (IBO), 以及 glVertexAttribPointer 之间的关系和正确的绑定顺序。
核心比喻:宜家家具组装
最终产品: 一个书架 (你想要渲染的物体)。
你: 总指挥 (开发者)。
VertexBuffer (VBO): 一箱零件 (顶点的位置、颜色、纹理坐标等原始数据)。
IndexBuffer (IBO): 一本装配说明书 (定义顶点的连接顺序来形成三角形)。
glVertexAttribPointer: 一份零件识别与连接指南 (解释 VBO 中原始字节流的布局)。
VertexArray (VAO): 一套完整的项目蓝图/工具箱 (记录以上所有配置的状态容器)。
1. 各组件职责详解
VertexBuffer (VBO - 顶点缓冲对象 ...
学习笔记:OpenGL 批量渲染 (Batch Rendering)1. 核心概念:什么是批量渲染?批量渲染是一种优化技术,其核心思想是将大量独立的、小型的绘制调用 (Draw Call) 合并成少数几个、大型的绘制调用。
问题场景: 想象一下,你要渲染一个由 10,000 个独立的方块(比如粒子、瓦片、UI 元素)组成的场景。
朴素方法: 对每个方块都进行一次完整的渲染流程:Bind VAO -> Bind Shader -> Set Uniforms -> glDrawElements(...)。这将产生 10,000 次绘制调用。
批量渲染方法: 将这 10,000 个方块的顶点数据打包到一个巨大的 VBO 中,然后用一次 glDrawElements 调用将它们全部绘制出来。
为什么绘制调用 (Draw Call) 很昂贵?每一次 glDraw... 调用,CPU 都需要向 GPU 发送指令。这个过程涉及到驱动程序的介入、状态验证、上下文切换等开销。当绘制调用非常频繁时(每秒数千甚至数万次),CPU 会把大量时间花在“准备发号施令”上,而不是让 GPU ...
设计模式笔记:组合模式 (Composite Pattern)一、一句话概括将对象组合成树形结构以表示“部分-整体”的层次结构,并使得客户端能够统一地对待单个对象(叶子)和对象组合(容器)。
二、为什么需要它 (Why - The Pain Point)在很多应用场景中,我们需要处理具有层级结构的数据。最经典的例子就是文件系统:一个目录下既可以包含文件(单个对象),也可以包含其他目录(对象组合),而这些子目录又可以继续包含文件和目录,形成一个无限嵌套的树形结构。
痛点:
如果你不使用组合模式,客户端代码在处理这种结构时会变得非常复杂和混乱。
12345678910111213141516// 痛点:客户端代码需要区分文件和目录,并分别处理public void display(Object node) { if (node instanceof File) { // 处理文件的逻辑 File file = (File) node; System.out.println("File: " + file ...
设计模式笔记:装饰模式 (Decorator Pattern)一、一句话概括在不改变原有对象结构的基础上,动态地为对象添加新的功能。它就像给对象穿上一层又一层的“外套”,每件外套都增加一种新能力。
二、为什么需要它 (Why - The Pain Point)假设你正在开发一个咖啡店的点单系统。一开始,你只有几种基础咖啡:
12345678// 基础咖啡接口interface Coffee { double getCost(); String getDescription();}class Espresso implements Coffee { /* ... */ }class Decaf implements Coffee { /* ... */ }
现在,顾客要求可以加各种调料,如牛奶(Milk)、摩卡(Mocha)、豆浆(Soy)等,每种调料都有自己的价格和描述。
你会怎么做?
糟糕的方案:使用继承
你可能会想为每一种组合创建一个子类:
12345class CoffeeWithMilk extends ...