🎭 程序员的周末:11种设计模式继续藏在你身边

🎭 程序员的周末:11种设计模式继续藏在你身边

Scroll Down

🎭 程序员的周末:11种设计模式继续藏在你身边

上篇文章程序员的一天:12种设计模式藏在你身边中,我们跟着小王度过了工作日,发现了12种设计模式。今天,让我们继续跟着小王,看看他的周末时光里,还有哪些设计模式在默默陪伴着他。

周末主打的就是休养生息,睡到自然醒,然后和老婆逛了大集。今天是冬至,中午和老婆一起包了饺子,吃了饺子(据说南方小土豆吃的是汤圆),下午去商场逛了逛,晚上去永辉超市胖改店捡了一波打折的漏。美好的一天结束,明天继续搬砖。

你可能会想:周末不就是休息吗?还能有什么设计模式?嘿,还真有!就连周末的休闲时光里,也藏着11种设计模式。它们悄无声息地融入我们的生活,让代码更优雅,也让生活更有趣。

🌅 睡到自然醒:创建型模式续篇

抽象工厂模式 - 统一的产品家族

周六早上9:30,小王终于睡到自然醒。他伸了个懒腰,肚子咕咕叫,准备做早餐。打开冰箱一看,里面东西不少:咖啡、面包、鸡蛋、牛奶,还有豆浆、包子、油条…

"要中式早餐还是西式早餐?"小王站在冰箱前纠结了一会儿,“算了,还是做西式早餐吧,咖啡配面包,再来个煎蛋,一套搭配下来,营养又美味。”

等等,你有没有发现?选择早餐套餐的时候,所有食物都是配套的。选西式,就是咖啡+面包+煎蛋;选中式,就是豆浆+包子+油条。不会出现咖啡配包子的混搭情况。

这就是抽象工厂模式——提供一个接口,用于创建一系列相关或相互依赖的对象,而不需要指定它们的具体类。就像选择一套早餐风格,所有食物都遵循同一套搭配规范。

生活中的抽象工厂模式

抽象工厂模式在生活中很常见:

  • 早餐套餐:中式早餐(豆浆、油条、包子)、西式早餐(咖啡、面包、煎蛋),每个风格下所有食物都配套
  • 家具套装:现代风格、古典风格、北欧风格,每个风格下所有家具都配套
  • 服装搭配:商务风、休闲风、运动风,每个风格下所有单品都统一

代码中的抽象工厂模式

// 产品接口
interface Drink {
    String getName();
}

interface MainFood {
    String getName();
}

interface SideDish {
    String getName();
}

// 西式早餐产品
class Coffee implements Drink {
    @Override
    public String getName() {
        return "咖啡";
    }
}

class Bread implements MainFood {
    @Override
    public String getName() {
        return "面包";
    }
}

class FriedEgg implements SideDish {
    @Override
    public String getName() {
        return "煎蛋";
    }
}

// 中式早餐产品
class SoyMilk implements Drink {
    @Override
    public String getName() {
        return "豆浆";
    }
}

class SteamedBun implements MainFood {
    @Override
    public String getName() {
        return "包子";
    }
}

class FriedDoughStick implements SideDish {
    @Override
    public String getName() {
        return "油条";
    }
}

// 早餐抽象工厂
interface BreakfastFactory {
    Drink createDrink();
    MainFood createMainFood();
    SideDish createSideDish();
}

// 西式早餐工厂
class WesternBreakfastFactory implements BreakfastFactory {
    @Override
    public Drink createDrink() {
        return new Coffee();
    }
    
    @Override
    public MainFood createMainFood() {
        return new Bread();
    }
    
    @Override
    public SideDish createSideDish() {
        return new FriedEgg();
    }
}

// 中式早餐工厂
class ChineseBreakfastFactory implements BreakfastFactory {
    @Override
    public Drink createDrink() {
        return new SoyMilk();
    }
    
    @Override
    public MainFood createMainFood() {
        return new SteamedBun();
    }
    
    @Override
    public SideDish createSideDish() {
        return new FriedDoughStick();
    }
}

// 使用示例
public class WeekendBreakfast {
    public static void main(String[] args) {
        // 选择西式早餐
        BreakfastFactory factory = new WesternBreakfastFactory();
        
        // 创建一套统一的早餐
        Drink drink = factory.createDrink();
        MainFood mainFood = factory.createMainFood();
        SideDish sideDish = factory.createSideDish();
        
        System.out.println("早餐搭配:");
        System.out.println("饮品:" + drink.getName());
        System.out.println("主食:" + mainFood.getName());
        System.out.println("配菜:" + sideDish.getName());
        System.out.println("完美搭配,营养又美味!");
    }
}

抽象工厂模式的优势:确保创建的对象属于同一个产品族,保证风格统一。就像选择一套早餐风格,所有食物都自动匹配,不会出现风格混搭的问题。


原型模式 - 克隆一个对象

上午10:30,小王和老婆准备出门逛大集。老婆递过来一张纸:“今天要买的东西有点多,我列了个清单,你照着买就行。”

小王接过清单一看:苹果、香蕉、橙子、青菜、土豆…密密麻麻写了一大堆。他挠了挠头:“这么多?要不我复制一份清单,然后我们分头买,这样快一点。”

老婆点点头:“好主意!”

于是小王拿出手机,咔嚓一拍,清单就复制好了。他删掉一些自己负责的,老婆删掉一些她负责的,分头行动,效率翻倍。

你有没有试过复制购物清单?这就是原型模式的典型应用——通过复制现有实例来创建新实例,而不是通过构造函数。就像复印清单,把原清单复制一份,然后可以在复印件上修改。

生活中的原型模式

原型模式在生活中很常见:

  • 购物清单:复制一份清单,分头采购,提高效率
  • 菜谱复制:看到好吃的菜,复制菜谱,然后根据自己的口味调整
  • 模板文档:从模板创建新文档,然后修改内容

