结构型模式(Structural Patterns)

结构型模式(Structural Patterns)

1、适配器模式(Adapter Pattern)

意图

用于解决两个不兼容接口之间的问题。它允许将一个类的接口转换成客户端期望的另一个接口,从而使不兼容的类能够一起工作。适配器模式有两种形式:类适配器和对象适配器。

程序实例

现实世界的例子

假设您的存储卡上有一些照片,并且需要将它们传输到计算机上。要传输它们,您需要某种与计算机端口兼容的适配器,以便可以将存储卡连接到计算机。在这种情况下,读卡器是一个适配器。另一个例子是著名的电源适配器;三脚插头不能连接两脚插座,需要使用兼容两脚插座的电源适配器。另一个例子是翻译人员将一个人所说的单词翻译成另一个人所说的单词

用简单的话来说

适配器模式允许您将不兼容的对象包装在适配器中,以使其与另一个类兼容。

维基百科说

在软件工程中,适配器模式是一种软件设计模式,允许将现有类的接口用作另一个接口。它通常用于使现有类与其他类一起工作,而无需修改其源代码。

考虑一位只能使用划艇而根本无法航行的船长。

首先,我们有接口RowingBoatFishingBoat

/**
 * 划艇
 * @author xs
 */
public interface RowingBoat {
    void row();

}

/**
 * @author xs
 */
@Slf4j
public class FishingBoat {
    void sail() {
        log.info("渔船正在航行....");
    }
}

然后我们使用适配器把这两者联系起来

/**
 * @author xs
 */
public class FishingBoatAdapter implements RowingBoat{
    private final FishingBoat boat = new FishingBoat();

    @Override
    public void row() {
        boat.sail();
    }
}

然后就可用使用RowingBoat的row方法了

/**
 * @author xs
 */
@Setter
@NoArgsConstructor
@AllArgsConstructor
public final class Captain {
    private RowingBoat rowingBoat;

    void row() {
        rowingBoat.row();
    }
}

运行

/**
 * @author xs
 */
public class Main {
    public static void main(String[] args) {
        // 船长只能操作划艇,但通过适配器他也可以使用渔船
        Captain captain = new Captain(new FishingBoatAdapter());
        captain.row();
    }
}

程序输出

[main] INFO com.xs.designpattern.FishingBoat - 渔船正在航行....

类图

请添加图片描述

使用场景

适配器模式适用于以下情况:

  • 当需要使用一个已经存在的类,但其接口与你的代码不兼容时。
  • 当希望创建一个可以重复使用的类,它能够与不同的类协同工作,而不需要修改客户端代码。
  • 当希望在不影响现有代码的情况下引入新功能。

优点

  • 允许不同接口的类一起工作,提高了代码的复用性。
  • 可以将不兼容的代码进行解耦,使得系统更加灵活。
  • 适配器可以隐藏具体的实现细节,使客户端代码更加简洁。

缺点

  • 增加了适配器类,可能会增加代码复杂性。
  • 适配器模式只适用于解决接口不兼容的问题,不适用于修改源代码或引入新功能。

2、桥接模式(Bridge Pattern)

意图

用于将抽象部分与实现部分分离,使它们可以独立变化。这种分离允许你在不修改客户端代码的情况下改变抽象部分和实现部分。

程序实例

现实世界的例子

假设你有一把具有不同附魔的武器,并且你应该允许将不同的武器与不同的附魔混合在一起。你会怎么办?为每个结界创建每个武器的多个副本,或者您只是创建单独的结界并根据需要为武器设置它?桥接模式可以帮你做第二件事。

用简单的话来说

桥接模式是关于更喜欢组合而不是继承。实现细节从一个层次结构推送到另一个具有单独层次结构的对象。

维基百科说

桥接模式是软件工程中使用的一种设计模式,旨在“将抽象与其实现分离,以便两者可以独立变化”

翻译上面的武器示例。这里我们有Weapon层次结构:

/**
 * 武器
 * @author xs
 */
public interface Weapon {
    void wield();

    void swing();

    void unwield();

    Enchantment getEnchantment();
}
/**
 * @author xs
 */
@Slf4j
@AllArgsConstructor
public class Sword implements Weapon{
    private final Enchantment enchantment;

    @Override
    public void wield() {
        log.info("剑已挥动");
        enchantment.onActivate();
    }

    @Override
    public void swing() {
        log.info("剑已挥出");
        enchantment.apply();
    }

    @Override
    public void unwield() {
        log.info("剑未挥动");
        enchantment.onDeactivate();
    }

    @Override
    public Enchantment getEnchantment() {
        return enchantment;
    }
}
/**
 * @author xs
 */
@Slf4j
@AllArgsConstructor
public class Hammer implements Weapon {
    private final Enchantment enchantment;

    @Override
    public void wield() {
        log.info("锤子被挥舞着...");
        enchantment.onActivate();
    }

    @Override
    public void swing() {
        log.info("锤子挥动起来...");
        enchantment.apply();
    }

    @Override
    public void unwield() {
        log.info("锤子未挥动...");
        enchantment.onDeactivate();
    }

    @Override
    public Enchantment getEnchantment() {
        return enchantment;
    }
}

