设计模式奇幻漂流:从单例孤岛到工厂流水线

深入浅出设计模式,掌握代码架构的魔法秘籍

大家好!我是你们的技术小伙伴FogLetter,今天我们要一起踏上一段设计模式的奇幻之旅。想象一下,如果你是一名魔法师,设计模式就是你的魔法咒语------它们能让你用更优雅、更强大的方式构建代码世界!

缘起:为什么我们需要设计模式?

还记得刚学编程时,我们总是急于实现功能,把代码堆砌在一起。随着项目越来越复杂,突然发现自己陷入了一片混乱的代码丛林:

  • 修改一个小功能,却引发了一连串的bug
  • 同样的代码在不同地方重复出现
  • 新同事要花好几天才能理解你的代码逻辑

这时候,设计模式就像一张精心绘制的地图,指引我们走出这片丛林。它们不是具体的代码,而是经过千锤百炼的最佳实践,是前辈程序员们智慧的结晶。

单例模式:独一无二的王者

什么是单例模式?

想象一下,在一个王国中只能有一位国王------这就是单例模式的精髓。它确保一个类只有一个实例,并提供一个全局访问点。

闭包实现的单例

javascript 复制代码
const Singleton = (function() {
    let instance;
    
    function createInstance() {
        return {
            name: 'MySingleton',
            timestamp: new Date(),
            sayHello() {
                console.log('Hello from ' + this.name);
            }
        };
    }
    
    return {
        getInstance: function() {
            if (!instance) {
                instance = createInstance();
            }
            return instance;
        }
    };
})();

// 测试一下
const obj1 = Singleton.getInstance();
const obj2 = Singleton.getInstance();
console.log(obj1 === obj2); // true - 确实是同一个实例!

这个实现很巧妙,利用了闭包的特性:instance 变量被封装在 IIFE(立即执行函数表达式)中,外部无法直接访问,只能通过暴露的 getInstance 方法来获取实例。

现代版的简洁实现

javascript 复制代码
const Singleton = (() => {
    let instance;
    return () => instance || (instance = {
        name: 'MySingleton',
        timestamp: new Date()
    });
})();

箭头函数让代码更加简洁,但原理是一样的。

面向对象风格的单例

javascript 复制代码
class Singleton {
    static #instance = null; // 私有静态字段
    
    constructor(name) {
        if (Singleton.#instance) {
            return Singleton.#instance; // 如果已存在实例,直接返回
        }
        this.name = name || 'ClassSingleton';
        Singleton.#instance = this;
    }
    
    static getInstance(name) {
        if (!Singleton.#instance) {
            Singleton.#instance = new Singleton(name);
        }
        return Singleton.#instance;
    }
}

这里使用了 ES2022 的私有字段语法 #instance,确保实例变量不会被外部访问到。

单例模式的实际应用场景

  1. 全局配置管理器 - 整个应用只需要一个配置对象
  2. 数据库连接池 - 避免频繁创建和销毁连接
  3. 日志记录器 - 所有日志通过同一个实例记录
  4. 缓存系统 - 统一管理应用级缓存

注意:单例模式虽然方便,但要谨慎使用。它相当于全局变量,过度使用会导致代码耦合度增加,测试困难。

工厂模式:对象的"生产流水线"

为什么需要工厂?

想象你要开一家披萨店。顾客点餐时,你不需要告诉他们如何制作披萨的每一个细节,只需要说"我要一个海鲜披萨",厨房就会制作出对应的披萨。

这就是工厂模式的思想:针对接口编程,而不是实现

工厂模式实战

假设我们正在开发一个图形绘制应用,需要创建各种形状:

javascript 复制代码
// 圆形
class Circle {
    constructor(radius) {
        this.radius = radius;
    }
    area() {
        return Math.PI * this.radius * this.radius;
    }
}

// 矩形
class Rectangle {
    constructor(width, height) {
        this.width = width;
        this.height = height;
    }
    area() {
        return this.width * this.height;
    }
}

// 形状工厂 - 我们的"厨房"
class ShapeFactory {
    static createShape(type, options) {
        switch (type) {
            case 'circle':
                return new Circle(options.radius);
            case 'rectangle':
                return new Rectangle(options.width, options.height);
            default:
                throw new Error('Unknown shape type');
        }
    }
}

// 点餐啦!
const circle = ShapeFactory.createShape('circle', { radius: 5 });
const rectangle = ShapeFactory.createShape('rectangle', { 
    width: 4, 
    height: 6 
});

console.log(circle.area());    // 78.53981633974483
console.log(rectangle.area()); // 24

工厂模式的优势

  1. 封装创建逻辑:客户端不需要知道对象创建的具体细节
  2. 易于扩展:添加新的形状类型时,只需要修改工厂类
  3. 代码复用:创建逻辑集中在一处,避免重复代码
  4. 解耦合:客户端代码与具体类解耦

实际应用场景

  1. UI组件库:根据配置创建不同的组件
  2. 数据库访问:根据数据库类型创建不同的连接对象
  3. 文件解析器:根据文件格式创建不同的解析器
  4. 游戏开发:根据类型创建不同的敌人或道具

设计模式的哲学思考

针对接口编程,而不是实现

这句话是设计模式的核心思想。让我们通过一个例子来理解:

不好的做法(针对实现):

javascript 复制代码
function calculateArea(shape) {
    if (shape.type === 'circle') {
        return Math.PI * shape.radius * shape.radius;
    } else if (shape.type === 'rectangle') {
        return shape.width * shape.height;
    }
    // 每增加一种新形状,都要修改这个函数
}

好的做法(针对接口):