代码中的原型模式

// 原型接口
interface Cloneable {
    Cloneable clone();
}

// 购物清单(原型)
class ShoppingList implements Cloneable {
    private List<String> items;
    
    public ShoppingList() {
        this.items = new ArrayList<>();
    }
    
    public ShoppingList(List<String> items) {
        this.items = new ArrayList<>(items);
    }
    
    public void addItem(String item) {
        items.add(item);
    }
    
    public void removeItem(String item) {
        items.remove(item);
    }
    
    // 克隆方法
    @Override
    public ShoppingList clone() {
        return new ShoppingList(this.items);
    }
    
    public List<String> getItems() {
        return new ArrayList<>(items);
    }
    
    @Override
    public String toString() {
        return "购物清单:" + String.join("、", items);
    }
}

// 使用示例
public class WeekendShopping {
    public static void main(String[] args) {
        // 创建原始清单(原型)
        ShoppingList originalList = new ShoppingList();
        originalList.addItem("苹果");
        originalList.addItem("香蕉");
        originalList.addItem("橙子");
        originalList.addItem("青菜");
        originalList.addItem("土豆");
        originalList.addItem("鸡蛋");
        
        System.out.println("原始清单:" + originalList);
        
        // 克隆清单,分头采购
        ShoppingList wangList = originalList.clone();
        wangList.removeItem("青菜");
        wangList.removeItem("土豆");
        System.out.println("\n小王负责:" + wangList);
        
        ShoppingList wifeList = originalList.clone();
        wifeList.removeItem("苹果");
        wifeList.removeItem("香蕉");
        wifeList.removeItem("橙子");
        System.out.println("老婆负责:" + wifeList);
        
        System.out.println("\n分头采购,效率翻倍!");
    }
}

原型模式的优势:比直接创建对象更高效,特别是当对象创建成本很高时。就像复制清单比重新写一份快多了,而且可以保留原清单。


🛒 逛大集:结构型模式续篇

桥接模式 - 分离抽象和实现

上午11:00,小王和老婆到了大集。老婆看中了一些装饰品,有不同形状的(圆形、方形)和不同材质的(木质、金属)。

"这个圆形的木质装饰品不错,那个方形的金属的也挺好看。"老婆一边挑,一边说。

小王在旁边看着,职业病又犯了:"如果用继承,会有圆形木质、圆形金属、方形木质、方形金属…类太多了!"他小声嘀咕,“不如把形状和材质分开,用组合的方式。”

老婆白了他一眼:“又来了,逛个街还想着代码。”

小王嘿嘿一笑,但心里想:这就是桥接模式啊——将抽象部分与实现部分分离,使它们可以独立变化。就像桥连接两岸,让抽象和实现可以独立演化。

生活中的桥接模式

桥接模式在生活中很常见:

  • 装饰品:形状(抽象)可以用不同材质(实现)制作
  • 手机和外壳:手机(抽象)可以配不同材质的外壳(实现)
  • 咖啡和杯子:咖啡(抽象)可以用不同材质的杯子(实现)装

代码中的桥接模式

// 材质接口(实现)
interface Material {
    String getMaterialName();
}

// 木质实现
class WoodMaterial implements Material {
    @Override
    public String getMaterialName() {
        return "木质";
    }
}

// 金属实现
class MetalMaterial implements Material {
    @Override
    public String getMaterialName() {
        return "金属";
    }
}

// 形状抽象类(抽象)
abstract class Decoration {
    protected Material material;
    
    public Decoration(Material material) {
        this.material = material;
    }
    
    public abstract void display();
}

// 圆形装饰品(具体抽象)
class CircleDecoration extends Decoration {
    public CircleDecoration(Material material) {
        super(material);
    }
    
    @Override
    public void display() {
        System.out.println("圆形装饰品,材质:" + material.getMaterialName());
    }
}

// 方形装饰品(具体抽象)
class SquareDecoration extends Decoration {
    public SquareDecoration(Material material) {
        super(material);
    }
    
    @Override
    public void display() {
        System.out.println("方形装饰品,材质:" + material.getMaterialName());
    }
}

// 使用示例
public class MarketShopping {
    public static void main(String[] args) {
        // 木质圆形装饰品
        Decoration woodCircle = new CircleDecoration(new WoodMaterial());
        woodCircle.display();
        
        // 金属圆形装饰品
        Decoration metalCircle = new CircleDecoration(new MetalMaterial());
        metalCircle.display();
        
        // 木质方形装饰品
        Decoration woodSquare = new SquareDecoration(new WoodMaterial());
        woodSquare.display();
        
        // 金属方形装饰品
        Decoration metalSquare = new SquareDecoration(new MetalMaterial());
        metalSquare.display();
    }
}

桥接模式的优势:避免了类爆炸问题,让抽象和实现可以独立变化。想加新材质?加个材质类就行。想加新形状?加个形状类就行。不用创建所有组合。


组合模式 - 树形结构

中午12:00,小王和老婆买完东西回到家,开始整理采购的物品。有蔬菜类、水果类、肉类,每个类别下还有具体的物品。

"青菜、土豆、西红柿放蔬菜类;苹果、香蕉、橙子放水果类;猪肉、鸡肉放肉类…"老婆一边整理,一边念叨。

小王在旁边帮忙,看着这分类结构,突然想到:“这就像一棵树,类别是节点,具体物品是叶子。不管是类别还是具体物品,都可以用同样的方式操作。”

这就是组合模式——将对象组合成树形结构,以表示"部分-整体"的层次结构。让客户端可以统一处理单个对象和组合对象。

生活中的组合模式

组合模式在生活中很常见:

  • 购物分类:蔬菜类包含具体蔬菜,水果类包含具体水果
  • 组织架构:部门包含子部门和员工
  • 菜单系统:菜单包含子菜单和菜单项