单独的附魔层次结构

/**
 * 结界
 * @author xs
 */
public interface Enchantment {
    /**
     * 激活时
     */
    void onActivate();

    /**
     * 使用时
     */
    void apply();

    /**
     * 关闭时
     */
    void onDeactivate();
}
/**
 * @author xs
 */
@Slf4j
public class FlyingEnchantment implements Enchantment{
    @Override
    public void onActivate() {
        log.info("该物品开始发出微弱的光芒...");
    }

    @Override
    public void apply() {
        log.info("物品飞行并攻击敌人最终回到主人手中...");
    }

    @Override
    public void onDeactivate() {
        log.info("该物品的光芒逐渐消失...");
    }
}
/**
 * @author xs
 */
@Slf4j
public class SoulEatingEnchantment implements Enchantment {
    @Override
    public void onActivate() {
        log.info("该物品传播嗜血...");
    }

    @Override
    public void apply() {
        log.info("该物品会吞噬敌人的灵魂...");
    }

    @Override
    public void onDeactivate() {
        log.info("嗜血之情慢慢消失...");
    }
}

以下是两个实际的层次结构:

/**
 * @author xs
 */
@Slf4j
public class Main {
    public static void main(String[] args) {
        log.info("骑士收到一把附魔剑..");
        Sword enchantedSword = new Sword(new SoulEatingEnchantment());
        enchantedSword.wield();
        enchantedSword.swing();
        enchantedSword.unwield();

        log.info("女武神收到一把附魔锤..");
        Hammer hammer = new Hammer(new FlyingEnchantment());
        hammer.wield();
        hammer.swing();
        hammer.unwield();
    }
}

程序输出

[main] INFO com.xs.designpattern.Main - 骑士收到一把附魔剑..
[main] INFO com.xs.designpattern.Sword - 剑已挥动
[main] INFO com.xs.designpattern.SoulEatingEnchantment - 该物品传播嗜血...
[main] INFO com.xs.designpattern.Sword - 剑已挥出
[main] INFO com.xs.designpattern.SoulEatingEnchantment - 该物品会吞噬敌人的灵魂...
[main] INFO com.xs.designpattern.Sword - 剑未挥动
[main] INFO com.xs.designpattern.SoulEatingEnchantment - 嗜血之情慢慢消失...
[main] INFO com.xs.designpattern.Main - 女武神收到一把附魔锤..
[main] INFO com.xs.designpattern.Hammer - 锤子被挥舞着...
[main] INFO com.xs.designpattern.FlyingEnchantment - 该物品开始发出微弱的光芒...
[main] INFO com.xs.designpattern.Hammer - 锤子挥动起来...
[main] INFO com.xs.designpattern.FlyingEnchantment - 物品飞行并攻击敌人最终回到主人手中...
[main] INFO com.xs.designpattern.Hammer - 锤子未挥动...
[main] INFO com.xs.designpattern.FlyingEnchantment - 该物品的光芒逐渐消失...

类图

请添加图片描述

使用场景

桥接模式适用于以下情况:

  • 当需要在抽象部分和实现部分之间有一个稳定的连接,同时允许它们可以独立变化时。
  • 当一个类存在多个维度的变化,而需要通过多重继承来实现时,可以使用桥接模式来避免类的爆炸性增长。
  • 当希望在运行时切换抽象部分和实现部分的具体实现时,可以使用桥接模式。

优点

  • 分离抽象和实现,提高了代码的可维护性和可扩展性。
  • 允许在运行时动态地切换抽象部分和实现部分的具体实现。
  • 避免类的爆炸性增长,特别适用于存在多维度变化的情况。

缺点

  • 增加了抽象类和实现类的数量,可能增加代码复杂性。
  • 某些情况下,桥接模式可能会增加代码的理解和设计难度。

3、组合模式(Composite Pattern)

意图

用于将对象组织成树形结构,以表示“整体-部分”的层次关系。它允许客户端统一处理单个对象和组合对象,从而使得处理复杂的对象结构变得更加简单。

程序实例

现实世界的例子

每个句子都由单词组成,而单词又由字符组成。这些对象中的每一个都是可打印的,并且它们可以在它们之前或之后打印一些东西,就像句子总是以句号结尾并且单词之前总是有空格。

用简单的话来说

复合模式让客户能够统一对待各个对象。

维基百科说

在软件工程中,组合模式是一种分区设计模式。复合模式描述了一组对象的处理方式与对象的单个实例相同。组合的目的是将对象“组合”成树结构以表示
部分-整体层次结构。实现复合模式可以让客户统一地处理单个对象和组合。

以上面的句子为例。这里我们有基类LetterComposite
不同的可打印类型Letter,WordSentence

/**
 * 这是一个具体的叶子节点,代表一个字母。
 * 它继承自 LetterComposite 类,实现了字母的打印。
 * @author xs
 */
@RequiredArgsConstructor
public class Letter extends LetterComposite {
    /**
     * 属性表示字母本身,通过构造函数传入
     */
    private final char character;

