Go的设计模式之工厂模式

本文详细介绍了面向对象设计模式中的简单工厂模式和工厂方法模式,包括它们的概念、优缺点和应用场景。简单工厂模式适用于创建逻辑相对简单的情况,而工厂方法模式则将创建逻辑交给子类,提高了代码的灵活性。通过具体的游戏世界示例,展示了抽象工厂模式如何用于创建相关对象的集合。此外,还提供了测试用例来验证各种模式的实现。
摘要由CSDN通过智能技术生成

简介

设计模式是面向对象软件的设计经验,是解决特定问题的一系列套路。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。每一种设计模式系统的命名、解释和评价了面向对象中一个重要的和重复出现的设计。

创建模式是用来帮助我们创建对象的,具体有如下几种:

工厂模式 (Factory Pattern)
抽象工厂模式 (Abstract Factory Pattern)
单例模式 (Singleton Pattern)
建造者模式 (Builder Pattern)
原型模式 (Prototype Pattern)

工厂模式

通俗解释
追 MM 少不了请吃饭了,麦当劳的鸡翅和肯德基的鸡翅都是 MM 爱吃的东西,虽然口味有所不同,但不管你带 MM 去麦当劳或肯德基,只管向服务员说「来四个鸡翅」就行了。麦当劳和肯德基就是生产鸡翅的 Factory 工厂模式:客户类和工厂类分开。

消费者任何时候需要某种产品,只需向工厂请求即可。消费者无须修改就可以接纳新产品。缺点是当产品修改时,工厂类也要做相应的修改。如:如何创建及如何向客户端提供。

简单工厂模式

简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。

优点

工厂类含有必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的责任,而仅仅“消费”产品;简单工厂模式通过这种做法实现了对责任的分割,它提供了专门的工厂类用于创建对象。 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,对于一些复杂的类名,通过简单工厂模式可以减少使用者的记忆量。 通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。

缺点

由于工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响。 使用简单工厂模式将会增加系统中类的个数,在一定程序上增加了系统的复杂度和理解难度。 系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护。 简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构。

代码编写

由于 Go 本身是没有构造函数的,一般而言我们采用 NewName 的方式创建对象/接口,当它返回的是接口的时候,其实就是简单工厂模式

package factory

// IRuleConfigParser IRuleConfigParser

type IRuleConfigParser interface {
	Parse(data []byte)
}

// jsonRuleConfigParser jsonRuleConfigParser
type jsonRuleConfigParser struct {
}

// Parse Parse
func (J jsonRuleConfigParser) Parse(data []byte) {
	panic("implement me")
}

// yamlRuleConfigParser yamlRuleConfigParser
type yamlRuleConfigParser struct {
}

// Parse Parse
func (Y yamlRuleConfigParser) Parse(data []byte) {
	panic("implement me")
}

// NewIRuleConfigParser NewIRuleConfigParser

func NewIRuleConfigParser(t string) IRuleConfigParser {
	switch t {
	case "json":
		return jsonRuleConfigParser{}
	case "yaml":
		return yamlRuleConfigParser{}
	}
	return nil
}

测试

package factory

import (
	"reflect"
	"testing"
)

func TestNewIRuleConfigParser(t *testing.T) {
	type args struct {
		t string
	}
	tests := []struct {
		name string
		args args
		want IRuleConfigParser
	}{
		{
			name: "json",
			args: args{t: "json"},
			want: jsonRuleConfigParser{},
		},
		{
			name: "yaml",
			args: args{t: "yaml"},
			want: yamlRuleConfigParser{},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := NewIRuleConfigParser(tt.args.t); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("NewIRuleConfigParser() = %v, want %v", got, tt.want)
			}
		})
	}
}

工厂模式

在工厂方法模式中,我们执行单个函数,传入一个参数(提供信息表明我们想要什么),但 并不要求知道任何关于对象如何实现以及对象来自哪里的细节

适用场景