代码中的组合模式

// 组件接口
interface ShoppingItem {
    void display(String indent);
}

// 具体物品(叶子节点)
class Item implements ShoppingItem {
    private String name;
    
    public Item(String name) {
        this.name = name;
    }
    
    @Override
    public void display(String indent) {
        System.out.println(indent + "  " + name);
    }
}

// 类别(组合节点)
class Category implements ShoppingItem {
    private String name;
    private List<ShoppingItem> children = new ArrayList<>();
    
    public Category(String name) {
        this.name = name;
    }
    
    public void add(ShoppingItem item) {
        children.add(item);
    }
    
    public void remove(ShoppingItem item) {
        children.remove(item);
    }
    
    @Override
    public void display(String indent) {
        System.out.println(indent + "  " + name);
        for (ShoppingItem item : children) {
            item.display(indent + "  ");
        }
    }
}

// 使用示例
public class ShoppingOrganization {
    public static void main(String[] args) {
        // 创建购物分类结构
        Category shopping = new Category("今日采购");
        
        Category vegetables = new Category("蔬菜类");
        vegetables.add(new Item("青菜"));
        vegetables.add(new Item("土豆"));
        vegetables.add(new Item("西红柿"));
        
        Category fruits = new Category("水果类");
        fruits.add(new Item("苹果"));
        fruits.add(new Item("香蕉"));
        fruits.add(new Item("橙子"));
        
        Category meat = new Category("肉类");
        meat.add(new Item("猪肉"));
        meat.add(new Item("鸡肉"));
        
        shopping.add(vegetables);
        shopping.add(fruits);
        shopping.add(meat);
        
        // 统一显示,不管是类别还是具体物品
        shopping.display("");
    }
}

组合模式的优势:可以统一处理单个对象和组合对象,代码简洁。就像购物分类,不管是类别还是具体物品,都可以用同样的方式操作。


外观模式 - 简化复杂系统

中午12:30,今天是冬至,小王和老婆准备包饺子。包饺子看起来简单,但实际步骤可不少:和面、调馅、擀皮、包饺子、煮饺子…每个步骤都有门道。

"这么多步骤,想想就头疼。"小王看着一堆食材,有点发怵。

老婆笑着说:“别担心,我们一步步来。和面、调馅、包、煮,虽然步骤多,但我们可以把它们组织好,就像调用一个’包饺子’的方法,背后自动完成所有步骤。”

小王点点头:“对!就像外观模式,把复杂的子系统封装起来,提供一个简单的接口。”

于是他们分工合作:老婆负责和面和调馅,小王负责擀皮和包,最后一起煮。虽然背后做了很多事情,但整个过程就像调用一个统一的"包饺子"方法,简单明了。

"听说南方小土豆今天吃的是汤圆呢。"小王一边包饺子,一边说。

"是啊,北方吃饺子,南方吃汤圆,都是冬至的传统。虽然做法不同,但都可以用外观模式来简化——把和面、调馅、包、煮这些复杂步骤封装起来,提供一个简单的’做汤圆’或’包饺子’接口。"老婆解释道。

这就是外观模式——为复杂的子系统提供一个统一的接口,隐藏子系统的复杂性。就像包饺子或做汤圆,背后做了很多事情,但用户只需要调用一个简单的方法。

生活中的外观模式

外观模式在生活中很常见:

  • 包饺子/做汤圆:冬至包饺子涉及和面、调馅、擀皮、包、煮等多个步骤,但可以封装成一个"包饺子"方法
  • 智能家居:说"我回家了",自动开灯、开空调、播放音乐
  • 外卖下单:点外卖背后涉及选餐、支付、配送等多个系统

代码中的外观模式

// 子系统:和面
class DoughMaker {
    public void makeDough() {
        System.out.println("和面中...面粉加水,揉成光滑面团");
    }
}

// 子系统:调馅
class FillingMaker {
    public void makeFilling(String type) {
        System.out.println("调馅中...准备" + type + "馅料");
    }
}

// 子系统:擀皮
class WrapperMaker {
    public void makeWrappers(int count) {
        System.out.println("擀皮中...制作" + count + "张饺子皮");
    }
}

// 子系统:包饺子
class DumplingMaker {
    public void wrapDumplings(int count) {
        System.out.println("包饺子中...包" + count + "个饺子");
    }
}

// 子系统:煮饺子
class DumplingCooker {
    public void cookDumplings() {
        System.out.println("煮饺子中...水开后下锅,煮至浮起");
    }
}

// 外观类:冬至饺子制作
class WinterSolsticeDumplingMaker {
    private DoughMaker doughMaker;
    private FillingMaker fillingMaker;
    private WrapperMaker wrapperMaker;
    private DumplingMaker dumplingMaker;
    private DumplingCooker cooker;
    
    public WinterSolsticeDumplingMaker() {
        this.doughMaker = new DoughMaker();
        this.fillingMaker = new FillingMaker();
        this.wrapperMaker = new WrapperMaker();
        this.dumplingMaker = new DumplingMaker();
        this.cooker = new DumplingCooker();
    }
    
    // 统一的包饺子接口,隐藏所有复杂步骤
    public void makeDumplings(String fillingType, int count) {
        System.out.println("=== 开始包饺子(冬至特供)===");
        doughMaker.makeDough();
        fillingMaker.makeFilling(fillingType);
        wrapperMaker.makeWrappers(count);
        dumplingMaker.wrapDumplings(count);
        cooker.cookDumplings();
        System.out.println("=== 饺子已煮好,可以开吃了!===\n");
    }
}

// 南方汤圆的外观类(类似结构)
class WinterSolsticeTangyuanMaker {
    private DoughMaker doughMaker;
    private FillingMaker fillingMaker;
    private DumplingMaker tangyuanMaker; // 包汤圆
    private DumplingCooker cooker;
    