    /**
     * 方法被覆写,用于在打印字母前打印字母本身
     */
    @Override
    protected void printThisBefore() {
        System.out.print(character);
    }
}
/**
 * 这是一个抽象类,作为组合模式中的组件,定义了组合对象的公共行为。
 * 持有一个 children 列表,用于保存子节点。
 * @author xs
 */
public abstract class LetterComposite {
    private final List<LetterComposite> children = new ArrayList<>();

    /**
     * 用于添加子节点
     * @param letter 字符
     */
    public void add(LetterComposite letter) {
        children.add(letter);
    }

    /**
     * 返回子节点数量。
     * @return list大小
     */
    public int count() {
        return children.size();
    }

    /**
     * 定义组合对象的打印前行为
     */
    protected void printThisBefore() {
    }

    /**
     * 定义组合对象的打印后行为
     */
    protected void printThisAfter() {
    }

    /**
     * 递归地打印组合对象的内容
     */
    public void print() {
        printThisBefore();
        children.forEach(LetterComposite::print);
        printThisAfter();
    }
}
/**
 * 这是一个创建者类,用于创建不同的消息。
 * messageFromOrcs() 方法返回一个由单词组成的消息,用于表示一种消息来源。
 * messageFromElves() 方法返回另一种消息来源的消息。
 * @author xs
 */
public class Messenger {
    LetterComposite messageFromOrcs() {

        List<Word> words =
            Arrays.asList(new Word('W', 'h', 'e', 'r', 'e'), new Word('t', 'h', 'e', 'r', 'e'), new Word('i', 's'),
                new Word('a'), new Word('w', 'h', 'i', 'p'), new Word('t', 'h', 'e', 'r', 'e'), new Word('i', 's'),
                new Word('a'), new Word('w', 'a', 'y'));
        return new Sentence(words);
    }

    LetterComposite messageFromElves() {
        List<Word> words =
            Arrays.asList(new Word('M', 'u', 'c', 'h'), new Word('w', 'i', 'n', 'd'), new Word('p', 'o', 'u', 'r', 's'),
                new Word('f', 'r', 'o', 'm'), new Word('y', 'o', 'u', 'r'), new Word('m', 'o', 'u', 't', 'h'));
        return new Sentence(words);
    }
}
/**
 * 这是一个组合节点,用于表示一个句子,由多个单词组成。
 * @author xs
 */
public class Sentence extends LetterComposite{
    /**
     * 构造函数接受一个单词列表,将单词作为子节点添加到句子中
     */
    public Sentence(List<Word> words) {
        words.forEach(this::add);
    }

    /**
     * 重新打印句子后打印.
     */
    @Override
    protected void printThisAfter() {
        System.out.print(".\n");
    }
}
/**
 * 这是一个组合节点,用于表示一个单词,由多个字母组成。
 * @author xs
 */
public class Word extends LetterComposite {
    /**
     * 构造函数可以接受一个字母列表作为参数,将字母作为子节点添加到单词中
     */
    public Word(List<Letter> letters) {
        letters.forEach(this::add);
    }

    /**
     * 构造函数可以接受多个字符作为参数,将字母作为子节点添加到单词中
     *
     * @param letters to include
     */
    public Word(char... letters) {
        for (char letter : letters) {
            this.add(new Letter(letter));
        }
    }

    /**
     * 重新方法用于在打印单词前打印一个空格
     */
    @Override
    protected void printThisBefore() {
        System.out.print(" ");
    }
}

这段代码通过组合模式实现了一种构建和打印文本消息的方式,可以递归地组合字母、单词和句子,然后统一地进行打印。这种设计方式可以用于创建复杂的文本结构,同时将构建和操作分离

测试

/**
 * @author xs
 */
@Slf4j
public class Main {
    public static void main(String[] args) {
        Messenger messenger = new Messenger();

        log.info("来自兽人的消息: ");
        messenger.messageFromOrcs().print();

        log.info("来自精灵的消息: ");
        messenger.messageFromElves().print();
    }
}

程序输出

[main] INFO com.xs.designpattern.Main - 来自兽人的消息: 
 Where there is a whip there is a way.
[main] INFO com.xs.designpattern.Main - 来自精灵的消息: 
 Much wind pours from your mouth.

类图

请添加图片描述

使用场景

组合模式适用于以下情况:

  • 当需要表示对象的层次结构,使得客户端可以统一地处理单个对象和组合对象时。
  • 当希望将对象的部分-整体结构与对象的行为进行解耦,使得增加新的对象类型更加方便。
  • 当需要递归地处理对象的树形结构时,可以使用组合模式。

优点

  • 将对象的组织形式与对象的行为分离,使得代码更加灵活和可维护。
  • 客户端代码可以统一地处理单个对象和组合对象,无需关心具体的层次结构。
  • 可以方便地增加新的对象类型,扩展性强。

缺点

  • 可能会增加对象的数量,增加了系统的复杂性。
  • 在某些情况下,可能会导致性能问题,特别是在处理大型对象树时。

4、装饰器模式(Decorator Pattern)

意图

它允许你在不改变对象结构的情况下,动态地将责任附加到对象上。通过将对象包装在装饰器类中,可以在运行时添加新的行为或功能。

程序实例

现实世界的例子