javascript 复制代码
function calculateArea(shape) {
    return shape.area(); // 只要都有area方法,我就不关心具体实现
}

开闭原则

软件实体应该对扩展开放,对修改关闭。工厂模式完美体现了这一原则:当需要添加新的产品类型时,我们扩展工厂类而不是修改现有代码。

更多设计模式初探

代理模式:找个"替身"帮忙

代理模式为其他对象提供一种代理以控制对这个对象的访问。

javascript 复制代码
// 真实的服务
class RealImage {
    constructor(filename) {
        this.filename = filename;
        this.loadFromDisk();
    }
    
    display() {
        console.log(`Displaying ${this.filename}`);
    }
    
    loadFromDisk() {
        console.log(`Loading ${this.filename} from disk...`);
    }
}

// 代理 - 延迟加载和访问控制
class ProxyImage {
    constructor(filename) {
        this.filename = filename;
        this.realImage = null;
    }
    
    display() {
        if (!this.realImage) {
            this.realImage = new RealImage(this.filename);
        }
        this.realImage.display();
    }
}

// 使用代理
const image = new ProxyImage("photo.jpg");
// 此时图片还没有加载
image.display(); // 第一次调用时才加载并显示

观察者模式:我关心的事情有消息了!

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。

javascript 复制代码
// 主题(被观察者)
class Subject {
    constructor() {
        this.observers = [];
    }
    
    addObserver(observer) {
        this.observers.push(observer);
    }
    
    removeObserver(observer) {
        this.observers = this.observers.filter(obs => obs !== observer);
    }
    
    notify(data) {
        this.observers.forEach(observer => observer.update(data));
    }
}

// 观察者
class Observer {
    constructor(name) {
        this.name = name;
    }
    
    update(data) {
        console.log(`${this.name} received:`, data);
    }
}

// 使用示例
const newsSubject = new Subject();

const reader1 = new Observer('小明');
const reader2 = new Observer('小红');

newsSubject.addObserver(reader1);
newsSubject.addObserver(reader2);

// 发布新闻
newsSubject.notify('JavaScript 新特性发布!');
// 小明 received: JavaScript 新特性发布!
// 小红 received: JavaScript 新特性发布!

发布订阅模式:更松散的耦合

发布订阅模式是观察者模式的变体,通过事件通道来通信,发布者和订阅者不需要知道彼此的存在。

javascript 复制代码
class EventEmitter {
    constructor() {
        this.events = {};
    }
    
    on(event, listener) {
        if (!this.events[event]) {
            this.events[event] = [];
        }
        this.events[event].push(listener);
    }
    
    emit(event, data) {
        if (this.events[event]) {
            this.events[event].forEach(listener => listener(data));
        }
    }
    
    off(event, listener) {
        if (this.events[event]) {
            this.events[event] = this.events[event].filter(l => l !== listener);
        }
    }
}

// 使用示例
const eventBus = new EventEmitter();

// 订阅者A
const subscriberA = (data) => {
    console.log('订阅者A收到消息:', data);
};

// 订阅者B
const subscriberB = (data) => {
    console.log('订阅者B收到消息:', data);
};

// 订阅事件
eventBus.on('news', subscriberA);
eventBus.on('news', subscriberB);

// 发布事件
eventBus.emit('news', '设计模式真的太有用了!');

// 输出:
// 订阅者A收到消息: 设计模式真的太有用了!
// 订阅者B收到消息: 设计模式真的太有用了!

设计模式的学习建议

1. 理解意图,而非死记硬背

不要机械记忆代码,要理解每个模式要解决的问题和它的核心思想。

2. 结合实际场景学习

在真实项目中识别可以使用设计模式的场景,比单纯学习理论更有效。

3. 避免过度设计

不是所有地方都需要设计模式。简单的需求用简单的代码,当变化和扩展成为真正需求时再考虑模式。

4. 学习重构到模式

先写出可工作的代码,然后通过重构逐步引入合适的设计模式。

结语

设计模式就像是编程世界中的棋谱或者武功秘籍------它们不是银弹,但掌握了它们,你就能在面对复杂设计问题时游刃有余。

记住,学习设计模式的旅程不是一蹴而就的。就像学武功一样,先理解招式,然后在实战中慢慢体会内功心法。最终,你会达到"手中无剑,心中有剑"的境界------不再机械套用模式,而是自然地将模式思想融入到你的设计中。

希望这篇笔记能帮助你在设计模式的学习之路上有所收获!如果你有任何问题或想法,欢迎在评论区交流讨论~

相关推荐
牧野星辰2 小时前
eslint你不懂的都在这里,不信你进来看嘛~
前端·eslint
ohyeah2 小时前
深入理解 JavaScript 数组:从创建到遍历的完整指南
前端·javascript
逛逛GitHub2 小时前
GitHub 开源 AI 好玩神器,自动记录你的一天。
前端·github
hollyhuang2 小时前
正则校验:校验只能输入数字且首位不能是0
前端
一室易安2 小时前
模仿elementUI 中Carousel 走马灯卡片模式 type=“card“ 的自定义轮播组件 图片之间有宽度
前端·javascript·elementui
脸大是真的好~2 小时前
黑马JAVAWeb -Vue工程化 - Element Plus- 表格-分页条-中文语言包-对话框-Form表单
前端·javascript·vue.js
一个小潘桃鸭2 小时前
记录:echarts tooltip内容过多时,会导致部分内容遮挡
前端
小满zs2 小时前
Next.js第四章(路由导航)
前端
进击的野人3 小时前
深入理解 CSS4 新特性:CSS 变量
前端·css