    public WinterSolsticeTangyuanMaker() {
        this.doughMaker = new DoughMaker();
        this.fillingMaker = new FillingMaker();
        this.tangyuanMaker = new DumplingMaker();
        this.cooker = new DumplingCooker();
    }
    
    // 统一的做汤圆接口
    public void makeTangyuan(String fillingType, int count) {
        System.out.println("=== 开始做汤圆(冬至特供)===");
        doughMaker.makeDough();
        fillingMaker.makeFilling(fillingType);
        tangyuanMaker.wrapDumplings(count); // 包汤圆
        cooker.cookDumplings(); // 煮汤圆
        System.out.println("=== 汤圆已煮好,可以开吃了!===\n");
    }
}

// 使用示例
public class WinterSolsticeCooking {
    public static void main(String[] args) {
        // 北方:包饺子
        System.out.println("【北方习俗:冬至吃饺子】\n");
        WinterSolsticeDumplingMaker dumplingMaker = new WinterSolsticeDumplingMaker();
        dumplingMaker.makeDumplings("猪肉韭菜", 30);
        
        // 南方:做汤圆
        System.out.println("【南方习俗:冬至吃汤圆】\n");
        WinterSolsticeTangyuanMaker tangyuanMaker = new WinterSolsticeTangyuanMaker();
        tangyuanMaker.makeTangyuan("芝麻", 20);
        
        System.out.println("冬至快乐!无论饺子还是汤圆,都是家的味道!");
    }
}

外观模式的优势:简化了客户端的使用,隐藏了系统的复杂性。就像包饺子或做汤圆,用户不需要知道背后做了什么(和面、调馅、擀皮、包、煮),只需要调用一个简单的方法就行。


享元模式 - 共享细粒度对象

下午2:00,小王和老婆去商场逛了逛。老婆看中了很多衣服,每件衣服都有品牌、颜色、尺码等属性。

"这件白色T恤不错,那件也是白色的,还有这件…"老婆一边挑,一边说。

小王跟在后面,看着满架子的衣服,职业病又犯了:"如果每件衣服都创建一个对象,内存会爆炸的!"他小声嘀咕,“相同品牌和颜色的衣服可以共享一个对象啊。”

老婆回头瞪了他一眼:“又来了,能不能好好逛街?”

小王赶紧闭嘴,但心里还在想:这就是享元模式——运用共享技术有效地支持大量细粒度对象的复用。就像服装库,相同品牌和颜色的衣服只需要存储一份,多个地方可以共享。

生活中的享元模式

享元模式在生活中很常见:

  • 服装库:相同品牌和颜色的衣服只需要存储一份,多个地方可以共享
  • 图标库:相同的图标只需要存储一份,多个地方可以共享
  • 游戏中的粒子:相同类型的粒子可以共享属性

代码中的享元模式

// 享元接口:服装样式
interface ClothingStyle {
    void display(String size, double price);
}

// 具体享元:服装样式
class ConcreteClothingStyle implements ClothingStyle {
    private String brand;
    private String color;
    private String type;
    
    public ConcreteClothingStyle(String brand, String color, String type) {
        this.brand = brand;
        this.color = color;
        this.type = type;
    }
    
    @Override
    public void display(String size, double price) {
        System.out.println("品牌:" + brand + ",颜色:" + color + 
                          ",类型:" + type + ",尺码:" + size + ",价格:¥" + price);
    }
}

// 享元工厂
class ClothingStyleFactory {
    private static Map<String, ClothingStyle> styles = new HashMap<>();
    
    public static ClothingStyle getStyle(String brand, String color, String type) {
        String key = brand + "-" + color + "-" + type;
        
        if (!styles.containsKey(key)) {
            styles.put(key, new ConcreteClothingStyle(brand, color, type));
            System.out.println("创建新样式:" + key);
        } else {
            System.out.println("复用已有样式:" + key);
        }
        
        return styles.get(key);
    }
    
    public static int getStyleCount() {
        return styles.size();
    }
}

// 衣服(使用享元)
class Clothing {
    private String size;
    private double price;
    private ClothingStyle style;
    
    public Clothing(String size, double price, ClothingStyle style) {
        this.size = size;
        this.price = price;
        this.style = style;
    }
    
    public void display() {
        style.display(size, price);
    }
}

// 使用示例
public class MallShopping {
    public static void main(String[] args) {
        // 创建大量衣服,但样式可以共享
        List<Clothing> clothes = new ArrayList<>();
        
        // 使用相同样式创建衣服(不同尺码)
        ClothingStyle style1 = ClothingStyleFactory.getStyle("优衣库", "白色", "T恤");
        clothes.add(new Clothing("S", 99.0, style1));
        clothes.add(new Clothing("M", 99.0, style1));
        clothes.add(new Clothing("L", 99.0, style1));
        clothes.add(new Clothing("XL", 99.0, style1));
        
        // 使用不同样式
        ClothingStyle style2 = ClothingStyleFactory.getStyle("ZARA", "黑色", "连衣裙");
        clothes.add(new Clothing("S", 299.0, style2));
        clothes.add(new Clothing("M", 299.0, style2));
        
        // 复用样式1(不同颜色)
        ClothingStyle style3 = ClothingStyleFactory.getStyle("优衣库", "蓝色", "T恤");
        clothes.add(new Clothing("M", 99.0, style3));
        
        System.out.println("\n总共创建了" + clothes.size() + "件衣服");
        System.out.println("但只创建了" + ClothingStyleFactory.getStyleCount() + "种样式");
        System.out.println("\n显示所有衣服:");
        for (Clothing clothing : clothes) {
            clothing.display();
        }
    }
}

享元模式的优势:大幅减少对象数量,节省内存。就像服装库,1000件相同品牌和颜色的衣服只需要存储一份样式,而不是1000份。