附近的山上住着一个愤怒的巨魔。通常,它赤手空拳,但有时它会携带武器。要武装巨魔,不需要创建新的巨魔,而是用合适的武器动态地装饰它。

用简单的话来说

装饰器模式允许您通过将对象包装在装饰器类的对象中来在运行时动态更改对象的行为。

维基百科说

在面向对象编程中,装饰器模式是一种设计模式,允许静态或动态地将行为添加到单个对象,而不影响同一类中其他对象的行为。装饰器模式通常对于遵守单一职责原则很有用,因为它允许在具有独特关注领域的类之间划分功能,并且通过允许扩展类的功能而无需扩展类的功能,从而遵循开闭原则。修改的。

以上面的例子为准,我们创建接口Troll和他的实现类SimpleTroll(简单巨魔)

/**
 * 巨魔接口
 * @author xs
 */
public interface Troll {
    /**
     * 攻击
     */
    void attack();

    /**
     * 获得攻击力
     * @return 大小
     */
    int getAttackPower();

    /**
     * 逃离战斗
     */
    void fleeBattle();
}
/**
 * 简单巨魔
 * @author xs
 */
@Slf4j
public class SimpleTroll implements Troll{
    @Override
    public void attack() {
        log.info("巨魔试图抓住你!");
    }

    @Override
    public int getAttackPower() {
        return 10;
    }

    @Override
    public void fleeBattle() {
        log.info("巨魔惊恐地尖叫着逃跑了!");
    }
}

然后我们要给简单巨魔进行装饰

/**
 * 棒状巨魔
 * @author xs
 */
@Slf4j
@RequiredArgsConstructor
public class ClubbedTroll implements Troll {
    private final Troll decorated;

    @Override
    public void attack() {
        decorated.attack();
        log.info("巨魔用棍子向你挥舞!");
    }

    @Override
    public int getAttackPower() {
        return decorated.getAttackPower() + 10;
    }

    @Override
    public void fleeBattle() {
        log.info("巨魔用棍子发现还是打不过!");
        decorated.fleeBattle();
    }
}

该类以简单巨魔为基准,给他装备是棍棒进行攻击,攻击力增加了

实验

/**
 * @author xs
 */
@Slf4j
public class Main {
    public static void main(String[] args) {
        // 简单的巨魔
        log.info("一个看起来很简单的巨魔接近了.");
        Troll troll = new SimpleTroll();
        troll.attack();
        troll.fleeBattle();
        log.info("简单的巨魔力量: {}.\n", troll.getAttackPower());

        // 通过添加装饰器来改变简单巨魔的行为
        log.info("拥有巨大棍棒的巨魔会让你大吃一惊.");
        Troll clubbedTroll = new ClubbedTroll(troll);
        clubbedTroll.attack();
        clubbedTroll.fleeBattle();
        log.info("棒状巨魔力量: {}.\n", clubbedTroll.getAttackPower());
    }
}

程序输出

[main] INFO com.xs.designpattern.Main - 一个看起来很简单的巨魔接近了.
[main] INFO com.xs.designpattern.SimpleTroll - 巨魔试图抓住你!
[main] INFO com.xs.designpattern.SimpleTroll - 巨魔惊恐地尖叫着逃跑了!
[main] INFO com.xs.designpattern.Main - 简单的巨魔力量: 10.

[main] INFO com.xs.designpattern.Main - 拥有巨大棍棒的巨魔会让你大吃一惊.
[main] INFO com.xs.designpattern.SimpleTroll - 巨魔试图抓住你!
[main] INFO com.xs.designpattern.ClubbedTroll - 巨魔用棍子向你挥舞!
[main] INFO com.xs.designpattern.ClubbedTroll - 巨魔用棍子发现还是打不过!
[main] INFO com.xs.designpattern.SimpleTroll - 巨魔惊恐地尖叫着逃跑了!
[main] INFO com.xs.designpattern.Main - 棒状巨魔力量: 20.

类图

请添加图片描述

使用场景

装饰器模式适用于以下情况:

  • 当需要动态地为对象添加额外的功能或责任时,可以使用装饰器模式。
  • 当不能使用子类来扩展对象的功能,或者希望避免使用太多子类时,可以使用装饰器模式。

优点

  • 可以动态地扩展对象的功能,而不需要修改现有代码。
  • 可以使用不同的装饰器组合来实现不同的功能组合。
  • 避免了使用过多的继承,使代码更加灵活和可维护。

缺点

  • 可能会导致类的数量增加,增加了系统的复杂性。
  • 需要仔细设计装饰器之间的关系,以避免混乱。

5、外观模式(Facade Pattern)

意图

用于为复杂子系统提供一个简化的接口,以便客户端可以更方便地与子系统进行交互。外观模式通过创建一个高层接口,将多个子系统的功能集成起来,从而隐藏了子系统的复杂性。

程序实例

现实世界的例子

金矿如何运作?“好吧,矿工们到那里去挖金子吧!” 你说。这就是你所相信的,因为你使用的是 goldmine 在外部提供的一个简单界面,在内部它必须做很多事情才能实现。这个复杂子系统的简单接口就是外观。

