结构型模式(Structural Patterns)

2023-11-07 03:50

本文主要是介绍结构型模式(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();@Overridepublic 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;@Overridepublic void wield() {log.info("剑已挥动");enchantment.onActivate();}@Overridepublic void swing() {log.info("剑已挥出");enchantment.apply();}@Overridepublic void unwield() {log.info("剑未挥动");enchantment.onDeactivate();}@Overridepublic Enchantment getEnchantment() {return enchantment;}
}
/*** @author xs*/
@Slf4j
@AllArgsConstructor
public class Hammer implements Weapon {private final Enchantment enchantment;@Overridepublic void wield() {log.info("锤子被挥舞着...");enchantment.onActivate();}@Overridepublic void swing() {log.info("锤子挥动起来...");enchantment.apply();}@Overridepublic void unwield() {log.info("锤子未挥动...");enchantment.onDeactivate();}@Overridepublic Enchantment getEnchantment() {return enchantment;}
}

单独的附魔层次结构

/*** 结界* @author xs*/
public interface Enchantment {/*** 激活时*/void onActivate();/*** 使用时*/void apply();/*** 关闭时*/void onDeactivate();
}
/*** @author xs*/
@Slf4j
public class FlyingEnchantment implements Enchantment{@Overridepublic void onActivate() {log.info("该物品开始发出微弱的光芒...");}@Overridepublic void apply() {log.info("物品飞行并攻击敌人最终回到主人手中...");}@Overridepublic void onDeactivate() {log.info("该物品的光芒逐渐消失...");}
}
/*** @author xs*/
@Slf4j
public class SoulEatingEnchantment implements Enchantment {@Overridepublic void onActivate() {log.info("该物品传播嗜血...");}@Overridepublic void apply() {log.info("该物品会吞噬敌人的灵魂...");}@Overridepublic 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;/*** 方法被覆写,用于在打印字母前打印字母本身*/@Overrideprotected 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);}/*** 重新打印句子后打印.*/@Overrideprotected 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));}}/*** 重新方法用于在打印单词前打印一个空格*/@Overrideprotected 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{@Overridepublic void attack() {log.info("巨魔试图抓住你!");}@Overridepublic int getAttackPower() {return 10;}@Overridepublic void fleeBattle() {log.info("巨魔惊恐地尖叫着逃跑了!");}
}

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

/*** 棒状巨魔* @author xs*/
@Slf4j
@RequiredArgsConstructor
public class ClubbedTroll implements Troll {private final Troll decorated;@Overridepublic void attack() {decorated.attack();log.info("巨魔用棍子向你挥舞!");}@Overridepublic int getAttackPower() {return decorated.getAttackPower() + 10;}@Overridepublic 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() 方法,用于执行具体的工作*/@Overridepublic void work() {log.info("{}将金块移出矿井.", name());}/*** 实现了 name() 方法* @return 返回矮人的名称*/@Overridepublic String name() {return "矮人推车操作员";}
}
/*** 具体的矮人工人类,代表矮人淘金者。* @author xs*/
@Slf4j
public class DwarvenGoldDigger extends DwarvenMineWorker{/*** 实现了 work() 方法,用于执行具体的工作*/@Overridepublic void work() {log.info("{} 挖掘黄金.", name());}/*** 实现了 name() 方法* @return 返回矮人的名称*/@Overridepublic String name() {return "矮人淘金者";}
}
/*** 具体的矮人工人类,代表矮人隧道挖掘机* @author xs*/
@Slf4j
public class DwarvenTunnelDigger extends DwarvenMineWorker {/*** 实现了 work() 方法,用于执行具体的工作*/@Overridepublic void work() {log.info("{} 创建另一个有前途的隧道.", name());}/*** 实现了 name() 方法* @return 返回矮人的名称*/@Overridepublic 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{@Overridepublic void drink() {log.info("你感觉被治愈了.(Potion={})", System.identityHashCode(this));}
}
/*** 圣水药剂* @author xs*/
@Slf4j
public class HolyWaterPotion implements Potion {@Overridepublic void drink() {log.info("你感到幸福. (Potion={})", System.identityHashCode(this));}
}
/*** 隐形药水* @author xs*/
@Slf4j
public class InvisibilityPotion implements Potion{@Overridepublic void drink() {log.info("你变得隐形. (Potion={})", System.identityHashCode(this));}
}
/*** 毒药水* @author xs*/
@Slf4j
public class PoisonPotion implements Potion{@Overridepublic void drink() {log.info("呃!这是有毒的. (Potion={})", System.identityHashCode(this));}
}
/*** 力量药水* @author xs*/
@Slf4j
public class StrengthPotion implements Potion{@Overridepublic 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 巫师*/@Overridepublic void enter(Wizard wizard) {log.info("{} 进入塔内.", wizard);}
}
/*** 巫师类* @author xs*/
@RequiredArgsConstructor
public class Wizard {private final String name;@Overridepublic 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;}@Overridepublic 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地址

其他分类

创造型模式

行为型模式

这篇关于结构型模式(Structural Patterns)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/361069

相关文章

Java实现状态模式的示例代码

《Java实现状态模式的示例代码》状态模式是一种行为型设计模式,允许对象根据其内部状态改变行为,本文主要介绍了Java实现状态模式的示例代码,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来... 目录一、简介1、定义2、状态模式的结构二、Java实现案例1、电灯开关状态案例2、番茄工作法状态案例

在JS中的设计模式的单例模式、策略模式、代理模式、原型模式浅讲

1. 单例模式(Singleton Pattern) 确保一个类只有一个实例,并提供一个全局访问点。 示例代码: class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}Singleton.instance = this;this.data = [];}addData(value)