🛍️ 逛商场:行为型模式续篇

迭代器模式 - 遍历集合

下午3:00,小王和老婆在商场里逛。服装店、化妆品店、书店…各种店铺,琳琅满目。

"这家店看看,那家店也看看…"老婆兴致勃勃,一家一家地逛。

小王跟在后面,累得不行,但突然想到:“不管什么店铺,我们都是用同样的方式逛——进去看看,挑挑拣拣,然后出来。不需要知道店铺内部是怎么组织的。”

这就是迭代器模式——提供一种方法顺序访问一个聚合对象中的各个元素,而不需要暴露该对象的内部表示。就像逛商场,不管什么店铺,都用同样的方式逛。

生活中的迭代器模式

迭代器模式在生活中很常见:

  • 逛商场:不管什么店铺,都用同样的方式逛
  • 播放列表:不管什么类型的歌曲,都用"下一首"按钮
  • 书架:不管什么书,都按顺序一本本看

代码中的迭代器模式

// 迭代器接口
interface Iterator<T> {
    boolean hasNext();
    T next();
}

// 聚合接口
interface Aggregate<T> {
    Iterator<T> createIterator();
}

// 具体聚合:服装店
class ClothingStore implements Aggregate<String> {
    private String[] items = {"T恤", "牛仔裤", "外套", "鞋子"};
    
    @Override
    public Iterator<String> createIterator() {
        return new StoreIterator<>(items);
    }
}

// 商店迭代器
class StoreIterator<T> implements Iterator<T> {
    private T[] items;
    private int position = 0;
    
    public StoreIterator(T[] items) {
        this.items = items;
    }
    
    @Override
    public boolean hasNext() {
        return position < items.length;
    }
    
    @Override
    public T next() {
        return items[position++];
    }
}

// 具体聚合:化妆品店
class CosmeticsStore implements Aggregate<String> {
    private List<String> items = Arrays.asList("口红", "粉底", "眼影", "香水");
    
    @Override
    public Iterator<String> createIterator() {
        return new StoreListIterator<>(items);
    }
}

// 商店列表迭代器
class StoreListIterator<T> implements Iterator<T> {
    private List<T> items;
    private int position = 0;
    
    public StoreListIterator(List<T> items) {
        this.items = items;
    }
    
    @Override
    public boolean hasNext() {
        return position < items.size();
    }
    
    @Override
    public T next() {
        return items.get(position++);
    }
}

// 使用示例
public class MallShopping {
    public static void main(String[] args) {
        // 服装店
        ClothingStore clothingStore = new ClothingStore();
        
        // 化妆品店
        CosmeticsStore cosmeticsStore = new CosmeticsStore();
        
        // 统一的方式逛店
        System.out.println("逛服装店:");
        visitStore(clothingStore.createIterator());
        
        System.out.println("\n逛化妆品店:");
        visitStore(cosmeticsStore.createIterator());
    }
    
    // 统一的逛店方法,不管什么店铺
    private static <T> void visitStore(Iterator<T> iterator) {
        while (iterator.hasNext()) {
            System.out.println("看看:" + iterator.next());
        }
    }
}

迭代器模式的优势:统一了遍历方式,客户端不需要知道集合的内部结构。就像逛商场,不管什么店铺,都用同样的方式逛。


中介者模式 - 协调对象交互

下午4:00,小王和老婆在商场里,想找一家餐厅吃饭。有多个餐厅,每个餐厅都有不同的特色:川菜、粤菜、日料…

"想吃什么呢?"老婆问。

"不知道,看看有什么吧。"小王有点选择困难。

他们走到美食广场,看到有个推荐台,可以根据偏好推荐餐厅。小王说:"我想吃川菜。"推荐台立刻推荐了几家川菜餐厅。

"如果每个餐厅都直接知道其他餐厅,耦合太紧了。"小王一边看推荐,一边想,“不如用一个中介者来协调。”

这就是中介者模式——用一个中介对象来封装一系列对象之间的交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散。就像美食广场,餐厅不直接竞争,而是通过美食广场这个中介。

生活中的中介者模式

中介者模式在生活中很常见:

  • 美食广场:餐厅通过美食广场协调,而不是直接竞争
  • 房产中介:买卖双方通过中介交易,而不是直接联系
  • 股票交易所:买卖双方通过交易所交易,而不是直接联系

代码中的中介者模式

// 中介者接口
interface FoodCourtMediator {
    void recommendRestaurant(String preference, Customer customer);
    void addRestaurant(Restaurant restaurant);
}

// 具体中介者:美食广场
class FoodCourt implements FoodCourtMediator {
    private List<Restaurant> restaurants = new ArrayList<>();
    
    @Override
    public void addRestaurant(Restaurant restaurant) {
        restaurants.add(restaurant);
    }
    
    @Override
    public void recommendRestaurant(String preference, Customer customer) {
        System.out.println("美食广场:根据您的偏好'" + preference + "',为您推荐:");
        for (Restaurant restaurant : restaurants) {
            if (restaurant.matches(preference)) {
                restaurant.recommend(customer.getName());
            }
        }
    }
}

// 餐厅类
class Restaurant {
    private String name;
    private String specialty;
    private FoodCourtMediator mediator;
    
    public Restaurant(String name, String specialty, FoodCourtMediator mediator) {
        this.name = name;
        this.specialty = specialty;
        this.mediator = mediator;
        mediator.addRestaurant(this);
    }
    
    public boolean matches(String preference) {
        return specialty.contains(preference);
    }
    
    public void recommend(String customerName) {
        System.out.println("  " + name + ":" + specialty + ",适合" + customerName);
    }
}

// 顾客类
class Customer {
    private String name;
    private FoodCourtMediator mediator;
    
    public Customer(String name, FoodCourtMediator mediator) {
        this.name = name;
        this.mediator = mediator;
    }
    