用简单的话来说

外观模式为复杂的子系统提供了简化的接口。

维基百科说

外观是一个对象,它为更大的代码体(例如类库)提供简化的接口

以上面的例子为例创建外观模式,我需要准备准备个基类DwarvenMineWorker

/**
 * 这是一个抽象矮人工人类,包含了矮人工人的基本行为和操作
 * @author xs
 */
@Slf4j
public abstract class DwarvenMineWorker {
    public void goToSleep() {
        log.info("{} 去睡觉.", name());
    }

    public void wakeUp() {
        log.info("{} 醒来.", name());
    }

    public void goHome() {
        log.info("{} 回家.", name());
    }

    public void goToMine() {
        log.info("{} 去矿井.", name());
    }

    private void action(Action action) {
        switch (action) {
            case GO_TO_SLEEP:
                goToSleep();
                break;
            case WAKE_UP:
                wakeUp();
                break;
            case GO_HOME:
                goHome();
                break;
            case GO_TO_MINE:
                goToMine();
                break;
            case WORK:
                work();
                break;
            default:
                log.info("未定义的动作");
        }
    }

    /**
     * 执行操作
     */
    public void action(Action... actions) {
        Arrays.stream(actions).forEach(this::action);
    }

    public abstract void work();

    public abstract String name();

    enum Action {
        /**
         * 操作的枚举
         */
        GO_TO_SLEEP, WAKE_UP, GO_HOME, GO_TO_MINE, WORK
    }
}

然后我们需要让不同的矮人工人去继承这个基类

/**
 * 具体的矮人工人类,代表矮人推车操作员
 * 继承自 DwarvenMineWorker 类
 * @author xs
 */
@Slf4j
public class DwarvenCartOperator extends DwarvenMineWorker {
    /**
     * 实现了 work() 方法,用于执行具体的工作
     */
    @Override
    public void work() {
        log.info("{}将金块移出矿井.", name());
    }

    /**
     * 实现了 name() 方法
     * @return 返回矮人的名称
     */
    @Override
    public String name() {
        return "矮人推车操作员";
    }
}
/**
 * 具体的矮人工人类,代表矮人淘金者。
 * @author xs
 */
@Slf4j
public class DwarvenGoldDigger extends DwarvenMineWorker{
    /**
     * 实现了 work() 方法,用于执行具体的工作
     */
    @Override
    public void work() {
        log.info("{} 挖掘黄金.", name());
    }

    /**
     * 实现了 name() 方法
     * @return 返回矮人的名称
     */
    @Override
    public String name() {
        return "矮人淘金者";
    }
}
/**
 * 具体的矮人工人类,代表矮人隧道挖掘机
 * @author xs
 */
@Slf4j
public class DwarvenTunnelDigger extends DwarvenMineWorker {
    /**
     * 实现了 work() 方法,用于执行具体的工作
     */
    @Override
    public void work() {
        log.info("{} 创建另一个有前途的隧道.", name());
    }

    /**
     * 实现了 name() 方法
     * @return 返回矮人的名称
     */
    @Override
    public String name() {
        return "矮人隧道挖掘机";
    }
}

然后我们提供一个外观类DwarvenGoldmineFacade去操作所以的工人

/**
 * 这是外观类,封装了矮人在金矿中的各种操作。
 *
 * @author xs
 */
public class DwarvenGoldmineFacade {
    private final List<DwarvenMineWorker> workers;

    /**
     * 在构造函数中初始化了矮人工人列表
     */
    public DwarvenGoldmineFacade() {
        workers = Arrays.asList(new DwarvenGoldDigger(), new DwarvenCartOperator(), new DwarvenTunnelDigger());
    }

    public void startNewDay() {
        makeActions(workers, DwarvenMineWorker.Action.WAKE_UP, DwarvenMineWorker.Action.GO_TO_MINE);
    }

    public void digOutGold() {
        makeActions(workers, DwarvenMineWorker.Action.WORK);
    }

    public void endDay() {
        makeActions(workers, DwarvenMineWorker.Action.GO_HOME, DwarvenMineWorker.Action.GO_TO_SLEEP);
    }

    /**
     * 使用私有的 makeActions() 方法来批量执行操作
     *
     * @param workers 工人
     * @param actions 操作类型
     */
    private static void makeActions(Collection<DwarvenMineWorker> workers, DwarvenMineWorker.Action... actions) {
        workers.forEach(worker -> worker.action(actions));
    }
}

通过外观模式实现了对矮人工人在金矿中的操作的封装和简化,使得客户端可以更方便地管理矮人的工作流程。外观模式可以隐藏底层的复杂性,提供一个简化的接口给客户端使用。

让我们来使用这个外观类

public class Main {
    public static void main(String[] args) {
        DwarvenGoldmineFacade facade = new DwarvenGoldmineFacade();
        facade.startNewDay();
        facade.digOutGold();
        facade.endDay();
    }
}

程序输出