创建对象需要大量重复的代码 客户端(应用层)不依赖于产品实例,如何被创建、实现等细节 一个类通过其子类来指定创建哪个对象

优点

用户只需要关心所需产品对应的工厂,无须关心创建细节 加入新产品符合开闭原则,提高可扩展性

缺点

在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。

代码编写

当对象的创建逻辑比较复杂,不只是简单的 new 一下就可以,而是要组合其他类对象,做各种初始化操作的时候,推荐使用工厂方法模式,将复杂的创建逻辑拆分到多个工厂类中,让每个工厂类都不至于过于复杂

// IRuleConfigParserFactory 工厂方法接口
type IRuleConfigParserFactory interface {
	CreateParser() IRuleConfigParser
}

// yamlRuleConfigParserFactory yamlRuleConfigParser 的工厂类
type yamlRuleConfigParserFactory struct {
}

// CreateParser CreateParser
func (y yamlRuleConfigParserFactory) CreateParser() IRuleConfigParser {
	return yamlRuleConfigParser{}
}

// jsonRuleConfigParserFactory jsonRuleConfigParser 的工厂类
type jsonRuleConfigParserFactory struct {
}

// CreateParser CreateParser
func (j jsonRuleConfigParserFactory) CreateParser() IRuleConfigParser {
	return jsonRuleConfigParser{}
}

// NewIRuleConfigParserFactory 用一个简单工厂封装工厂方法
func NewIRuleConfigParserFactory(t string) IRuleConfigParserFactory {
	switch t {
	case "json":
		return jsonRuleConfigParserFactory{}
	case "yaml":
		return yamlRuleConfigParserFactory{}
	}
	return nil
}

测试

package factory

import (
	"reflect"
	"testing"
)

func TestNewIRuleConfigParserFactory(t *testing.T) {
	type args struct {
		t string
	}
	tests := []struct {
		name string
		args args
		want IRuleConfigParserFactory
	}{
		{
			name: "json",
			args: args{t: "json"},
			want: jsonRuleConfigParserFactory{},
		},
		{
			name: "yaml",
			args: args{t: "yaml"},
			want: yamlRuleConfigParserFactory{},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := NewIRuleConfigParserFactory(tt.args.t); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("NewIRuleConfigParserFactory() = %v, want %v", got, tt.want)
			}
		})
	}
}

抽象工厂模式

抽象工厂模式(Abstract Factory Pattern):提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,属于对象创建型模式。一个抽象工厂是一些工厂方法的(逻辑)集合,其中每一个工厂方法负责生成不同的对象。

通俗解释

请 MM 去麦当劳吃汉堡,不同的 MM 有不同的口味,要每个都记住是一件烦人的事情,我一般采用 Factory Method 模式,带着 MM 到服务员那儿,说「要一个汉堡」,具体要什么样的汉堡呢,让 MM 直接跟服务员说就行了。

工厂方法模式:核心工厂类不再负责所有产品的创建,而是将具体创建的工作交给子类去做,成为一个抽象工厂角色,仅负责给出具体工厂类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节。

应用环境

使用工厂模式还是抽象工厂模式?

通常先从简单的工厂模式开始,后面发现应用程序需要许多的工厂方法,且将这些工厂方法组合起来创建一系列的对象是有意义的,这样的话就应该使用抽象工厂。

优点

抽象工厂模式隔离了具体类的生成,使得客户并不需要知道什么被创建。由于这种隔离,更换一个具体工厂就变得相对容易。所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只需改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。另外,应用抽象工厂模式可以实现高内聚低耦合的设计目的,因此抽象工厂模式得到了广泛的应用。 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。这对一些需要根据当前环境来决定其行为的软件系统来说,是一种非常实用的设计模式。 增加新的具体工厂和产品族很方便,无须修改已有系统,符合“开闭原则”。

缺点

