我的設計模式之旅、10 抽象工廠

来源:https://www.cnblogs.com/linxiaoxu/archive/2022/09/16/16700346.html
-Advertisement-
Play Games

我的設計模式之旅,本節使用抽象工廠模式實現阿迪達斯、耐克品牌服飾生產,分別用C#跟Golang實現。對抽象方法模式進行了細緻的介紹。 ...


一個菜鳥的設計模式之旅,文章可能會有不對的地方,懇請大佬指出錯誤。

編程旅途是漫長遙遠的,在不同時刻有不同的感悟,本文會一直更新下去。

程式介紹

你想要購買一組運動裝備, 比如一雙鞋與一件襯衫這樣由兩種不同產品組合而成的套裝。 相信你會想去購買同一品牌的商品, 這樣商品之間能夠互相搭配起來。如果把這樣的行為轉換成代碼的話, 幫助創建此類產品組的工具就是抽象工廠,便於產品之間能夠相互匹配。

案例中 Shoe、Shirt 是兩個抽象產品,具體產品NikeShirt、NikeShoe、AdidasShirt、AdidasShoe 是對兩個抽象產品的具體分類的實現。ISportsFactory\IAbstractFactory 是抽象工廠介面,裡面包含所有產品創建的抽象方法。客戶端用的都是抽象產品介面,沒有出現任何一個具體產品了字樣,達到瞭解耦的目的!

最大的好處是易於交換產品系列,只需要修改具體的工廠實例!

阿迪達斯工廠正在生產襯衫
阿迪達斯工廠正在生產襯衫
阿迪達斯工廠正在生產鞋子
耐克工廠正在生產襯衫
耐克工廠正在生產襯衫
耐克工廠正在生產鞋子
襯衫 品牌 阿迪達斯 大小 14	
襯衫 品牌 阿迪達斯 大小 14
襯衫 品牌 耐克 大小 12
襯衫 品牌 耐克 大小 12
鞋子 品牌 阿迪達斯 大小 10
鞋子 品牌 耐克 大小 12

程式代碼、抽象工廠模式 Golang

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
}

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
}

adidas.go 阿迪達斯工廠

package main

type Adidas struct {
}

type AdidasShirt struct {
	Shirt
}

type AdidasShoe struct {
	Shoe
}

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 NikeShirt struct {
	Shirt
}

type NikeShoe struct {
	Shoe
}

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,
		},
	}
}

iSportsFactory.go 抽象工廠介面、簡單工廠

package main

import "fmt"

// 這個是抽象工廠介面
type ISportsFactory interface {
	makeShoe() IShoe
	makeShirt() IShirt
}

// 這裡是簡單工廠,根據brand生成特定工廠實例
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")
}

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()
}

Console 輸出

Logo: nike
Size: 14
Logo: nike
Size: 14
Logo: adidas
Size: 14
Logo: adidas
Size: 14

程式代碼、抽象工廠模式 C#

Shirt.cs 產品類

namespace 抽象工廠;

public abstract class Shirt
{
    public string Logo { get; private set; }
    public int Size { get; private set; }

    protected Shirt(string logo, int size)
    {
        Logo = logo;
        Size = size;
    }
}

public class AdidasShirt : Shirt
{
    public AdidasShirt(int size) : base("阿迪達斯", size)
    {
    }
}

public class NikeShirt : Shirt
{
    public NikeShirt(int size) : base("耐克", size)
    {
    }
}

Shoe.cs 產品類

namespace 抽象工廠;

public abstract class Shoe
{
    public string Logo { get; private set; }
    public int Size { get; private set; }

    protected Shoe(string logo, int size)
    {
        Logo = logo;
        Size = size;
    }
}

public class AdidasShoe : Shoe
{
    public AdidasShoe(int size) : base("阿迪達斯", size)
    {
    }
}

public class NikeShoe : Shoe
{
    public NikeShoe(int size) : base("耐克", size)
    {
    }
}

AbstractFactory.cs 抽象工廠類

namespace 抽象工廠;

public interface IAbstractFactory
{
    Shoe createShoe(int size);
    Shirt createShirt(int size);
}

public class AdidasFactory : IAbstractFactory
{
    public Shoe createShoe(int size)
    {
        Console.WriteLine("阿迪達斯工廠正在生產鞋子");
        return new AdidasShoe(size);
    }

    public Shirt createShirt(int size)
    {
        Console.WriteLine("阿迪達斯工廠正在生產襯衫");
        return new AdidasShirt(size);
    }
}
public class NikeFactory : IAbstractFactory
{
    public Shoe createShoe(int size)
    {
        Console.WriteLine("耐克工廠正在生產鞋子");
        return new NikeShoe(size);
    }