[main] INFO com.xs.designpattern.DwarvenMineWorker - 矮人淘金者 醒来.
[main] INFO com.xs.designpattern.DwarvenMineWorker - 矮人淘金者 去矿井.
[main] INFO com.xs.designpattern.DwarvenMineWorker - 矮人推车操作员 醒来.
[main] INFO com.xs.designpattern.DwarvenMineWorker - 矮人推车操作员 去矿井.
[main] INFO com.xs.designpattern.DwarvenMineWorker - 矮人隧道挖掘机 醒来.
[main] INFO com.xs.designpattern.DwarvenMineWorker - 矮人隧道挖掘机 去矿井.
[main] INFO com.xs.designpattern.DwarvenGoldDigger - 矮人淘金者 挖掘黄金.
[main] INFO com.xs.designpattern.DwarvenCartOperator - 矮人推车操作员将金块移出矿井.
[main] INFO com.xs.designpattern.DwarvenTunnelDigger - 矮人隧道挖掘机 创建另一个有前途的隧道.
[main] INFO com.xs.designpattern.DwarvenMineWorker - 矮人淘金者 回家.
[main] INFO com.xs.designpattern.DwarvenMineWorker - 矮人淘金者 去睡觉.
[main] INFO com.xs.designpattern.DwarvenMineWorker - 矮人推车操作员 回家.
[main] INFO com.xs.designpattern.DwarvenMineWorker - 矮人推车操作员 去睡觉.
[main] INFO com.xs.designpattern.DwarvenMineWorker - 矮人隧道挖掘机 回家.
[main] INFO com.xs.designpattern.DwarvenMineWorker - 矮人隧道挖掘机 去睡觉.

类图

请添加图片描述

使用场景

外观模式适用于以下情况:

  • 当一个复杂的子系统存在,而客户端只需要与子系统的某些功能进行交互时。
  • 当需要将子系统的复杂性隐藏起来,提供一个更简单的接口给客户端使用时。
  • 当希望解耦客户端与多个子系统之间的关系,避免客户端直接依赖于子系统的细节。

优点

  • 提供了一个简化的接口,使得客户端使用更加方便。
  • 隐藏了子系统的复杂性,提高了代码的可维护性和可读性。
  • 解耦了客户端与子系统之间的关系,降低了耦合度。

缺点

  • 可能会导致系统出现一个过于庞大的外观类,如果设计不当,可能会变得难以维护。
  • 如果需要访问子系统的高级功能,仍然需要绕过外观直接访问子系统。

6、享元模式(Flyweight Pattern)

意图

旨在减少对象的内存消耗,通过共享对象来降低系统的内存开销。它适用于有大量相似对象的情况,通过共享共同的状态,可以减少创建相同对象的实例,从而提高系统的性能和效率。

程序实例

现实世界的例子

炼金术士的商店里的货架上摆满了魔法药剂。许多药水是相同的,因此无需为每个药水创建一个新对象。相反,一个对象实例可以代表多个架子项,因此内存占用量仍然很小。

用简单的话来说

它用于通过与相似对象尽可能共享来最大限度地减少内存使用或计算费用

维基百科说

在计算机编程中,享元是一种软件设计模式。享元是一种通过与其他类似对象共享尽可能多的数据来最小化内存使用的对象;当简单的重复表示会使用不可接受的内存量时,这是一种使用大量对象的方法。

翻译上面我们的炼金术士商店的例子。首先,我们有不同的药水类型Potion以及他的实现类:

/**
 * 药水类接口
 * @author xs
 */
public interface Potion {
    /**
     * 喝药水的抽象方法
     */
    void drink();
}
/**
 * 治疗药水
 * @author xs
 */
@Slf4j
public class HealingPotion implements Potion{
    @Override
    public void drink() {
        log.info("你感觉被治愈了.(Potion={})", System.identityHashCode(this));
    }
}
/**
 * 圣水药剂
 * @author xs
 */
@Slf4j
public class HolyWaterPotion implements Potion {
    @Override
    public void drink() {
        log.info("你感到幸福. (Potion={})", System.identityHashCode(this));
    }
}
/**
 * 隐形药水
 * @author xs
 */
@Slf4j
public class InvisibilityPotion implements Potion{
    @Override
    public void drink() {
        log.info("你变得隐形. (Potion={})", System.identityHashCode(this));
    }
}
/**
 * 毒药水
 * @author xs
 */
@Slf4j
public class PoisonPotion implements Potion{
    @Override
    public void drink() {
        log.info("呃!这是有毒的. (Potion={})", System.identityHashCode(this));
    }
}
/**
 * 力量药水
 * @author xs
 */
@Slf4j
public class StrengthPotion implements Potion{
    @Override
    public void drink() {
        log.info("你感觉很坚强. (Potion={})", System.identityHashCode(this));
    }
}

然后我们做一个生成药水的工厂PotionFactory

/**
 * 生成药水的工厂
 * @author xs
 */
@Slf4j
public class PotionFactory {
    /**
     * 存放要药水的map货架
     */
    private final Map<PotionType, Potion> potions;

    /**
     * 狗构造函数指定map的枚举map类型
     */
    public PotionFactory() {
        potions = new EnumMap<>(PotionType.class);
    }

