首页 > 其他分享 >2023跟我一起学设计模式:抽象工厂模式( Abstract Factory)

2023跟我一起学设计模式:抽象工厂模式( Abstract Factory)

时间:2023-06-18 20:00:58浏览次数:39  
标签:Abstract Factory 接口 工厂 抽象 产品 设计模式 代码 客户端

抽象工厂模式( Abstract Factory)

意图

抽象工厂模式是一种创建型设计模式, 它能创建一系列相关的对象, 而无需指定其具体类。

2023跟我一起学设计模式:抽象工厂模式( Abstract Factory)_设计模式

问题

假设你正在开发一款家具商店模拟器。 你的代码中包括一些类, 用于表示:

  1. 一系列相关产品, 例如 椅子Chair 、 沙发Sofa和 咖啡桌Coffee­Table 。
  2. 系列产品的不同变体。 例如, 你可以使用 现代Modern 、 维多利亚Victorian 、 装饰风艺术Art­Deco等风格生成 椅子沙发咖啡桌

2023跟我一起学设计模式:抽象工厂模式( Abstract Factory)_工厂类_02

系列产品及其不同变体。

你需要设法单独生成每件家具对象, 这样才能确保其风格一致。 如果顾客收到的家具风格不一样, 他们可不会开心。

2023跟我一起学设计模式:抽象工厂模式( Abstract Factory)_golang_03

现代风格的沙发和维多利亚风格的椅子不搭。

此外, 你也不希望在添加新产品或新风格时修改已有代码。 家具供应商对于产品目录的更新非常频繁, 你不会想在每次更新时都去修改核心代码的。

解决方案

首先, 抽象工厂模式建议为系列中的每件产品明确声明接口 (例如椅子、 沙发或咖啡桌)。 然后, 确保所有产品变体都继承这些接口。 例如, 所有风格的椅子都实现 椅子接口; 所有风格的咖啡桌都实现 咖啡桌接口, 以此类推。

2023跟我一起学设计模式:抽象工厂模式( Abstract Factory)_工厂类_04

同一对象的所有变体都必须放置在同一个类层次结构之中。

接下来, 我们需要声明抽象工厂——包含系列中所有产品构造方法的接口。 例如 create­Chair创建椅子 、 create­Sofa创建沙发和 create­Coffee­Table创建咖啡桌 。 这些方法必须返回抽象产品类型, 即我们之前抽取的那些接口: 椅子沙发咖啡桌等等。

2023跟我一起学设计模式:抽象工厂模式( Abstract Factory)_工厂类_05

每个具体工厂类都对应一个特定的产品变体。

那么该如何处理产品变体呢? 对于系列产品的每个变体, 我们都将基于 抽象工厂接口创建不同的工厂类。 每个工厂类都只能返回特定类别的产品, 例如, 现代家具工厂Modern­Furniture­Factory只能创建 现代椅子Modern­Chair 、 现代沙发Modern­Sofa和 现代咖啡桌Modern­Coffee­Table对象。

客户端代码可以通过相应的抽象接口调用工厂和产品类。 你无需修改实际客户端代码, 就能更改传递给客户端的工厂类, 也能更改客户端代码接收的产品变体。

2023跟我一起学设计模式:抽象工厂模式( Abstract Factory)_客户端_06

客户端无需了解其所调用工厂的具体类信息。

假设客户端想要工厂创建一把椅子。 客户端无需了解工厂类, 也不用管工厂类创建出的椅子类型。 无论是现代风格, 还是维多利亚风格的椅子, 对于客户端来说没有分别, 它只需调用抽象 椅子接口就可以了。 这样一来, 客户端只需知道椅子以某种方式实现了 sit­On坐下方法就足够了。 此外, 无论工厂返回的是何种椅子变体, 它都会和由同一工厂对象创建的沙发或咖啡桌风格一致。

最后一点说明: 如果客户端仅接触抽象接口, 那么谁来创建实际的工厂对象呢? 一般情况下, 应用程序会在初始化阶段创建具体工厂对象。 而在此之前, 应用程序必须根据配置文件或环境设定选择工厂类别。

抽象工厂模式结构

