【设计模式深度剖析】【A】【创建型】【对比】| 工厂模式重点理解产品族的概念

回 顾:创建型设计模式

1.单例模式👈️

2.工厂方法模式👈️

3.抽象工厂模式👈️

4.建造者模式👈️

5.原型模式👈️

👈️上一篇:原型模式    |   👉️下一篇:代理模式

目录

  • 创建型模式对比
  • 概览
  • 1. 工厂方法模式
    • 类图
    • 1.1 抽象工厂角色:SuperManFactory:
    • 1.2 具体工厂类
      • 1.2.1 某具体工厂类:AdultSuperManFactory.java
      • 1.2.2 某具体工厂类:ChildSuperManFactory.java
    • 1.3 抽象产品角色:ISuperMan.java
    • 1.4 具体产品角色
      • 1.4.1 某具体产品类:AdultSuperMan.java
      • 1.4.2 某具体产品类:ChildSuperMan.java
    • 1.5 测试类:DemoTest.java
  • 2. 建造者模式
    • 类图
    • 2.1 抽象建造者:Builder.java
    • 2.2 具体建造者角色
      • 2.2.1 某具体建造者:AdultSuperManBuilder.java
      • 2.2.2 某具体建造者:ChildSuperManBuilder.java
    • 2.3 产品角色类:SuperMan.java
    • 2.4 导演者、指挥官角色:Director.java
    • 2.5 测试类:DemoTest.java
  • 3. 抽象工厂模式
    • 类图
    • 3.1 抽象工厂角色:HeroFactory.java
    • 3.2 具体工厂角色
      • 3.2.1 某具体工厂:AdultHeroFactory.java
      • 3.2.2 某具体工厂:ChildHeroFactory.java
    • 3.3 抽象产品-超人类:ISuperMan.java
      • 3.3.1 具体产品:AdultSuperMan.java
      • 3.3.2 具体产品:ChildSuperMan.java
    • 3.4 抽象产品-蜘蛛侠类:ISuperMan.java
      • 3.4.1 具体产品:AdultSpiderMan.java
      • 3.4.2 具体产品:ChildSpiderMan.java
    • 3.5 测试类

创建型模式对比

本文示例源码

创建型模式包括单例模式工厂方法模式抽象工厂模式建造者模式原型模式,这些模式都能够提供对象的创建和管理职责。

其中:

  1. 单例模式和原型模式非常容易理解,单例模式是在内存中保持只有一个对象;
  2. 原型模式是通过复制的方式产生一个新的对象,这两个模式不容易混淆,在此不做对比。

工厂方法模式抽象工厂模式建造者模式具有较多的相似性,它们之间的区别如下。

  1. 工厂方法模式注重的是整体对象的创建方法
  2. 建造者模式注重的是部件构建的过程,旨在通过一步步精确构造,创建出一个复杂的对象。
  3. 抽象工厂模式实现对产品家族的创建,抽象工厂模式不关心构建过程,只关心什么产品由什么工厂生产即可

下面分别使用工厂方法模式、建造者模式和抽象工厂模式制造超人,通过对比可以清晰地认识它们之间的差别。

工厂方法模式通过不同的工厂生产不同的超人,主要注重创建方法。

建造者模式则需要通过不同的建造者组装超人的各部分组件,然后通过导演类调用建造者的具体建造方法建造超人,最后通过建造者返回超人。建造者模式注重创建的过程。

抽象工厂模式通过不同的工厂生产一系列超级英雄,注重产品族的完整性

当一个对象族(或是一组没有任何关系的对象)都有相同的约束,则可以使用抽象工厂模式。(多个产品族1,2,3,产品族4… ,每个产品族都有A、B、C等等产品),
如果产品族中只有一种产品,则抽象工厂模式就退化为工厂方法模式

建造者角色抽象了产品各个属性的配置方法,并为客户端提供build()方法,按需构建所需对象。

抽象工厂模式针对的是生产不同产品类型,但是这些产品类型有不同的族别(或者叫做等级之类的),

比如生产汽车、摩托车,这是两种产品,如果仅仅生产这两种产品类型(也可以理解为笼统的、不区分产品族,或者说没有产品族的概念,或者说仅有一种产品族),使用工厂方法模式即可;