    /**
     * 通过类型创建药水,以及创建的药水类型直接拿来使用,减少资源的浪费,避免频繁的创建对象
     * @param type 药水的类型
     * @return 返回药水的实例
     */
    Potion createPotion(PotionType type) {
        Potion potion = potions.get(type);
        //如果药水类型对应的对象没有创建就走进if内部,创建了的话直接返回这个对象的实例就行 
        if (potion == null) {
            switch (type) {
                case HEALING:
                    potion = new HealingPotion();
                    break;
                case HOLY_WATER:
                    potion = new HolyWaterPotion();
                    break;
                case INVISIBILITY:
                    potion = new InvisibilityPotion();
                    break;
                case POISON:
                    potion = new PoisonPotion();
                    break;
                case STRENGTH:
                    potion = new StrengthPotion();
                    break;
                default:
                    log.info("没有这种的药水类型!");
            }
            //如何药水在map中是空的我们就把上面创建的对象放到map中进行二次使用
            if (potion != null) {
                potions.put(type, potion);
            }
        }
        //返回实例的对象
        return potion;
    }
}

创建一个买药水的炼金药店AlchemistShop

/**
 * 炼金术士商店
 * @author xs
 */
@Slf4j
public class AlchemistShop {
    /**
     * 顶层存放的药水
     */
    private final List<Potion> topShelf;
    /**
     * 底层存放的药水
     */
    private final List<Potion> bottomShelf;

    /**
     * 构造函数创建药品,重复药水类型归于一类.
     */
    public AlchemistShop() {
        PotionFactory factory = new PotionFactory();
        topShelf =
            Arrays.asList(factory.createPotion(PotionType.INVISIBILITY), factory.createPotion(PotionType.INVISIBILITY),
                factory.createPotion(PotionType.STRENGTH), factory.createPotion(PotionType.HEALING),
                factory.createPotion(PotionType.INVISIBILITY), factory.createPotion(PotionType.STRENGTH),
                factory.createPotion(PotionType.HEALING), factory.createPotion(PotionType.HEALING));

        bottomShelf = Arrays.asList(factory.createPotion(PotionType.POISON), factory.createPotion(PotionType.POISON),
            factory.createPotion(PotionType.POISON), factory.createPotion(PotionType.HOLY_WATER),
            factory.createPotion(PotionType.HOLY_WATER));
    }

    /**
     * 获取顶层架子上所有物品的只读列表
     *
     * @return 最顶层的药水
     */
    public final List<Potion> getTopShelf() {
        return new ArrayList<>(this.topShelf);
    }

    /**
     * 获取底层架子上所有物品的只读列表
     *
     * @return 最底层的药水架子
     */
    public final List<Potion> getBottomShelf() {
        return new ArrayList<>(this.bottomShelf);
    }

    /**
     * Drink all the potions.
     */
    public void drinkPotions() {
        log.info("喝顶层的药水");
        topShelf.forEach(Potion::drink);
        log.info("喝底架的药水");
        bottomShelf.forEach(Potion::drink);
    }
}

测试

/**
 * @author xs
 */
public class Main {
    public static void main(String[] args) {
        // 用药水创建炼金术士商店
        AlchemistShop alchemistShop = new AlchemistShop();
        // 一位勇敢的访客进入炼金术士商店并喝掉了所有药剂
        alchemistShop.drinkPotions();
    }
}

程序输出

[main] INFO com.xs.designpattern.AlchemistShop - 喝顶层的药水
[main] INFO com.xs.designpattern.InvisibilityPotion - 你变得隐形. (Potion=672320506)
[main] INFO com.xs.designpattern.InvisibilityPotion - 你变得隐形. (Potion=672320506)
[main] INFO com.xs.designpattern.StrengthPotion - 你感觉很坚强. (Potion=1682092198)
[main] INFO com.xs.designpattern.HealingPotion - 你感觉被治愈了.(Potion=1626877848)
[main] INFO com.xs.designpattern.InvisibilityPotion - 你变得隐形. (Potion=672320506)
[main] INFO com.xs.designpattern.StrengthPotion - 你感觉很坚强. (Potion=1682092198)
[main] INFO com.xs.designpattern.HealingPotion - 你感觉被治愈了.(Potion=1626877848)
[main] INFO com.xs.designpattern.HealingPotion - 你感觉被治愈了.(Potion=1626877848)
[main] INFO com.xs.designpattern.AlchemistShop - 喝底架的药水
[main] INFO com.xs.designpattern.PoisonPotion - 呃!这是有毒的. (Potion=2137589296)
[main] INFO com.xs.designpattern.PoisonPotion - 呃!这是有毒的. (Potion=2137589296)
[main] INFO com.xs.designpattern.PoisonPotion - 呃!这是有毒的. (Potion=2137589296)
[main] INFO com.xs.designpattern.HolyWaterPotion - 你感到幸福. (Potion=249515771)
[main] INFO com.xs.designpattern.HolyWaterPotion - 你感到幸福. (Potion=249515771)

类图

请添加图片描述

使用场景

享元模式适用于以下情况:

  • 当系统中存在大量相似对象,且这些对象的大部分状态是相同的时候。
  • 当需要降低对象的内存消耗,提高系统性能和效率时。
  • 当外部状态可以被外部管理时,而内部状态可以共享时。