    public String getName() {
        return name;
    }
    
    public void askRecommendation(String preference) {
        System.out.println(name + ":我想吃" + preference);
        mediator.recommendRestaurant(preference, this);
    }
}

// 使用示例
public class MallFoodCourt {
    public static void main(String[] args) {
        // 创建美食广场(中介者)
        FoodCourtMediator foodCourt = new FoodCourt();
        
        // 创建餐厅
        Restaurant restaurant1 = new Restaurant("川味小馆", "川菜", foodCourt);
        Restaurant restaurant2 = new Restaurant("粤式茶餐厅", "粤菜", foodCourt);
        Restaurant restaurant3 = new Restaurant("日式料理", "日料", foodCourt);
        
        // 创建顾客
        Customer customer = new Customer("小王", foodCourt);
        
        // 顾客通过中介者询问推荐
        System.out.println("=== 美食广场推荐 ===\n");
        customer.askRecommendation("川菜");
    }
}

中介者模式的优势:降低了对象之间的耦合,让系统更容易维护和扩展。就像美食广场,餐厅不需要知道其他餐厅的存在,只需要知道美食广场就行。


备忘录模式 - 保存和恢复状态

下午5:00,小王和老婆在商场里试衣服。老婆试了好几件:白色T恤、蓝色牛仔裤、黑色外套…

"这件怎么样?"老婆问。

"还行,再看看其他的吧。"小王说。

试了一圈,老婆突然说:“我觉得第一件白色T恤最好看,想回去看看。”

"啊?"小王有点懵,“第一件是哪个?”

"就是刚才试的那件啊!"老婆有点着急。

小王赶紧翻手机,还好刚才拍了照,记录了试过的衣服。他翻出照片:“是不是这件?”

"对!就是这件!"老婆眼睛一亮。

"还好我记录了,不然就找不到了。"小王松了口气。

这就是备忘录模式——在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后可以恢复。就像试衣记录,可以保存试过的衣服,随时可以回去看。

生活中的备忘录模式

备忘录模式在生活中很常见:

  • 试衣记录:保存试过的衣服,随时可以回去看
  • 购物车:保存选中的商品,随时可以恢复
  • 游戏存档:保存游戏状态,随时可以恢复

代码中的备忘录模式

// 备忘录:保存试衣状态
class TryOnMemento {
    private String clothingName;
    private String size;
    private double price;
    
    public TryOnMemento(String clothingName, String size, double price) {
        this.clothingName = clothingName;
        this.size = size;
        this.price = price;
    }
    
    public String getClothingName() {
        return clothingName;
    }
    
    public String getSize() {
        return size;
    }
    
    public double getPrice() {
        return price;
    }
}

// 原发器:试衣间
class FittingRoom {
    private String currentClothing = "";
    private String currentSize = "";
    private double currentPrice = 0.0;
    
    public void tryOn(String clothing, String size, double price) {
        this.currentClothing = clothing;
        this.currentSize = size;
        this.currentPrice = price;
        System.out.println("试穿:" + clothing + ",尺码:" + size + ",价格:¥" + price);
    }
    
    public TryOnMemento save() {
        System.out.println("记录试穿的衣服...");
        return new TryOnMemento(currentClothing, currentSize, currentPrice);
    }
    
    public void restore(TryOnMemento memento) {
        this.currentClothing = memento.getClothingName();
        this.currentSize = memento.getSize();
        this.currentPrice = memento.getPrice();
        System.out.println("恢复记录,之前试穿的是:" + currentClothing + 
                          ",尺码:" + currentSize + ",价格:¥" + currentPrice);
    }
    
    public String getCurrentClothing() {
        return currentClothing;
    }
}

// 管理者:保存备忘录
class TryOnHistory {
    private List<TryOnMemento> history = new ArrayList<>();
    
    public void save(TryOnMemento memento) {
        history.add(memento);
    }
    
    public TryOnMemento getPrevious() {
        if (history.size() > 1) {
            // 返回上一个状态
            return history.get(history.size() - 2);
        }
        return history.get(0);
    }
    
    public List<TryOnMemento> getAllHistory() {
        return new ArrayList<>(history);
    }
}

// 使用示例
public class MallShopping {
    public static void main(String[] args) {
        FittingRoom fittingRoom = new FittingRoom();
        TryOnHistory history = new TryOnHistory();
        
        // 开始试衣
        System.out.println("=== 开始试衣 ===\n");
        fittingRoom.tryOn("白色T恤", "M", 99.0);
        history.save(fittingRoom.save());
        
        fittingRoom.tryOn("蓝色牛仔裤", "L", 199.0);
        history.save(fittingRoom.save());
        
        fittingRoom.tryOn("黑色外套", "M", 299.0);
        history.save(fittingRoom.save());
        
        // 想看看第一件
        System.out.println("\n=== 查看之前的试衣记录 ===\n");
        fittingRoom.restore(history.getPrevious());
        
        System.out.println("\n所有试衣记录:");
        for (TryOnMemento memento : history.getAllHistory()) {
            System.out.println("  " + memento.getClothingName() + 
                            ",尺码:" + memento.getSize() + 
                            ",价格:¥" + memento.getPrice());
        }
    }
}

备忘录模式的优势:可以在不破坏封装性的前提下保存和恢复状态。就像试衣记录,可以随时保存,随时恢复,非常方便。


解释器模式 - 解释语言

晚上6:00,小王和老婆去永辉超市胖改店捡漏。看到打折标签:“买2送1”、“满100减20”、“第二件半价”。

"这个买2送1是什么意思?"老婆问。

"就是买2个,送1个,相当于3个只付2个的钱。"小王解释。

“那这个满100减20呢?”

“就是买够100块,减20块。”

“这个第二件半价呢?”

“就是第二件半价,第一件原价。”

