我的设计模式之旅、10 抽象工厂

一个菜鸟的设计模式之旅,文章可能会有不对的地方,恳请大佬指出错误。

编程旅途是漫长遥远的,在不同时刻有不同的感悟,本文会一直更新下去。

程序介绍

你想要购买一组运动装备, 比如一双鞋与一件衬衫这样由两种不同产品组合而成的套装。 相信你会想去购买同一品牌的商品, 这样商品之间能够互相搭配起来。如果把这样的行为转换成代码的话, 帮助创建此类产品组的工具就是抽象工厂,便于产品之间能够相互匹配。

案例中 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语言高级编程》柴树彬、曹春辉
  • 《大话设计模式》程杰
  • 《深入设计模式》亚历山大·什韦茨
  • 菜鸟教程
posted @ 2022-09-16 16:17  小能日记  阅读(81)  评论(0编辑  收藏  举报