优点

  • 减少了对象的创建,降低了内存开销,提高了系统的性能。
  • 可以共享相同的内部状态,使得系统更加轻量级。
  • 外部状态独立,可以被多个对象共享。

缺点

  • 对象的状态分为内部状态和外部状态,增加了系统的复杂性。
  • 需要维护一个享元池,可能会增加代码的维护成本。

7、代理模式(Proxy Pattern)

意图

它允许你创建一个代理对象,用于控制对另一个对象的访问。代理对象充当客户端与目标对象之间的中介,可以添加额外的逻辑,以实现不同的功能,例如延迟加载、访问控制、缓存等。

程序实例

现实世界的例子

想象一下当地巫师去象牙塔学习咒语。象牙塔只能通过代理人才能进去,这确保只有前三个巫师才能进入。这里代理人代表塔的功能并为其添加访问控制。

用简单的话来说

使用代理模式,一个类代表另一个类的功能。

维基百科说

代理,以其最一般的形式,是一个充当其他东西的接口的类。代理是一个包装器或代理对象,客户端调用它来访问幕后的真实服务对象。使用代理可以简单地转发到真实对象,或者可以提供额外的逻辑。在代理中可以提供额外的功能,例如当对真实对象的操作是资源密集型时进行缓存,或者在调用对真实对象的操作之前检查先决条件。

以上面的巫师塔为例。首先我们有WizardTower接口和IvoryTower类。

/**
 * 巫师塔接口
 * @author xs
 */
public interface WizardTower {
    /**
     * 定义了巫师进入塔内的方法
     * @param wizard 巫师
     */
    void enter(Wizard wizard);
}
/**
 * 巫师塔类
 * @author xs
 */
@Slf4j
public class IvoryTower implements WizardTower {
    /**
     * 用于巫师进入塔内
     * @param wizard 巫师
     */
    @Override
    public void enter(Wizard wizard) {
        log.info("{} 进入塔内.", wizard);
    }
}
/**
 * 巫师类
 * @author xs
 */
@RequiredArgsConstructor
public class Wizard {
    private final String name;

    @Override
    public String toString() {
        return name;
    }
}

然后我们使用代理类进行访问控制WizardTower

/**
 * 代理类
 *
 * @author xs
 */
@Slf4j
public class WizardTowerProxy implements WizardTower {
    /**
     * 限制进入塔内的巫师数量
     */
    private static final int NUM_WIZARDS_ALLOWED = 3;

    /**
     * 记录当前进入塔内的巫师数量
     */
    private int numWizards;

    private final WizardTower tower;

    public WizardTowerProxy(WizardTower tower) {
        this.tower = tower;
    }

    @Override
    public void enter(Wizard wizard) {
        // 检查巫师数量是否达到限制,如果没有达到,允许巫师进入,并增加巫师数量;否则,不允许巫师进入。
        if (numWizards < NUM_WIZARDS_ALLOWED) {
            tower.enter(wizard);
            numWizards++;
        } else {
            log.info("{} 不允许进入!", wizard);
        }
    }
}

通过使用代理模式,WizardTowerProxy 在巫师进入塔内之前添加了额外的逻辑,用于控制巫师的数量。这样,你可以限制进入塔内的巫师数量,确保不超过限制的人数。这是一个简单但有效的示例,展示了代理模式在控制访问方面的应用。

测试

/**
 * @author xs
 */
public class Main {
    public static void main(String[] args) {
        WizardTower proxy = new WizardTowerProxy(new IvoryTower());
        proxy.enter(new Wizard("红巫师"));
        proxy.enter(new Wizard("白巫师"));
        proxy.enter(new Wizard("黑巫师"));
        proxy.enter(new Wizard("绿巫师"));
        proxy.enter(new Wizard("棕色巫师"));
    }
}

程序输出

[main] INFO com.xs.designpattern.IvoryTower - 红巫师 进入塔内.
[main] INFO com.xs.designpattern.IvoryTower - 白巫师 进入塔内.
[main] INFO com.xs.designpattern.IvoryTower - 黑巫师 进入塔内.
[main] INFO com.xs.designpattern.WizardTowerProxy - 绿巫师 不允许进入!
[main] INFO com.xs.designpattern.WizardTowerProxy - 棕色巫师 不允许进入!

类图

请添加图片描述

使用场景

代理模式适用于以下情况:

  • 当需要控制对目标对象的访问,例如权限控制、延迟加载等。
  • 当需要在访问目标对象之前或之后添加额外的逻辑,而不改变客户端代码。
  • 当需要将对目标对象的访问进行封装,以便实现更复杂的交互逻辑。

优点

  • 代理模式可以实现对目标对象的访问控制,限制客户端直接访问。
  • 可以实现延迟加载,即在真正需要时再创建和初始化目标对象,提高性能。
  • 可以添加额外的逻辑,实现更复杂的功能,而无需修改客户端代码。

缺点

  • 增加了系统的复杂性,引入了额外的类。
  • 如果设计不当,可能会影响性能。

参考文档

Java Design Patterns

参考的github地址

其他分类

创造型模式

行为型模式

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

贪吃的小松鼠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值