// 双重锁用于降低 synchronized 方法的开销
public static ThreadSafeSingleton getInstanceDoubleLocking() {
if (instance == null) {
synchronized (ThreadSafeSingleton.class) {
if (instance == null) {
instance = new ThreadSafeSingleton();
return instance;
现在进入下一个分类。
类型2:结构型 - 装饰器设计模式
我将为你提供一个小场景,用来给出一个更好的上下文用来说明为什么以及在何处使用修饰器模式。
话说你拥有一家咖啡店,就像所有新手一样,刚开始你只需要家常咖啡和焦炒咖啡两种普通咖啡。在你的结算系统中,有一个类用于不同的混合咖啡,它继承了饮料抽象类。人们走进店里,准备喝你美妙(虽然苦涩)的咖啡。这时,有个咖啡新人想要加糖或牛奶,(很无语)真是对咖啡的嘲弄。
现在你需要将这两个配料放在菜单和结算系统上,你的IT人员别出心裁地为每种咖啡分别添加两个子类,一种加糖,一种加牛奶。这时,由于客户总是对的,其中一个又说这些可怕的话:
“请来一杯加糖的牛奶咖啡。”
望着你的结算系统,尴尬而又不失礼貌的微笑再次挂在了你的脸上。 好吧,回到绘图板……
然后,IT人员将加糖的牛奶咖啡作为子类添加到每个父咖啡类中。本月剩下的时间一帆风顺,人们排队等候你的咖啡,你实际上赚钱了。
但等等,这还不够!
整个世界又怼你,一个竞争对手在街对面开业,不仅有4种咖啡,还有超过10种配料!
为了自己的咖啡卖得更好,对手有的没有的你都买了,这时候你想起了忘记更新那个讨厌的结算系统。你几乎不可能为所有的配料组合添加无穷无尽的子类,更不用说最终系统的大小。
是时候接入正确的结算系统了。你找到了懂行的IT人员,他们说:“如果使用装饰器模式,这将更容易和更小。”
装饰器模式究竟是什么?
装饰器设计模式属于结构型,它处理类的实际结构,无论是继承,组合或是两者结合。它设计的目标是在运行时修改对象的功能,这也是许多其他设计模式的目的之一。
让我们给数学一个登场机会(不寒而栗?),来把这一切都带入视野:
4种混合咖啡和10种配料。如果我们坚持为每一种类型的咖啡的所有配料的每一种不同组合生成子类。那是:
(10–1)² = 9² = 81 个子类
我们从10中减去1,因为你不能将同一种配料混合,糖加糖听起来太蠢了,这只是一种混合咖啡。81乘以4,你会得到惊人的324个不同的子类!再想想那些涉及到的代码……
但是在这种情况下,装饰器模式只需要16个类。敢打赌吗?
如果我们根据上面的类图绘制出我们的场景,我们得到4个混合咖啡类,10个配料类,1个抽象组件,1个抽象装饰器。看!16个!现在交出100美元。(开玩笑别当真, 当然如果你愿意给,我也不会拒绝……嘛,说说而已)
从上面可以看出,正如实现混合咖啡类是饮料抽象类的子类一样,配料抽象类也从饮料抽象类继承了方法。配料类作为饮料抽象类的子类,能在需要时用自己的新方法向基础对象添加功能。
让我们来编码,看看这个模式如何运用。
首先制作抽象饮料类,所有不同的混合咖啡类将继承自饮料抽象类:
public abstract class Beverage {
private String description;
public Beverage(String description) {
super();
this.description = description;
public String getDescription() {
return description;
public abstract double cost();
然后添加实现混合咖啡类
public class HouseBlend extends Beverage {
public HouseBlend() {
super(“House blend”);
@Override
public double cost() {
return 250;
public class DarkRoast extends Beverage {
public DarkRoast() {
super(“Dark roast”);
@Override
public double cost() {
return 300;
配料抽象类也继承自饮料抽象类(更多内容见下文)。
public abstract class AddOn extends Beverage {
protected Beverage beverage;
public AddOn(String description, Beverage bev) {
super(description);
this.beverage = bev;
public abstract String getDescription();
现在这个抽象类的具体实现类:
public class Sugar extends AddOn {
public Sugar(Beverage bev) {
super(“Sugar”, bev);
@Override
public String getDescription() {
return beverage.getDescription() + “ with Mocha”;
@Override
public double cost() {
return beverage.cost() + 50;
public class Milk extends AddOn {
public Milk(Beverage bev) {
super(“Milk”, bev);
@Override
public String getDescription() {
return beverage.getDescription() + “ with Milk”;
@Override public double cost() {
return beverage.cost() + 100;
如你所见,我们可以将饮料抽象类的任何子类传递给配料抽象类的任何子类,并且获得增加的成本以及更新描述。由于配料抽象类继承自饮料抽象类,我们可以将配料抽象类传递给另一个配料抽象类。这样,我们就可以为特定的混合咖啡添加任意数量的配料。
现在编写一些代码来测试它。
public class CoffeeShop {
public static void main(String[] args) {
HouseBlend houseblend = new HouseBlend();
System.out.println(houseblend.getDescription() + “: “ + houseblend.cost());
Milk milkAddOn = new Milk(houseblend);
System.out.println(milkAddOn.getDescription() + “: “ + milkAddOn.cost());
Sugar sugarAddOn = new Sugar(milkAddOn);
System.out.println(sugarAddOn.getDescription() + “: “ + sugarAddOn.cost());
最终结果是:
运行成功!我们不需要为所有混合咖啡的每种配料组合添加无限多的子类,也能够在混合咖啡中添加多个配料,并成功更新其最终成本和描述。
来,看看最后一类。
类型3:行为型 - 命令设计模式
行为设计模式侧重于类、对象如何相互通信。命令模式的主要焦点是在所涉及的各个类之间灌输更高程度的低耦合。
呃……那是什么?
耦合是两个(或多个)类之间的交互方式。这些类交互时的理想情况是它们不会相互依赖,这被称为低耦合。所以,低耦合的良好定义是互相连接的类,彼此直接调用最少。
当需要发送请求而不需要明确知道请求者或接收者是谁时,就需要这种模式。
在这种模式中,调用类与实际执行操作的类分离。调用者类中只有在客户端请求时运行必要命令的可调用方法 execute
。
我们来看一个现实世界中的基本例子——在一家高档餐厅点餐。随着流程的进行,你将订单(命令)交给服务员(调用者),然后服务员将其交给厨师(接收者),这样你就可以获得食物。可能听起来很简单,但对于代码实现就有点……
技术方面的操作流程是,你点菜形成了一个订单(这个订单 Order
具体实现了命令接口要求厨师完成一道菜)。你把订单交个服务员(服务员 Waiter
就是调用者,他知道何时发出此命令)。服务员把订单交给厨师(收到特定订单时,厨师 Chef
是唯一知道具体该做什么的人,这就实现了低耦合)。如此,当调用者执行 execute
方法时,它调用订单对象的 execute
方法,而订单对象的 execute
方法又调用接收者对应的方法,从而完成必要的操作。
我们需要实现:
一个接口 Command
一个具体实现 Command
接口的 Order
类
一个 Waiter
类(调用者)
一个 Chef
类(接收者)
所以代码大概像这样:
厨师是接收者
public class Chef {
public void cookPasta() {
System.out.println(“Chef is cooking Chicken Alfredo…”);
public void bakeCake() {
System.out.println(“Chef is baking Chocolate Fudge Cake…”);
命令接口( Command Interface
)
public interface Command {
public abstract void execute();
订单是命令接口具体实现类
public class Order implements Command {
private Chef chef;
private String food;
public Order(Chef chef, String food) {
this.chef = chef;
this.food = food;
@Override
public void execute() {
if (this.food.equals(“Pasta”)) {
this.chef.cookPasta();
} else {
this.chef.bakeCake();
服务员是调用者
public class Waiter {
private Order order;
public Waiter(Order ord) {
this.order = ord;
public void execute() {
this.order.execute();
public class Client {
public static void main(String[] args) {
Chef chef = new Chef();
Order order = new Order(chef, “Pasta”);
Waiter waiter = new Waiter(order);
waiter.execute();
order = new Order(chef, “Cake”);
waiter = new Waiter(order);
waiter.execute();
如上所示,客户发出订单并将接收者设置为厨师。该订单将发送给服务员,服务员知道何时执行订单(即何时向厨师下订单进行烹饪)。当调用者执行程序时,订单的执行方法在接收者上运行(即主厨被命令要么煮意大利面要么烘烤蛋糕)。
快速回顾一下
在这篇文章中我们说明了:
真正的设计模式是什么
不同类型的设计模式以及它们为什么不同
每种类型的一个基本或通用设计模式
我希望这可以帮到你。
点击这里跳转到这篇文章示例代码的仓库
深入研究单例模式
对象和实例的区别
freeCodeCamp 是捐助者支持的 501(c)(3) 条款下具有免税资格的慈善组织(税号:82-0779546)。
我们的使命:帮助人们免费学习编程。我们通过创建成千上万的视频、文章和交互式编程课程——所有内容向公众免费开放——来实现这一目标。
所有给 freeCodeCamp 的捐款都将用于我们的教育项目,购买服务器和其他服务,以及聘用员工。
你可以点击此处免税捐款。
about:blank 是什么意思
打开 .dat 文件
Node 最新版本
反恶意软件服务
Windows10 产品密钥
Git 切换分支
AppData 文件夹
Windows 10 屏幕亮度
JSON 注释
MongoDB Atlas 教程
Python 字符串转数字
Git 命令
更新 NPM 依赖
谷歌恐龙游戏
CSS 使用 SVG 图片
Python 获取时间
Git Clone 指定分支
JS 字符串反转
React 个人作品网站
媒体查询范围
forEach 遍历数组
撤销 Git Add
OSI 七层网络
Event Loop 执行顺序
CMD 删除文件
Git 删除分支
HTML 表格代码
Nano 怎么保存退出
HTML5 模板