    public Shirt createShirt(int size)
    {
        Console.WriteLine("耐克工廠正在生產襯衫");
        return new NikeShirt(size);
    }
}

// 一個類用於存放工廠單例對象,使用餓漢式單例類
public sealed class AbstractFactorySingleton
{
    public static readonly IAbstractFactory Adidas = new AdidasFactory();
    public static readonly IAbstractFactory Nike = new NikeFactory();
    
    // 常規寫法應該提供一個全局訪問點,這裡為了方便省略
}

Program.cs

// See https://aka.ms/new-console-template for more information

using 抽象工廠;

// 產品的庫存
List<Shoe> shoes = new();
List<Shirt> shirts = new();

IAbstractFactory factory = AbstractFactorySingleton.Adidas;
Shirt s1 = factory.createShirt(14);
Shirt s2 = factory.createShirt(14);
shirts.Add(s1);
shirts.Add(s2);
Shoe s3 = factory.createShoe(10);
shoes.Add(s3);

factory = AbstractFactorySingleton.Nike;
s1 = factory.createShirt(12);
s2 = factory.createShirt(12);
shirts.Add(s1);
shirts.Add(s2);
s3 = factory.createShoe(12);
shoes.Add(s3);

foreach (var shirt in shirts)
{
    Console.WriteLine($"襯衫 品牌 {shirt.Logo} 大小 {shirt.Size}");
}

foreach (var shoe in shoes)
{
    Console.WriteLine($"鞋子 品牌 {shoe.Logo} 大小 {shoe.Size}");
}

Console 輸出

阿迪達斯工廠正在生產襯衫
阿迪達斯工廠正在生產襯衫
阿迪達斯工廠正在生產鞋子
耐克工廠正在生產襯衫
耐克工廠正在生產襯衫
耐克工廠正在生產鞋子
襯衫 品牌 阿迪達斯 大小 14
襯衫 品牌 阿迪達斯 大小 14
襯衫 品牌 耐克 大小 12
襯衫 品牌 耐克 大小 12
鞋子 品牌 阿迪達斯 大小 10
鞋子 品牌 耐克 大小 12

思考總結

註:把不同的產品變體、產品族理解為不同的產品系列,如現代風格傢具類、北約風格傢具類。

什麼是抽象工廠模式

image-20220916160544800

抽象工廠模式:創建型設計模式。提供一個創建一系列相關或相互依賴對象的介面,而無需指定它們具體的類。

何時使用:系統的產品有多於一個的產品族,而系統只消費其中某一族的產品。

如何解決:

  • 為系列中的每件產品明確聲明介面,確保所有產品變體都繼承這些介面。
  • 我們需要聲明抽象工廠——包含系列中所有產品構造方法的介面。
  • 這些方法必須返回抽象產品類型。每個工廠類都只能返回特定類別的產品。

關鍵代碼:在一個工廠里聚合多個同類產品。

應用場景:

  • 代碼需要與多個不同系列的相關產品交互,但是由於無法提前獲取相關信息,或出於對未來擴展性的考慮。
  • 如果你有一個基於一組抽象方法的類,且其主要功能因此變 得不明確,那麼在這種情況下可以考慮使用抽象工廠模式。
  • 設計良好的程式中,每個類僅負責一件事。如果一個類與多種類型產品交互,就可以考慮將工廠方法抽取到獨立的工廠類或具備完整功能的抽象工廠類

實現步驟:

  1. 以不同的產品類型與產品變體為維度繪製矩陣。
  2. 為所有產品聲明抽象產品介面。然後讓所有具體產品類實現這些介面。
  3. 聲明抽象工廠介面,並且在介面中為所有抽象產品提供一組構建方法。
  4. 每種產品變體實現一個具體工廠類
  5. 在應用程式中開發初始化代碼。該代碼根據應用程式配置或當前環境,對特定具體工廠類進行初始化。然後將該工廠對 象傳遞給所有需要創建產品的類。
  6. 找出代碼中所有對產品構造函數的直接調用,將其替換為對工廠對象中相應構建方法的調用。

優點:

  • 當一個產品族中的多個對象被設計成一起工作時,它能保證客戶端始終只使用同一個產品族中的對象。
  • 確保同一工廠生成的產品相互匹配
  • 避免客戶端和具體產品代碼的耦合
  • 單一職責原則。你可以將產品生成代碼抽取到同一位置,使得代碼易於維護。
  • 開閉原則。嚮應用程式中引入新產品變體時,你無需修改客戶端代碼。