2023跟我一起学设计模式:抽象工厂模式( Abstract Factory)_抽象工厂_07

  1. 抽象产品 (Abstract Product) 为构成系列产品的一组不同但相关的产品声明接口。
  2. 具体产品 (Concrete Product) 是抽象产品的多种不同类型实现。 所有变体 (维多利亚/现代) 都必须实现相应的抽象产品 (椅子/沙发)。
  3. 抽象工厂 (Abstract Factory) 接口声明了一组创建各种抽象产品的方法。
  4. 具体工厂 (Concrete Factory) 实现抽象工厂的构建方法。 每个具体工厂都对应特定产品变体, 且仅创建此种产品变体。
  5. 尽管具体工厂会对具体产品进行初始化, 其构建方法签名必须返回相应的抽象产品。 这样, 使用工厂类的客户端代码就不会与工厂创建的特定产品变体耦合。 客户端 (Client) 只需通过抽象接口调用工厂和产品对象, 就能与任何具体工厂/产品变体交互。

伪代码

下面例子通过应用抽象工厂模式, 使得客户端代码无需与具体 UI 类耦合, 就能创建跨平台的 UI 元素, 同时确保所创建的元素与指定的操作系统匹配。

2023跟我一起学设计模式:抽象工厂模式( Abstract Factory)_工厂类_08

跨平台 UI 类示例。

跨平台应用中的相同 UI 元素功能类似, 但是在不同操作系统下的外观有一定差异。 此外, 你需要确保 UI 元素与当前操作系统风格一致。 你一定不希望在 Windows 系统下运行的应用程序中显示 macOS 的控件。

抽象工厂接口声明一系列构建方法, 客户端代码可调用它们生成不同风格的 UI 元素。 每个具体工厂对应特定操作系统, 并负责生成符合该操作系统风格的 UI 元素。

其运作方式如下: 应用程序启动后检测当前操作系统。 根据该信息, 应用程序通过与该操作系统对应的类创建工厂对象。 其余代码使用该工厂对象创建 UI 元素。 这样可以避免生成错误类型的元素。

使用这种方法, 客户端代码只需调用抽象接口, 而无需了解具体工厂类和 UI 元素。 此外, 客户端代码还支持未来添加新的工厂或 UI 元素。

这样一来, 每次在应用程序中添加新的 UI 元素变体时, 你都无需修改客户端代码。 你只需创建一个能够生成这些 UI 元素的工厂类, 然后稍微修改应用程序的初始代码, 使其能够选择合适的工厂类即可。


抽象工厂模式适合应用场景

如果代码需要与多个不同系列的相关产品交互, 但是由于无法提前获取相关信息, 或者出于对未来扩展性的考虑, 你不希望代码基于产品的具体类进行构建, 在这种情况下, 你可以使用抽象工厂。

抽象工厂为你提供了一个接口, 可用于创建每个系列产品的对象。 只要代码通过该接口创建对象, 那么你就不会生成与应用程序已生成的产品类型不一致的产品。

如果你有一个基于一组抽象方法的类, 且其主要功能因此变得不明确, 那么在这种情况下可以考虑使用抽象工厂模式。

在设计良好的程序中, 每个类仅负责一件事。 如果一个类与多种类型产品交互, 就可以考虑将工厂方法抽取到独立的工厂类或具备完整功能的抽象工厂类中。

实现方式

  1. 以不同的产品类型与产品变体为维度绘制矩阵。
  2. 为所有产品声明抽象产品接口。 然后让所有具体产品类实现这些接口。
  3. 声明抽象工厂接口, 并且在接口中为所有抽象产品提供一组构建方法。
  4. 为每种产品变体实现一个具体工厂类。
  5. 在应用程序中开发初始化代码。 该代码根据应用程序配置或当前环境, 对特定具体工厂类进行初始化。 然后将该工厂对象传递给所有需要创建产品的类。
  6. 找出代码中所有对产品构造函数的直接调用, 将其替换为对工厂对象中相应构建方法的调用。

抽象工厂模式优缺点

  • 你可以确保同一工厂生成的产品相互匹配。
  • 你可以避免客户端和具体产品代码的耦合。
  • 单一职责原则。 你可以将产品生成代码抽取到同一位置, 使得代码易于维护。
  • 开闭原则。 向应用程序中引入新产品变体时, 你无需修改客户端代码。
  • 由于采用该模式需要向应用中引入众多接口和类, 代码可能会比之前更加复杂。

Go 抽象工厂模式讲解和代码示例

抽象工厂是一种创建型设计模式, 它能创建一系列相关的对象, 而无需指定其具体类。

抽象工厂定义了用于创建不同产品的接口, 但将实际的创建工作留给了具体工厂类。 每个工厂类型都对应一个特定的产品变体。