老婆点点头:“明白了,这些促销规则就像一门语言,需要解释才能理解。”

小王一边看标签,一边想:这就是解释器模式——给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。就像促销规则,把标签解释成能理解的价格。

生活中的解释器模式

解释器模式在生活中很常见:

  • 促销规则:解释"买2送1"、"满100减20"等促销标签
  • 计算器:解释数学表达式
  • 正则表达式:解释匹配规则

代码中的解释器模式

// 表达式接口
interface PromotionExpression {
    double interpret(double originalPrice, int quantity);
}

// 原价表达式
class PriceExpression implements PromotionExpression {
    private double price;
    
    public PriceExpression(double price) {
        this.price = price;
    }
    
    @Override
    public double interpret(double originalPrice, int quantity) {
        return price;
    }
}

// 买2送1表达式
class BuyTwoGetOneExpression implements PromotionExpression {
    private double unitPrice;
    
    public BuyTwoGetOneExpression(double unitPrice) {
        this.unitPrice = unitPrice;
    }
    
    @Override
    public double interpret(double originalPrice, int quantity) {
        int paidQuantity = quantity - (quantity / 3);
        return unitPrice * paidQuantity;
    }
}

// 满100减20表达式
class FullReductionExpression implements PromotionExpression {
    private double threshold;
    private double reduction;
    
    public FullReductionExpression(double threshold, double reduction) {
        this.threshold = threshold;
        this.reduction = reduction;
    }
    
    @Override
    public double interpret(double originalPrice, int quantity) {
        double total = originalPrice * quantity;
        if (total >= threshold) {
            return total - reduction;
        }
        return total;
    }
}

// 第二件半价表达式
class SecondHalfPriceExpression implements PromotionExpression {
    private double unitPrice;
    
    public SecondHalfPriceExpression(double unitPrice) {
        this.unitPrice = unitPrice;
    }
    
    @Override
    public double interpret(double originalPrice, int quantity) {
        if (quantity >= 2) {
            int fullPriceCount = (quantity / 2) + (quantity % 2);
            int halfPriceCount = quantity / 2;
            return unitPrice * fullPriceCount + unitPrice * 0.5 * halfPriceCount;
        }
        return originalPrice * quantity;
    }
}

// 解释器:解析促销规则
class PromotionInterpreter {
    public static PromotionExpression parse(String rule, double price) {
        if (rule.contains("买2送1")) {
            return new BuyTwoGetOneExpression(price);
        } else if (rule.contains("满100减20")) {
            return new FullReductionExpression(100, 20);
        } else if (rule.contains("第二件半价")) {
            return new SecondHalfPriceExpression(price);
        } else {
            return new PriceExpression(price);
        }
    }
}

// 使用示例
public class SupermarketShopping {
    public static void main(String[] args) {
        // 解析促销规则
        PromotionExpression expr1 = PromotionInterpreter.parse("买2送1", 50.0);
        System.out.println("买2送1,单价50元,买3件:" + expr1.interpret(50.0, 3) + "元");
        
        PromotionExpression expr2 = PromotionInterpreter.parse("满100减20", 30.0);
        System.out.println("满100减20,单价30元,买4件:" + expr2.interpret(30.0, 4) + "元");
        
        PromotionExpression expr3 = PromotionInterpreter.parse("第二件半价", 40.0);
        System.out.println("第二件半价,单价40元,买2件:" + expr3.interpret(40.0, 2) + "元");
    }
}

解释器模式的优势:可以很容易地扩展语言,添加新的促销规则。就像超市促销,想加新规则,只需要添加新的表达式类就行。


访问者模式 - 分离算法和结构

晚上7:00,小王和老婆在超市里,需要对不同类型的商品执行不同的操作:计算价格、检查保质期、统计重量。

"这个牛奶保质期到什么时候?"老婆问。

小王看了看:“到1月20号,还有几天。”

“这个面包呢?”

"到1月14号,已经过期了!"小王赶紧放回去。

“这个纸巾多少钱?”

“12块。”

小王一边购物,一边想:“如果每个商品都实现所有操作——计算价格、检查保质期、统计重量,商品类会变得很臃肿。不如把操作分离出来,用访问者来处理。”

这就是访问者模式——表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。就像超市管理员访问商品,不同的管理员(访问者)对商品(元素)执行不同的检查。

生活中的访问者模式

访问者模式在生活中很常见:

  • 超市管理:不同的管理员对商品执行不同的检查(价格、保质期、重量)
  • 医生看病:不同的医生对病人执行不同的检查
  • 税务审计:不同的审计员对账目执行不同的检查

代码中的访问者模式

// 元素接口
interface Product {
    void accept(Visitor visitor);
}

// 具体元素:食品
class FoodProduct implements Product {
    private String name;
    private double price;
    private String expiryDate;
    private double weight;
    
    public FoodProduct(String name, double price, String expiryDate, double weight) {
        this.name = name;
        this.price = price;
        this.expiryDate = expiryDate;
        this.weight = weight;
    }
    
    public String getName() {
        return name;
    }
    
    public double getPrice() {
        return price;
    }
    
    public String getExpiryDate() {
        return expiryDate;
    }
    
    public double getWeight() {
        return weight;
    }
    
    @Override
    public void accept(Visitor visitor) {
        visitor.visitFood(this);
    }
}

// 具体元素:日用品
class DailyProduct implements Product {
    private String name;
    private double price;
    private double weight;
    
    public DailyProduct(String name, double price, double weight) {
        this.name = name;
        this.price = price;
        this.weight = weight;
    }
    
    public String getName() {
        return name;
    }
    
    public double getPrice() {
        return price;
    }
    
    public double getWeight() {
        return weight;
    }
    
    @Override
    public void accept(Visitor visitor) {
        visitor.visitDaily(this);
    }
}

// 访问者接口
interface Visitor {
    void visitFood(FoodProduct food);
    void visitDaily(DailyProduct daily);
}