模版方法模式template method

学习笔记,原文链接 https://refactoringguru.cn/design-patterns/template-method 超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。 上层接口有默认实现的方法和子类需要自己实现的方法

【iOS】MVC模式

MVC模式 MVC模式MVC模式demo MVC模式 MVC模式全称为model(模型)view(视图)controller(控制器),他分为三个不同的层分别负责不同的职责。 View:该层用于存放视图,该层中我们可以对页面及控件进行布局。Model:模型一般都拥有很好的可复用性,在该层中,我们可以统一管理一些数据。Controlller:该层充当一个CPU的功能,即该应用程序

迭代器模式iterator

学习笔记,原文链接 https://refactoringguru.cn/design-patterns/iterator 不暴露集合底层表现形式 (列表、 栈和树等) 的情况下遍历集合中所有的元素

《x86汇编语言:从实模式到保护模式》视频来了

《x86汇编语言:从实模式到保护模式》视频来了 很多朋友留言,说我的专栏《x86汇编语言:从实模式到保护模式》写得很详细,还有的朋友希望我能写得更细,最好是覆盖全书的所有章节。 毕竟我不是作者,只有作者的解读才是最权威的。 当初我学习这本书的时候,只能靠自己摸索,网上搜不到什么好资源。 如果你正在学这本书或者汇编语言,那你有福气了。 本书作者李忠老师,以此书为蓝本,录制了全套视频。 试

利用命令模式构建高效的手游后端架构

在现代手游开发中,后端架构的设计对于支持高并发、快速迭代和复杂游戏逻辑至关重要。命令模式作为一种行为设计模式,可以有效地解耦请求的发起者与接收者,提升系统的可维护性和扩展性。本文将深入探讨如何利用命令模式构建一个强大且灵活的手游后端架构。 1. 命令模式的概念与优势 命令模式通过将请求封装为对象,使得请求的发起者和接收者之间的耦合度降低。这种模式的主要优势包括: 解耦请求发起者与处理者

springboot实战学习(1)(开发模式与环境)

目录 一、实战学习的引言 (1)前后端的大致学习模块 (2)后端 (3)前端 二、开发模式 一、实战学习的引言 (1)前后端的大致学习模块 (2)后端 Validation:做参数校验Mybatis:做数据库的操作Redis:做缓存Junit:单元测试项目部署:springboot项目部署相关的知识 (3)前端 Vite:Vue项目的脚手架Router:路由Pina:状态管理Eleme

状态模式state

学习笔记,原文链接 https://refactoringguru.cn/design-patterns/state 在一个对象的内部状态变化时改变其行为, 使其看上去就像改变了自身所属的类一样。 在状态模式中,player.getState()获取的是player的当前状态,通常是一个实现了状态接口的对象。 onPlay()是状态模式中定义的一个方法,不同状态下(例如“正在播放”、“暂停

软件架构模式:5 分钟阅读

原文: https://orkhanscience.medium.com/software-architecture-patterns-5-mins-read-e9e3c8eb47d2 软件架构模式:5 分钟阅读 当有人潜入软件工程世界时,有一天他需要学习软件架构模式的基础知识。当我刚接触编码时,我不知道从哪里获得简要介绍现有架构模式的资源,这样它就不会太详细和混乱,而是非常抽象和易