但是如果有了族别的差别,比如:运动型(Sporty)一族、豪华型(Luxury)一族、越野型(Off-Road)一族、经济型(Economy)一族、城市型(Urban)一族、旅行型(Touring)一族、复古型(Retro)一族,每一族都有对应的汽车产品和摩托车产品这种多种产品族的场景,每种产品族有对应的产品类型,此种情况,需要使用抽象工厂模式

在汽车和摩托车这两个产品类型中,除了运动型(Sporty)之外,还可以抽象出多种共有的产品族,这些产品族通常基于车辆的用途、风格、功能或者性能来划分。以下是一些示例:

  1. 豪华型(Luxury)
    • 豪华型汽车通常具有高级的内饰、舒适的座椅、先进的驾驶辅助系统和高级的音响系统。
    • 豪华型摩托车也追求同样的豪华体验,可能包括高级材料、精细的工艺、舒适的骑行位置和高级的电子设备。
  2. 越野型(Off-Road)
    • 越野型汽车通常具有强大的动力系统、高离地间隙、四驱系统以及适合越野驾驶的轮胎和悬挂系统。
    • 越野型摩托车(也称为冒险型或越野摩托车)也拥有类似的特性,用于穿越崎岖的地形和越野驾驶。
  3. 经济型(Economy)
    • 经济型汽车旨在提供高效、省油的驾驶体验,通常具有较小的排量、较轻的车身和较低的维护成本。
    • 经济型摩托车也注重燃油效率和成本控制,适合日常通勤和短途旅行。
  4. 城市型(Urban)
    • 城市型汽车通常设计紧凑、易于停放,并且具有良好的城市驾驶性能,如灵活的操控和快速的加速。
    • 城市型摩托车(也称为街车或城市通勤摩托车)也适合城市环境,提供轻便、灵活的驾驶体验。
  5. 旅行型(Touring)
    • 旅行型汽车拥有宽敞的内部空间、舒适的座椅和大量的储物空间,适合长途旅行和多人出行。
    • 旅行型摩托车(也称为长途摩托车或巡航摩托车)也提供了舒适的骑行环境、大量的储物空间和长途骑行的稳定性。
  6. 复古型(Retro)
    • 复古型汽车和摩托车都采用了经典的设计元素和风格,旨在唤起人们对过去的怀念和向往。

这些产品族都是汽车和摩托车共有的,可以根据市场需求和产品定位来设计和生产。通过使用抽象工厂模式,可以方便地管理和扩展这些产品族,以满足不同客户的需求。
但,如果只是简单的一个产品族,或者未区别产品族的情况,工厂方法模式即可满足需求。

概览

  • 工厂方法模式
    • 使用工厂方法模式制造超人,管理一个产品族产品生产
  • 建造者模式
    • 使用建造者模式制造超人:构造超人各个部分
  • 抽象工厂模式
    • 使用抽象工厂模式制造超人和蜘蛛侠,管理不同产品族产品生产

1. 工厂方法模式

本示例源码

类图

下述内容用于实现任务描述:使用工厂方法模式制造超人,其整体类图如下:

在这里插入图片描述

1.1 抽象工厂角色:SuperManFactory:

上层工厂接口,定义生产产品的标准,也就是定义了一个接口方法,生产超人的方法。

而且抽象工厂角色类,只规定标准,定义生产产品的方法,

它不关心具体怎样生产,那是具体工厂类该干的事情。

实现类也就是具体工厂类按照标准去实现即可。

public interface SuperManFactory {// 生产超人ISuperMan createSuperMan();
}

1.2 具体工厂类

实现抽象工厂,某具体工厂,会创建某具体产品实例

1.2.1 某具体工厂类:AdultSuperManFactory.java

某具体工厂类:成年超人工厂类,生产成年超人

public class AdultSuperManFactory implements SuperManFactory {// 制作成年超人@Overridepublic ISuperMan createSuperMan() {return new AdultSuperMan();}
}

1.2.2 某具体工厂类:ChildSuperManFactory.java

某具体工厂类:未成年超人工厂类,生产未成年超人

public class ChildSuperManFactory implements SuperManFactory {// 制造未成年人超人@Overridepublic ISuperMan createSuperMan() {return new ChildSuperMan();}
}