缺點:

  • 產品族擴展非常困難,要增加一個系列的某一產品,既要在介面裡加代碼,又要在具體類裡面加代碼。

使用場景:

  • QQ 換皮膚,一整套一起換。
  • 生成不同操作系統的程式。
  • 跨平臺UI元素

註意事項:

  • 產品系列難擴展,具體產品易擴展。
  • 一般情況下,應用程式會在初始化階段創建具體工廠對象。而在此之前,應用程式必鬚根據配置文件或環境設定選擇工廠類別。

與其他模式的關係:

  • 在許多設計工作的初期都會使用工廠方法(較為簡單,而且可以更方便地通過子類進行定製),隨後演化為使用抽象工 廠、原型或生成器(更靈活但更加複雜)。
  • 生成器(建造者模式)重點關註如何分步生成複雜對象。抽象工廠專門用於生產一系列相關對象。抽象工廠會馬上返回產品,生成器則允許你在獲取產品前執行一些額外構造步驟。
  • 抽象工廠模式通常基於一組工廠方法,但你也可以使用原型模式來生成這些類的方法。
  • 當只需對客戶端代碼隱藏子系統創建對象的方式時,你可以 使用抽象工廠來代替外觀。
  • 你可以將抽象工廠和橋接搭配使用。如果由橋接定義的抽象只能與特定實現合作,這一模式搭配就非常有用。在這種情 況下,抽象工廠可以對這些關係進行封裝,並且對客戶端代 碼隱藏其複雜性。
  • 抽象工廠、生成器和原型都可以用單例來實現。

餓漢式單例模式

在c#的例子中使用了餓漢式單例模式來生成各個工廠實例,這是C#與公告語言運行庫提供的一種靜態初始化方法,這種方法不需要開發人員顯式地編寫線程安全代碼,即可解決多線程環境下它是不安全的問題。這種靜態初始化的方式是在自己被載入時就將自己實例化,所以被稱為懶漢式單例類。

更多參考:我的設計模式之旅、02 單例模式 - 小能日記 - 博客園

參考資料

  • 《Go語言核心編程》李文塔
  • 《Go語言高級編程》柴樹彬、曹春輝
  • 《大話設計模式》程傑
  • 《深入設計模式》亞歷山大·什韋茨
  • 菜鳥教程

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • vue3中,新增了 defineComponent ,它並沒有實現任何的邏輯,只是把接收的 Object 直接返回,它的存在是完全讓傳入的整個對象獲得對應的類型,它的存在就是完全為了服務 TypeScript 而存在的。 我都知道普通的組件就是一個普通的對象,既然是一個普通的對象,那自然就不會獲得自 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 一、前言 入職的第一個需求是跟著一位前端大佬一起完成的一個活動項目。 由於是一起開發,當然不會放過閱讀大佬的代碼的機會。 因為我的頁面中需要使用到倒計時功能,發現大佬的已經寫了個現成的倒計時組件,於是直接就拿過來用了。 傳個參數就實現了功 ...
  • 本人的工作項目中,需求是: 點擊“列印”按鈕,打開pdf預覽彈出框,彈出框有:頭部選擇列印模板、列印方式、印表機,都是下拉選擇框;中部是pdf預覽塊;底部是確定列印。 準備工作: 預覽pdf,後端介面返回了pdf預覽地址,可線上直接打開。vue-pdf插件可以滿足需求。 選擇方式如果選擇本地列印,下 ...
  • 1.如果只比較兩個值的話 效果是這種的 // 這是<template>的 <el-row> <el-col :span="12"> <el-form-item label="計劃評審日期(起)" prop="planPsDateStart"> <el-date-picker v-model="vm. ...
  • 每日3題 1 以下代碼執行後,控制臺中的輸出內容為? // 以下代碼執行後,瀏覽器的控制臺中輸出的內容是什麼 var arr = [0, 1, 2]; arr[10] = 10; var newArr = arr.filter((x) => x undefined); console.log(new ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 uniapp上如何實現安卓app微信登錄功能?下麵本篇文章給大家分享一下uniapp上實現安卓app微信登錄的許可權申請、開發的具體操作流程,希望對大家有所幫助! 微信開放平臺提供了微信的一些開放介面,比如微信登錄、分享支付等,為其他各平臺 ...
  • 我的前端之旅。本節整合了"A Complete Guide to Flexbox"最新版本,介紹了flexbox的所有屬性,外帶幾個實用的例子。 ...
  • 如果你覺得 UITableViewDelegate 和 UITableViewDataSource 這兩個協議中有大量方法每次都是複製粘貼,實現起來大同小異;如果你覺得發起網路請求並解析數據需要一大段代碼,加上刷新和載入後簡直複雜度爆表,如果你想知道為什麼下麵的代碼可以滿足上述所有要求: 解耦後的V ...