在创建产品时, 客户端代码调用的是工厂对象的构建方法, 而不是直接调用构造函数 ( new操作符)。 由于一个工厂对应一种产品变体, 因此它创建的所有产品都可相互兼容。

客户端代码仅通过其抽象接口与工厂和产品进行交互。 该接口允许同一客户端代码与不同产品进行交互。 你只需创建一个具体工厂类并将其传递给客户端代码即可。

概念示例

让我们假设一下, 如果你想要购买一组运动装备, 比如一双鞋与一件衬衫这样由两种不同产品组合而成的套装。 相信你会想去购买同一品牌的商品, 这样商品之间能够互相搭配起来。

如果我们把这样的行为转换成代码的话, 帮助我们创建此类产品组的工具就是抽象工厂, 便于产品之间能够相互匹配。

iSportsFactory.go: 抽象工厂接口
package main

import "fmt"

type ISportsFactory interface {
    makeShoe() IShoe
    makeShirt() IShirt
}

func GetSportsFactory(brand string) (ISportsFactory, error) {
    if brand == "adidas" {
        return &Adidas{}, nil
    }

    if brand == "nike" {
        return &Nike{}, nil
    }

    return nil, fmt.Errorf("Wrong brand type passed")
}
adidas.go: 具体工厂
package main

type Adidas struct {
}

func (a *Adidas) makeShoe() IShoe {
    return &AdidasShoe{
        Shoe: Shoe{
            logo: "adidas",
            size: 14,
        },
    }
}

func (a *Adidas) makeShirt() IShirt {
    return &AdidasShirt{
        Shirt: Shirt{
            logo: "adidas",
            size: 14,
        },
    }
}
nike.go: 具体工厂
package main

type Nike struct {
}

func (n *Nike) makeShoe() IShoe {
    return &NikeShoe{
        Shoe: Shoe{
            logo: "nike",
            size: 14,
        },
    }
}

func (n *Nike) makeShirt() IShirt {
    return &NikeShirt{
        Shirt: Shirt{
            logo: "nike",
            size: 14,
        },
    }
}
iShoe.go: 抽象产品
package main

type IShoe interface {
    setLogo(logo string)
    setSize(size int)
    getLogo() string
    getSize() int
}

type Shoe struct {
    logo string
    size int
}

func (s *Shoe) setLogo(logo string) {
    s.logo = logo
}

func (s *Shoe) getLogo() string {
    return s.logo
}

func (s *Shoe) setSize(size int) {
    s.size = size
}

func (s *Shoe) getSize() int {
    return s.size
}
adidasShoe.go: 具体产品
package main

type AdidasShoe struct {
    Shoe
}
nikeShoe.go: 具体产品
package main

type NikeShoe struct {
    Shoe
}
iShirt.go: 抽象产品
package main

type IShirt interface {
    setLogo(logo string)
    setSize(size int)
    getLogo() string
    getSize() int
}

type Shirt struct {
    logo string
    size int
}

func (s *Shirt) setLogo(logo string) {
    s.logo = logo
}

func (s *Shirt) getLogo() string {
    return s.logo
}

func (s *Shirt) setSize(size int) {
    s.size = size
}

func (s *Shirt) getSize() int {
    return s.size
}
adidasShirt.go: 具体产品
package main

type AdidasShirt struct {
    Shirt
}
nikeShirt.go: 具体产品
package main

type NikeShirt struct {
    Shirt
}
main.go: 客户端代码
package main

import "fmt"

func main() {
    adidasFactory, _ := GetSportsFactory("adidas")
    nikeFactory, _ := GetSportsFactory("nike")

    nikeShoe := nikeFactory.makeShoe()
    nikeShirt := nikeFactory.makeShirt()

    adidasShoe := adidasFactory.makeShoe()
    adidasShirt := adidasFactory.makeShirt()

    printShoeDetails(nikeShoe)
    printShirtDetails(nikeShirt)

    printShoeDetails(adidasShoe)
    printShirtDetails(adidasShirt)
}

func printShoeDetails(s IShoe) {
    fmt.Printf("Logo: %s", s.getLogo())
    fmt.Println()
    fmt.Printf("Size: %d", s.getSize())
    fmt.Println()
}

func printShirtDetails(s IShirt) {
    fmt.Printf("Logo: %s", s.getLogo())
    fmt.Println()
    fmt.Printf("Size: %d", s.getSize())
    fmt.Println()
}
output.txt: 执行结果
Logo: nike
Size: 14
Logo: nike
Size: 14
Logo: adidas
Size: 14
Logo: adidas
Size: 14