1.3 抽象产品角色:ISuperMan.java

抽象产品接口,规定了产品该有的功能,而不关心该功能的具体实现,

怎样实现该功能,那是具体产品类的事情,

实现类也就是具体产品类按照规定的标准去实现该功能即可。

public interface ISuperMan {// 超人的特殊能力void specialTalent();
}

1.4 具体产品角色

对抽象产品要求的标准(抽象方法)进行具体实现

1.4.1 某具体产品类:AdultSuperMan.java

某具体产品类,成年超人产品类,实现了抽象产品接口,完成了接口规定的方法

public class AdultSuperMan implements ISuperMan {@Overridepublic void specialTalent() {// 实现特殊能力System.out.println("成年超人力大无穷、可以飞行!");}
}

1.4.2 某具体产品类:ChildSuperMan.java

某具体产品类,未成年超人产品类,实现了抽象产品接口,完成了接口规定的方法

public class ChildSuperMan implements ISuperMan {@Overridepublic void specialTalent() {// 实现特殊能力System.out.println("小超人可以快速运动");}
}

1.5 测试类:DemoTest.java

测试抽象工厂类:

抽象工厂定义了产品生产方法;

具体工厂实现抽象工厂接口,实现了产品实例生产的方法。

public class DemoTest {public static void main(String[] args) {HeroFactory adultFactory = new AdultHeroFactory();ISuperMan adultSuperMan = adultFactory.createSuperMan();ISpiderMan adultSpiderMan = adultFactory.createSpiderMan();adultSuperMan.specialTalent();adultSpiderMan.launchSilk();System.out.println("--------------------");HeroFactory childFactory = new ChildHeroFactory();ISuperMan childSuperMan = childFactory.createSuperMan();ISpiderMan childSpiderMan = childFactory.createSpiderMan();childSuperMan.specialTalent();childSpiderMan.launchSilk();}
}
/* Output:
成年超人力大无穷、能飞行
成年蜘蛛侠发射出100米长的蛛丝
--------------------
未成年超人快速移动
幼年蜘蛛侠发射出5米长的蛛丝
*///~

2. 建造者模式

本示例源码

类图

下述内容用于实现任务描述:使用建造者模式制造超人,其整体类图如下:

在这里插入图片描述

2.1 抽象建造者:Builder.java

抽象建造者(Builder)角色,抽象了所有属性配置的方法,并提供build()方法,供客户端调用按需获取所需产品实例。

public abstract class Builder {protected final SuperMan superMan = new SuperMan();// 建造身体public abstract void setBody();// 建造能力public abstract void setSpecialTalent();// 建造标志public abstract void setSpecialSymbol();// 获得创建好的超人public SuperMan build() {return this.superMan;}
}

2.2 具体建造者角色

2.2.1 某具体建造者:AdultSuperManBuilder.java

某具体建造者,成年超人构建者,按照抽象建造者的标准提供具体实现,来构建成年超人

public class AdultSuperManBuilder extends Builder {@Overridepublic void setBody() {superMan.setBody("强壮的身体");}@Overridepublic void setSpecialTalent() {this.superMan.setSpecialTalent("可以飞行");}@Overridepublic void setSpecialSymbol() {superMan.setSpecialSymbol("胸前带S标记");}
}

2.2.2 某具体建造者:ChildSuperManBuilder.java

某具体建造者,未成年超人构建者,按照抽象建造者的标准提供具体实现,来构建未成年超人

public class ChildSuperManBuilder extends Builder {@Overridepublic void setBody() {this.superMan.setBody("灵敏的身子");}@Overridepublic void setSpecialTalent() {this.superMan.setSpecialTalent("快速运动");}@Overridepublic void setSpecialSymbol() {this.superMan.setSpecialSymbol("胸前带小S标记");}
}

2.3 产品角色类:SuperMan.java

产品(Product)角色,超人类

public class SuperMan {@Getter@Setter// 超人的身体private String body;@Getter@Setter// 超人的特殊能力private String specialTalent;@Getter@Setter// 超人的标志private String specialSymbol;@Overridepublic String toString() {return this.getBody() + "," +this.getSpecialTalent() + "," +this.getSpecialSymbol();}
}

2.4 导演者、指挥官角色:Director.java

客户端,调用建造者实例的方法来获得所需对象

public class Director {private Builder builder;public SuperMan buildSuperMan(String type) {if (type.equals("child")) {builder = new ChildSuperManBuilder();} else if (type.equals("adult")) {builder = new AdultSuperManBuilder();} else {return null;}builder.setBody();builder.setSpecialTalent();builder.setSpecialSymbol();return builder.build();}
}

2.5 测试类:DemoTest.java

package com.polaris.designpattern.list1.creational.comparison.builder;public class DemoTest {public static void main(String[] args) {Director director = new Director();SuperMan adultSuperMan = director.buildSuperMan("adult");System.out.println("成年超人:" + adultSuperMan);SuperMan childSuperMan = director.buildSuperMan("child");System.out.println("小超人:" + childSuperMan);}
}/* Output:
成年超人:强壮的身体,可以飞行,胸前带S标记
小超人:灵敏的身子,快速运动,胸前带小S标记
*///~

3. 抽象工厂模式

本示例源码
这里的示例中,有两个产品族:成年和未成年的英雄,每个产品族工厂都能创建超人和蜘蛛侠两类产品

类图

下述内容用于实现任务描述:使用抽象工厂模式制造超人和蜘蛛侠,其整体类图如下:

在这里插入图片描述

3.1 抽象工厂角色:HeroFactory.java

约定了生产两种产品,超人和蜘蛛侠这两种产品

public interface HeroFactory {// 生产超人ISuperMan createSuperMan();// 生产蜘蛛侠ISpiderMan createSpiderMan();
}

3.2 具体工厂角色

3.2.1 某具体工厂:AdultHeroFactory.java

针对成年英雄产品族,生产成年英雄,

实现生产超人和蜘蛛侠这两种产品的方法,

因此该工厂生产成年(产品族)的超人和蜘蛛侠

public class AdultHeroFactory implements HeroFactory {@Overridepublic ISuperMan createSuperMan() {return new AdultSuperMan();}@Overridepublic ISpiderMan createSpiderMan() {return new AdultSpiderMan();}
}

3.2.2 某具体工厂:ChildHeroFactory.java

针对未成年英雄产品族,生产未成年英雄,

实现生产超人和蜘蛛侠这两种产品的方法,

因此该工厂生产未成年(产品族)的超人和蜘蛛侠

public class ChildHeroFactory implements HeroFactory {@Overridepublic ISuperMan createSuperMan() {return new ChildSuperMan();}@Overridepublic ISpiderMan createSpiderMan() {return new ChildSpiderMan();}
}

3.3 抽象产品-超人类:ISuperMan.java

规定了超人产品所具备的功能

public interface ISuperMan {// 超人的特殊能力public abstract void specialTalent();
}

3.3.1 具体产品:AdultSuperMan.java

成年一族产品,超人产品,成年超人产品类

public class AdultSuperMan implements ISuperMan {@Overridepublic void specialTalent() {System.out.println("成年超人力大无穷、能飞行");}
}

3.3.2 具体产品:ChildSuperMan.java

未成年一族产品,超人产品,未成年超人产品类

public class ChildSuperMan implements ISuperMan {@Overridepublic void specialTalent() {System.out.println("未成年超人快速移动");}
}

3.4 抽象产品-蜘蛛侠类:ISuperMan.java

规定了蜘蛛侠产品所具备的功能

public interface ISpiderMan {// 发出蛛丝public abstract void launchSilk();
}

3.4.1 具体产品:AdultSpiderMan.java

成年一族产品,蜘蛛侠产品,成年蜘蛛侠产品类

public class AdultSpiderMan implements ISpiderMan {@Overridepublic void launchSilk() {System.out.println("成年蜘蛛侠发射出100米长的蛛丝");}
}

3.4.2 具体产品:ChildSpiderMan.java

未成年一族产品,蜘蛛侠产品,未成年蜘蛛侠产品类

public class ChildSpiderMan implements ISpiderMan {@Overridepublic void launchSilk() {System.out.println("幼年蜘蛛侠发射出5米长的蛛丝");}
}        

3.5 测试类

public class DemoTest {public static void main(String[] args) {HeroFactory adultFactory = new AdultHeroFactory();ISuperMan adultSuperMan = adultFactory.createSuperMan();ISpiderMan adultSpiderMan = adultFactory.createSpiderMan();adultSuperMan.specialTalent();adultSpiderMan.launchSilk();System.out.println("--------------------");HeroFactory childFactory = new ChildHeroFactory();ISuperMan childSuperMan = childFactory.createSuperMan();ISpiderMan childSpiderMan = childFactory.createSpiderMan();childSuperMan.specialTalent();childSpiderMan.launchSilk();}
}
/* Output:
成年超人力大无穷、能飞行
成年蜘蛛侠发射出100米长的蛛丝
--------------------
未成年超人快速移动
幼年蜘蛛侠发射出5米长的蛛丝
*///~

回 顾:创建型设计模式

1.单例模式👈️

2.工厂方法模式👈️

3.抽象工厂模式👈️

4.建造者模式👈️

5.原型模式👈️

👈️上一篇:原型模式    |   👉️下一篇:代理模式

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/14421.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

高中数学:平面向量-基本定理

一、基本定理 二、向量夹角 三、常用性质 三角形中线对应的向量与三角形两边的关系 四、练习 例题1 例题2 解 例题3 解 此题,用到了向量的基本定理:若 a → \mathop{a}\limits ^{\rightarrow} a→、 b → \mathop{b}\limits ^{\rightarrow} b→​…

领券拿外卖返利红包,最低0元吃外卖

小蚕荟是利用本地资源和自媒体优势构建的“本地生活服务”平台,总部位于杭州,旨在为用户提供热门的吃喝玩乐本地生活服务类产品。布局已覆盖杭州、南京、上海等一二线城市。 小蚕荟是一款专为用户吃外卖省钱的生活工具,单单可返利15元起&…

ASP+ACCESS基于WEB车辆管理系统

3.1 系统需求分析 该过程是个不断认识不断细化的过程。这里所要完成的工作是深入描述软件的功能和性能,确定软件的设计限制和软件同其他系统元素的接口细节,从而奠定软件的开发基础。 性能需求:该系统中,管理员模块只有管理人员…

CentOS7离线安装Nginx

目录 1. 安装gcc2. 安装g3. 安装openssl4. 安装pcre5. 安装zlib6. 安装Nginx7. 启动nginx8. 开放80端口9. 访问测试10. 设置开机自启 Nginx离线安装需要依赖gcc、g环境,安装前要先检查linux系统中是否自带gcc和g,如果没有就需要先进行安装。 然后再安装o…

【MySQL精通之路】InnoDB(4)-架构图

下图显示了构成InnoDB存储引擎体系结构的内存和磁盘结构。有关每个结构的信息 请参阅“内存中的InnoDB结构”和“磁盘上的InnoDB结构”。

Jmeter+prometheus+grafana性能测试

文章目录 Jmeterprometheusgrafana性能测试背景目标设计思路原理案例启发 Jmeterprometheusgrafana性能测试 背景 ​ 在现代社会中,人们对于应用程序的响应速度和性能体验提出了越来越高的要求。无论是电子商务网站、社交媒体平台还是企业级软件系统,都…

6款网站登录页(附带源码)

6款网站登录页 效果图及部分源码123456 领取源码下期更新预报 效果图及部分源码 1 部分源码 <style>* {margin: 0;padding: 0;}html {height: 100%;}body {height: 100%;}.container {height: 100%;background-image: linear-gradient(to right, #fbc2eb, #a6c1ee);}.l…

ipad协议849最新版

ipad协议其实就是模拟ipad端微信的人工操作&#xff0c;跟微信服务器通信。协议的关键点主要是PB协议、mmtls、06加密算法、rqt算法、aes加密、rsa加密等&#xff0c;只要把这些点拿下&#xff0c;就可以模拟官方微信的所有功能了&#xff0c;还可以模拟android、pc、mac端的登…

flutter开发实战-美颜前后对比图效果实现

flutter开发实战-美颜前后对比图效果实现 最近使用代码中遇到了图片前后对比&#xff0c;这里使用的是CustomClipper来实现 一、CustomClipper 我们实现CustomClipper子类来实现美颜后的图片裁剪功能 getClip()是用于获取剪裁区域的接口&#xff0c;由于图片大小是6060&am…

Flash与EEPROM

文章目录 1. 分类2. 工作原理2.1 擦除操作2.2 写入操作 3. 参考资料 1. 分类 2. 工作原理 在存储数据之前&#xff0c;先擦除存储区域&#xff08;写成全1&#xff09;&#xff0c;进行存储时&#xff0c;将对应位写为0。 注&#xff1a;这里编程不能反向&#xff0c;若写错了…

嵌入式岗位,你有能力,你同样可以拿到高薪资

在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「嵌入式的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01; 就算你进去了&#xff0…

用手机打印需要下载什么软件

在快节奏的现代生活中&#xff0c;打印需求无处不在&#xff0c;无论是工作文件、学习资料还是生活小贴士&#xff0c;都可能需要一纸呈现。然而&#xff0c;传统的打印方式往往受限于时间和地点&#xff0c;让人倍感不便。今天&#xff0c;就为大家推荐一款便捷又省钱的手机打…

做OZON怎么选择物流,OZON物流Xingyuan

随着跨境电商的蓬勃发展&#xff0c;OZON作为俄罗斯领先的电商平台&#xff0c;吸引了大量中国卖家入驻。然而&#xff0c;物流作为跨境电商的关键环节&#xff0c;其选择对于卖家来说至关重要。本文将围绕“做OZON怎么选择物流”这一问题&#xff0c;深度解析OZON物流Xingyuan…

“云加”万里信,共赴山海约,解密协同云官网设计之路

门户的设计改版是怎样的&#xff1f;本文从浪潮海岳云加案例出发&#xff0c;手把手带你认识完整系统的门户设计升级。 一、升级背景 1、云加官网作为云加产品对外唯一官方门户&#xff0c;承载整个云加业务售前及售中的核心渠道&#xff0c;是用户接触云加产品的重要渠道之一…

解决GoLand无法Debug

goland 调试的的时候提示如下错误 WARNING: undefined behavior - version of Delve is too old for Go version 1.22.3 (maximum supported v 其实个原因是因为正在使用的Delve调试器版本太旧&#xff0c;无法兼容当前的Go语言版本1.22.3。Delve是Go语言的一个调试工具&#…

构建稳健、高效与安全的企业级API网关

在现代企业信息化建设中&#xff0c;各种微服务架构系统以及不同类型的管理系统广泛兴起&#xff0c;平台中的数据安全逐渐成为企业重视的部分&#xff0c;在iPaaS系统中&#xff0c;一个名为“企业级API网关”的功能出现在大众眼中&#xff0c;随着企业信息化建设的不断深入&a…

vue3+ts+vant4 实现购物车 前端代码

一、功能效果 二、前端代码 购物车的vue代码 <template><van-nav-bar left-text"返回" title"购物车" click-left"onClickLeft"><template #right><van-popover v-model:show"showPopover" placement"bot…

Transormer(2)-位置编码

位置编码公式 偶数位置用sin,奇数位置用cos. d_model 表示token的维度&#xff1b;pos表示token在序列中的位置&#xff1b;i表示每个token编码的第i个位置&#xff0c;属于[0,d_model)。 torch实现 import math import torch from torch import nn from torch.autograd im…

移动云ECS主机:未来云计算的驱动力

文章目录 前言一、移动云云主机ECS云主机ECS产品优势云主机ECS产品功能云主机ECS应用场景 二、移动云云主机ECS选购三、移动云云主机ECS配置四、移动云云主机ECS牛刀小试五、移动云云主机ECS安装部署消息中间件RocketMQ云主机ECS安装RocketMQ云主机ECS配置RocketMQ云主机ECS启动…

ubuntu22部署Docker私有仓库Harbor (http https方式)

harbor日志&#xff1a;/var/log/harbor 前置安装配置 需先安装docker和docker-compose&#xff1a; 0.配置清华大学apt源并安装docker #信任 Docker 的 GPG 公钥: sudo apt-get install ca-certificates curl gnupg curl -fsSL https://download.docker.com/linux/ubunt…