// 具体访问者:计算总价
class PriceCalculator implements Visitor {
    private double totalPrice = 0.0;
    
    @Override
    public void visitFood(FoodProduct food) {
        totalPrice += food.getPrice();
        System.out.println("食品 " + food.getName() + " 价格:¥" + food.getPrice());
    }
    
    @Override
    public void visitDaily(DailyProduct daily) {
        totalPrice += daily.getPrice();
        System.out.println("日用品 " + daily.getName() + " 价格:¥" + daily.getPrice());
    }
    
    public double getTotalPrice() {
        return totalPrice;
    }
}

// 具体访问者:检查保质期
class ExpiryChecker implements Visitor {
    private List<String> expiredItems = new ArrayList<>();
    
    @Override
    public void visitFood(FoodProduct food) {
        // 简化版:假设今天日期是2024-01-15
        if (food.getExpiryDate().compareTo("2024-01-15") < 0) {
            expiredItems.add(food.getName());
            System.out.println("警告:食品 " + food.getName() + " 已过期!");
        } else {
            System.out.println("食品 " + food.getName() + " 保质期正常");
        }
    }
    
    @Override
    public void visitDaily(DailyProduct daily) {
        System.out.println("日用品 " + daily.getName() + " 无保质期限制");
    }
    
    public List<String> getExpiredItems() {
        return expiredItems;
    }
}

// 使用示例
public class SupermarketShopping {
    public static void main(String[] args) {
        // 构建购物车
        List<Product> cart = new ArrayList<>();
        cart.add(new FoodProduct("牛奶", 15.0, "2024-01-20", 1.0));
        cart.add(new FoodProduct("面包", 8.0, "2024-01-14", 0.5));
        cart.add(new DailyProduct("纸巾", 12.0, 0.3));
        cart.add(new DailyProduct("洗发水", 35.0, 0.5));
        
        // 使用访问者计算总价
        System.out.println("=== 计算总价 ===\n");
        PriceCalculator priceCalculator = new PriceCalculator();
        for (Product product : cart) {
            product.accept(priceCalculator);
        }
        System.out.println("\n总价:¥" + priceCalculator.getTotalPrice());
        
        // 使用访问者检查保质期
        System.out.println("\n=== 检查保质期 ===\n");
        ExpiryChecker expiryChecker = new ExpiryChecker();
        for (Product product : cart) {
            product.accept(expiryChecker);
        }
    }
}

访问者模式的优势:可以把算法和数据结构分离,想加新操作只需要加新的访问者,不需要修改元素类。就像超市管理,想加新的检查项目,只需要派新的管理员(访问者)就行。


📊 设计模式全景图:小王的周末时光

看完了小王的周末时光,我画了张图,把11种设计模式都标出来了:

小王的周末创建型模式2种结构型模式4种行为型模式5种抽象工厂模式早餐套餐原型模式购物清单桥接模式装饰品组合模式购物分类外观模式一键做饭享元模式服装样式迭代器模式逛商场中介者模式美食广场备忘录模式试衣记录解释器模式促销规则访问者模式商品检查

🎯 GoF 23种设计模式完整分类

🏭 创建型模式(5种)

  1. 单例模式 - 确保一个类只有一个实例
  2. 工厂模式 - 通过工厂创建对象
  3. 抽象工厂模式 - 创建一系列相关对象
  4. 建造者模式 - 一步步构建复杂对象
  5. 原型模式 - 通过克隆创建对象

🏗️ 结构型模式(7种)

  1. 适配器模式 - 让不兼容的接口协同工作
  2. 桥接模式 - 分离抽象和实现
  3. 组合模式 - 树形结构,统一处理
  4. 装饰器模式 - 动态添加功能
  5. 外观模式 - 简化复杂系统
  6. 享元模式 - 共享细粒度对象
  7. 代理模式 - 控制对象访问

🎭 行为型模式(11种)

  1. 责任链模式 - 逐级处理请求
  2. 命令模式 - 封装请求为对象
  3. 解释器模式 - 解释语言
  4. 迭代器模式 - 统一遍历方式
  5. 中介者模式 - 协调对象交互
  6. 备忘录模式 - 保存和恢复状态
  7. 观察者模式 - 一对多通知
  8. 状态模式 - 状态改变行为
  9. 策略模式 - 封装算法
  10. 模板方法模式 - 定义算法骨架
  11. 访问者模式 - 分离算法和结构

💡 设计模式的选择原则

看完23种设计模式,你会发现选择模式有几个原则:

  1. 先理解问题 - 不要为了用模式而用模式,先理解问题本质
  2. 简单优先 - 能用简单方法解决就不要用复杂模式
  3. 适度使用 - 不要过度设计,保持代码简洁
  4. 灵活扩展 - 考虑未来可能的变化,但不要过度预测

🌟 结语

看完小王的周末时光,我们又发现了11种设计模式(加上第一篇文章的12种,正好覆盖全部23种GoF设计模式)。它们不是高深的理论,而是解决实际问题的经验总结。就连周末的休闲时光里,也藏着设计模式的智慧。

你可能会问:为什么要学设计模式?其实很简单,它们能帮我们写出更好的代码,解决实际的问题。但记住:

  • 设计模式是工具,不是目的 - 不要为了用而用
  • 合适的时候用,不合适的时候别硬用 - 简单问题用简单方法
  • 理解思想比记住结构更重要 - 知道为什么用,比知道怎么用更重要
  • 从生活出发,理解设计模式 - 设计模式就在我们身边

当你理解了设计模式背后的思想,你会发现,它们其实很简单,很实用,也很有趣!就连周末逛超市、做顿饭,都能看到设计模式的影子。

美好的一天结束了,虽然累,但很充实。明天继续搬砖,但设计模式的智慧,会一直陪伴着我们!