在添加新的产品对象时,难以扩展抽象工厂来生产新种类的产品,这是因为在抽象工厂角色中规定了所有可能被创建的产品集合,要支持新种类的产品就意味着要对该接口进行扩展,而这将涉及到对抽象工厂角色及其所有子类的修改,显然会带来较大的不便。 开闭原则的倾斜性(增加新的工厂和产品族容易,增加新的产品等级结构麻烦)。

代码实现

该示例来自《Mastering Python Design Patterns,Second Edition》,模拟一个游戏。

游戏的实现需要三个接口:Hero、Obstacle、World

Hero 的实现:青蛙、巫师
Obstacle 的实现:虫子、邪恶兽人
World 的实现:青蛙世界、巫师世界

package abstractfactory

import (
	"fmt"
)

// Hero 英雄
type Hero interface {
	// InteractWith 和 xx 交互
	InteractWith(Obstacle)
	String() string
}

var _ Hero = (*Frog)(nil)

// Frog 青蛙🐸
type Frog struct {
	Name string
}

func (f *Frog) InteractWith(o Obstacle) {
	act := o.Action()

	fmt.Printf("%s the Frog encounters %s and %s!\n", f, o, act)
}

func (f *Frog) String() string {
	return f.Name
}

var _ Hero = (*Wizard)(nil)

// Wizard 巫师💂
type Wizard struct {
	Name string
}

func (w *Wizard) InteractWith(o Obstacle) {
	act := o.Action()

	fmt.Printf("%s the Wizard battles against %s and %s!\n", w, o, act)
}

func (w *Wizard) String() string {
	return w.Name
}

// Obstacle 障碍物
type Obstacle interface {
	// Action 动作
	Action() string
	String() string
}

var _ Obstacle = (*Bug)(nil)

// Bug 虫子🐛
type Bug struct{}

func (b *Bug) Action() string {
	return "eat it"
}

func (b *Bug) String() string {
	return "a bug"
}

var _ Obstacle = (*Ork)(nil)

// Ork 兽人👹
type Ork struct{}

func (o *Ork) Action() string {
	return "kills it"
}

func (o *Ork) String() string {
	return "an evil ork"
}

// World  世界
type World interface {
	// MakeCharacter 初始化角色
	MakeCharacter() Hero
	// MakeObstacle 初始化障碍物
	MakeObstacle() Obstacle
	String() string
}

var _ World = (*FrogWorld)(nil)

// FrogWorld 青蛙的世界
type FrogWorld struct {
	PlayerName string
}

func (fw *FrogWorld) MakeCharacter() Hero {
	return &Frog{
		Name: fw.PlayerName,
	}
}

func (fw *FrogWorld) MakeObstacle() Obstacle {
	return &Bug{}
}

func (fw *FrogWorld) String() string {
	return "\n\n\t------ Frog World ------"
}

var _ World = (*WizardWorld)(nil)

// WizardWorld 巫师世界
type WizardWorld struct {
	PlayerName string
}

func (ww *WizardWorld) MakeCharacter() Hero {
	return &Wizard{
		Name: ww.PlayerName,
	}
}

func (ww *WizardWorld) MakeObstacle() Obstacle {
	return &Ork{}
}

func (ww *WizardWorld) String() string {
	return "\n\n\t------ Wizard World ------"
}

// GameEnvironment 游戏入口 🕹
type GameEnvironment struct {
	hero     Hero
	obstacle Obstacle
}

func NewGame(world World) GameEnvironment {
	fmt.Println(world)
	return GameEnvironment{
		hero:     world.MakeCharacter(),
		obstacle: world.MakeObstacle(),
	}
}

// Play 开始游戏
func (g GameEnvironment) Play() {
	g.hero.InteractWith(g.obstacle)
}

测试

package abstractfactory

import "testing"

func TestFrogWorld(t *testing.T) {
	var world World = &FrogWorld{PlayerName: "Billy"}

	var game GameEnvironment = NewGame(world)

	game.Play()
}

func TestWizardWorld(t *testing.T) {
	var world World = &WizardWorld{PlayerName: "Charles"}

	var game GameEnvironment = NewGame(world)

	game.Play()
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值