一周排行
    -Advertisement-
    Play Games
  • 1、預覽地址:http://139.155.137.144:9012 2、qq群:801913255 一、前言 隨著網路的發展,企業對於信息系統數據的保密工作愈發重視,不同身份、角色對於數據的訪問許可權都應該大相徑庭。 列如 1、不同登錄人員對一個數據列表的可見度是不一樣的,如數據列、數據行、數據按鈕 ...
  • 前言 上一篇文章寫瞭如何使用RabbitMQ做個簡單的發送郵件項目,然後評論也是比較多,也是準備去學習一下如何確保RabbitMQ的消息可靠性,但是由於時間原因,先來說說設計模式中的簡單工廠模式吧! 在瞭解簡單工廠模式之前,我們要知道C#是一款面向對象的高級程式語言。它有3大特性,封裝、繼承、多態。 ...
  • Nodify學習 一:介紹與使用 - 可樂_加冰 - 博客園 (cnblogs.com) Nodify學習 二:添加節點 - 可樂_加冰 - 博客園 (cnblogs.com) 介紹 Nodify是一個WPF基於節點的編輯器控制項,其中包含一系列節點、連接和連接器組件,旨在簡化構建基於節點的工具的過程 ...
  • 創建一個webapi項目做測試使用。 創建新控制器,搭建一個基礎框架,包括獲取當天日期、wiki的請求地址等 創建一個Http請求幫助類以及方法,用於獲取指定URL的信息 使用http請求訪問指定url,先運行一下,看看返回的內容。內容如圖右邊所示,實際上是一個Json數據。我們主要解析 大事記 部 ...
  • 最近在不少自媒體上看到有關.NET與C#的資訊與評價,感覺大家對.NET與C#還是不太瞭解,尤其是對2016年6月發佈的跨平臺.NET Core 1.0,更是知之甚少。在考慮一番之後,還是決定寫點東西總結一下,也回顧一下.NET的發展歷史。 首先,你沒看錯,.NET是跨平臺的,可以在Windows、 ...
  • Nodify學習 一:介紹與使用 - 可樂_加冰 - 博客園 (cnblogs.com) Nodify學習 二:添加節點 - 可樂_加冰 - 博客園 (cnblogs.com) 添加節點(nodes) 通過上一篇我們已經創建好了編輯器實例現在我們為編輯器添加一個節點 添加model和viewmode ...
  • 前言 資料庫併發,數據審計和軟刪除一直是數據持久化方面的經典問題。早些時候,這些工作需要手寫複雜的SQL或者通過存儲過程和觸發器實現。手寫複雜SQL對軟體可維護性構成了相當大的挑戰,隨著SQL字數的變多,用到的嵌套和複雜語法增加,可讀性和可維護性的難度是幾何級暴漲。因此如何在實現功能的同時控制這些S ...
  • 類型檢查和轉換:當你需要檢查對象是否為特定類型,並且希望在同一時間內將其轉換為那個類型時,模式匹配提供了一種更簡潔的方式來完成這一任務,避免了使用傳統的as和is操作符後還需要進行額外的null檢查。 複雜條件邏輯:在處理複雜的條件邏輯時,特別是涉及到多個條件和類型的情況下,使用模式匹配可以使代碼更 ...
  • 在日常開發中,我們經常需要和文件打交道,特別是桌面開發,有時候就會需要載入大批量的文件,而且可能還會存在部分文件缺失的情況,那麼如何才能快速的判斷文件是否存在呢?如果處理不當的,且文件數量比較多的時候,可能會造成卡頓等情況,進而影響程式的使用體驗。今天就以一個簡單的小例子,簡述兩種不同的判斷文件是否... ...
  • 前言 資料庫併發,數據審計和軟刪除一直是數據持久化方面的經典問題。早些時候,這些工作需要手寫複雜的SQL或者通過存儲過程和觸發器實現。手寫複雜SQL對軟體可維護性構成了相當大的挑戰,隨著SQL字數的變多,用到的嵌套和複雜語法增加,可讀性和可維護性的難度是幾何級暴漲。因此如何在實現功能的同時控制這些S ...