标签:Abstract,Factory,接口,工厂,抽象,产品,设计模式,代码,客户端
From: https://blog.51cto.com/demo007x/6509319

相关文章

  • 2023跟我一起学设计模式:Golang 抽象工厂模式讲解和代码示例
    Golang抽象工厂模式讲解和代码示例抽象工厂是一种创建型设计模式,它能创建一系列相关的对象,而无需指定其具体类。抽象工厂定义了用于创建不同产品的接口,但将实际的创建工作留给了具体工厂类。每个工厂类型都对应一个特定的产品变体。在创建产品时,客户端代码调用的是工厂对象的......
  • abstract class CBase{ public abstract void say_sth();
    packageTest;publicclasshello{ /** *@paramargs */ publicstaticvoidmain(String[]args){ //TODOAuto-generatedmethodstub Stringarg1="world!"; System.out.printf("hello%s\n",arg1); CBaseobj; obj=newCDer......
  • 设计模式的原则(一)
    相信自己,无论自己到了什么局面,请一定要继续相信自己。新的世界开始了,接下来,老蝴蝶带领大家学习一下设计模式。我们先了解一下设计原则一.设计模式一.一设计原则设计模式常用的七大原则:单一职责原则接口隔离原则依赖倒转(倒置)原则里氏替换原则开闭原则迪米特法则合成复用原则一.......
  • PHP开发:代码风格、重构和设计模式的实践
    一、代码风格和规范:采用一致的代码风格和规范有助于提高代码的可读性和可维护性。我们将介绍一些常见的PHP代码风格指南,如PSR-12(PHPStandardRecommendation),以及一些静态代码分析工具,如PHPCodeSniffer,可以帮助您自动检测代码规范问题。示例代码风格(使用PSR-12):<?phpnamespaceV......
  • 设计模式:适配器模式(论如何把鼠头适配成鸭脖)
    适配器模式(AdapterPattern)有时候也称包装样式或者包装,是一种结构型设计模式,它可以将一个类的接口转换成客户端所期望的另一个接口。适配器模式可以让原本由于接口不兼容而不能一起工作的那些类可以一起工作。适配器模式有三种类型:类适配器模式、对象适配器模式和接口适配器模式......
  • Go设计模式实战--用状态模式实现系统工作流和状态机
    大家好,这里是每周都在陪你进步的网管~!本节我们讲一个行为型的设计模式--状态模式,并通过Golang示例进行实战演示。状态模式(StatePattern)也叫作状态机模式(StateMachinePattern)状态模式允许对象的内部状态发生改变时,改变它的行为,就好像对象看起来修改了它实例化的类,状态模式是一种......
  • 【Azure 应用服务】Azure Data Factory中调用Function App遇见403 - Forbidden
    问题描述在AzureDataFactory(数据工厂)中,调用同在Azure中的FunctionApp函数,却出现403-Forbidden错误。截图如下:  问题解答访问AzureFunctionApp遇见403-Forbidden错误,这是因为FunctionApp启用了限制访问功能,在其中配置了允许访问的IP地址列表,而从ADF中发出的请求使用的I......
  • 设计模式:适配器模式(论如何把鼠头适配加工成鸭脖)
    定义适配器模式(AdapterPattern)有时候也称包装样式或者包装,是一种结构型设计模式,它可以将一个类的接口转换成客户端所期望的另一个接口。适配器模式可以让原本由于接口不兼容而不能一起工作的那些类可以一起工作。适配器模式有三种类型:类适配器模式、对象适配器模式和接口适配器......
  • Python设计模式-07-装饰模式
    装饰模式是一种结构型设计模式,它允许我们动态地将行为添加到对象中,而不需要使用继承。装饰模式通常包括以下几个角色:抽象组件(Component):定义了一个接口,用于被装饰对象和装饰器共同实现。具体组件(ConcreteComponent):实现了抽象组件定义的接口,并提供了默认的行为。抽象装饰......
  • Python设计模式-17-外观模式
    外观模式是一种结构型设计模式,它为复杂的子系统提供了一个简单的接口,从而隐藏了子系统的复杂性。外观模式通常包括以下几个角色:外观(Facade):提供了一个简单的接口,用于访问子系统中的一组接口。子系统(Subsystem):实现了子系统的功能,并处理外观对象指派的任务。下面是一个简单......