草庐IT

我的设计模式之旅、09 工厂方法、简单工厂

小能的博客 CanAngle's Blog 2023-03-28 原文

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

程序介绍

由于 Go 中缺少类和继承等 OOP 特性, 所以无法使用 Go 来实现经典的工厂方法模式。 不过, 我们仍然能实现模式的基础版本, 即简单工厂。案例中使用工厂结构体来构建多种类型的武器。因此工厂方法模式代码使用 C# 表示。

Gun: AK47 gun
Power: 4
Gun: Musket gun
Power: 1

程序代码、简单工厂模式 Golang

首先, 创建一个名为 i­Gun的接口, 其中将定义一支枪所需具备的所有方法。 然后是实现了 iGun 接口的 gun枪支结构体类型。 两种具体的枪支——ak47mus­ket火枪 ——两者都嵌入了枪支结构体, 且间接实现了所有的 i­Gun方法。

gun­Fac­to­ry枪支工厂结构体将发挥工厂的作用, 即通过传入参数构建所需类型的枪支。 main.go 则扮演着客户端的角色。 其不会直接与 ak47mus­ket进行互动, 而是依靠 gun­Fac­to­ry来创建多种枪支的实例, 仅使用字符参数来控制生产。

iGun.go: 产品接口

package main

type IGun interface {
    setName(name string)
    setPower(power int)
    getName() string
    getPower() int
}

gun.go: 具体产品

package main

type Gun struct {
    name  string
    power int
}

func (g *Gun) setName(name string) {
    g.name = name
}

func (g *Gun) getName() string {
    return g.name
}

func (g *Gun) setPower(power int) {
    g.power = power
}

func (g *Gun) getPower() int {
    return g.power
}

ak47.go: 具体产品

package main

type Ak47 struct {
    Gun
}

func newAk47() IGun {
    return &Ak47{
        Gun: Gun{
            name:  "AK47 gun",
            power: 4,
        },
    }
}

musket.go: 具体产品

package main

type musket struct {
    Gun
}

func newMusket() IGun {
    return &musket{
        Gun: Gun{
            name:  "Musket gun",
            power: 1,
        },
    }
}

gunFactory.go: 工厂

package main

import "fmt"

func getGun(gunType string) (IGun, error) {
    if gunType == "ak47" {
        return newAk47(), nil
    }
    if gunType == "musket" {
        return newMusket(), nil
    }
    return nil, fmt.Errorf("Wrong gun type passed")
}

main.go: 客户端代码

package main

import "fmt"

func main() {
    ak47, _ := getGun("ak47")
    musket, _ := getGun("musket")

    printDetails(ak47)
    printDetails(musket)
}

func printDetails(g IGun) {
    fmt.Printf("Gun: %s", g.getName())
    fmt.Println()
    fmt.Printf("Power: %d", g.getPower())
    fmt.Println()
}

Console: 输出

Gun: AK47 gun
Power: 4
Gun: Musket gun
Power: 1

程序代码、工厂方法模式 C#

程序功能和简单工厂相同,用工厂方法模式实现。

Gun.cs: 武器类

namespace 工厂方法;

public abstract class Gun
{
    protected Gun(string name, int power)
    {
        Name = name;
        Power = power;
    }

    public string Name { get; private set; }

    public int Power { get; private set; }

    public void setName(string name)
    {
        Name = name;
    }

    public void setPower(int power)
    {
        Power = power;
    }
}

public class AK47 : Gun
{
    public AK47() : base("ak47", 10)
    {
    }
}

public class Musket : Gun
{
    public Musket() : base("musket", 5)
    {
    }
}

Factory.cs: 工厂类

namespace 工厂方法;

public interface GunFactory
{
    Gun createGun();
}

public class AK47Factory : GunFactory
{
    public Gun createGun()
    {
        Console.WriteLine("正在生产AK47");
        return new AK47();
    }
}

public class MusketFactory : GunFactory
{
    public Gun createGun()
    {
        Console.WriteLine("正在生产Musket");
        return new Musket();
    }
}

Program.cs: 客户端代码

using 工厂方法;

GunFactory gunFactory = new AK47Factory();

Gun ak47_1 = gunFactory.createGun();
Gun ak47_2 = gunFactory.createGun();

gunFactory = new MusketFactory();

Gun Musket_1 = gunFactory.createGun();

List<Gun> guns = new List<Gun>() { ak47_1, ak47_2, Musket_1 };

foreach (Gun gun in guns)
{
    Console.WriteLine($"武器名字:{gun.Name}");
    Console.WriteLine($"武器伤害:{gun.Power}");
}

Console: 输出

正在生产AK47
正在生产AK47
正在生产Musket
武器名字:ak47
武器伤害:10
武器名字:ak47
武器伤害:10
武器名字:musket
武器伤害:5

思考总结

什么是简单工厂模式

简单工厂模式也是工厂模式的一种,但不属于23种设计模式。

目的使客户端与产品解耦。将产品创建实例过程从客户端代码中独立出去。生成具体对象的逻辑判断也从客户端分离至简单工厂类中。

简单工厂模式的最大优点在于工厂类中包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关的类,对于客户端来说,去除了与具体产品的依赖。

但简单工厂违背了开放-封闭原则,导致每次添加新类别的时候,都需要去修改工厂类的分支case判断,可以用反射动态生成实例解决。

简单工厂反射案例:我的设计模式之旅、01 策略模式、简单工厂、反射 - 小能日记

什么是工厂方法模式

工厂方法是一种创建型设计模式, 其在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。

工厂方法模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到子类。

工厂方法类 (创建者类):主要职责并非是创建产品。其中通常会包含一些核心业务逻辑,这些逻辑依赖于由工厂方法返回的产品对象。子类可通过重写工厂方法并使其返回不同类型的产品来间接修改业务逻辑。

主要解决:主要解决接口选择的问题。客户端将所有产品视为抽象的接口,客户端知道所有产品都提供该接口要求的方法,并不关心其具体实现方式。

何时使用:

  • 我们明确地计划不同条件创建不同实例时。
  • 当你在编写代码的过程中,如果无法预知对象确切类别及其依赖关系时,可使用工厂方法。
  • 希望用户能扩展你软件库或框架的内部组件,可使用工厂方法。
  • 希望复用现有对象来节省系统资源,而不是每次都重新创建对象,可使用工厂方法。在处理大型资源密集型对象(数据库连接、文件系统和网络资源)时,你会经常碰到这种资源需求。
  • 在许多设计工作的初期都会使用工厂方法(较为简单,而且可以更方便地通过子类进行定制),随后演化为使用抽象工 厂、原型或生成器(更灵活但更加复杂)。

如何解决:让其子类实现工厂接口,返回的也是一个抽象的产品。使用特殊的工厂方法代替对于对象构造函数new的直接调用。

实现步骤:

  1. 让所有产品遵循同一接口。改接口必须声明对所有产品都有意义的方法。
  2. 在工厂方法类(创建类中)添加一个空的工厂方法。该方法的返回类型必须遵循通用的产品接口。
  3. 找到对于产品构造函数的所有引用。依次替换为对工厂方法的调用,并将创建产品的代码移入工厂方法。(类似简单工厂,此时switch分支是庞大的)
  4. 为每种产品编写创建者子类,在子类中重写工厂方法,将基本方法的相关创建代码移动到工厂方法中。子类过多时,可以给子类创建者传入参数,判断生成。
  5. 如果代码经过上述移动后,基础工厂方法中已经没有任何代码,你可以将其转变为抽象类。如果基础工厂方法中还有其他语句,你可以将其设置为该方法的默认行为。一般总是把创建者基类作为抽象类。

关键代码:创建过程在其子类执行。

应用实例:

  • 您需要一辆汽车,可以直接从工厂里面提货,而不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现。
  • Hibernate 换数据库只需换方言和驱动就可以。

优点:

  • 一个调用者想创建一个对象,只要知道其名称就可以了。
  • 开闭原则。扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
  • 屏蔽产品的具体实现,调用者只关心产品的接口。避免创建者和具体产品之间的紧密耦合。
  • 单一职责原则。你可以将产品创建代码放在程序的单一位置, 从而使得代码更容易维护。

缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。

使用场景:

  • 日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。
  • 数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。
  • 设计一个连接服务器的框架,需要三个协议,"POP3"、"IMAP"、"HTTP",可以把这三个作为产品类,共同实现一个接口。

注意事项:

  • 作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。

  • 创建对象的逻辑判断依旧在客户端中实现。

与其他模式的关系:

  • 抽象工厂模式通常基于一组工厂方法,但你也可以使用原型模式来生成这些类的方法。
  • 你可以同时使用工厂方法和迭代器来让子类集合返回不同类型的迭代器,并使得迭代器与集合相匹配。
  • 工厂方法是模板方法的一种特殊形式。同时,工厂方法可以作为一个大型模板方法中的一个步骤。

思考对象复用的方法

  1. 首先,你需要创建存储空间来存放所有已经创建的对象。

  2. 当他人请求一个对象时,程序将在对象池中搜索可用对象。

  3. 然后将其返回给客户端代码。

  4. 如果没有可用对象,程序则创建一个新对象(并将其添加到对象池中)。

四个步骤的代码必须位于同一处,确保重复代码不会污染程序!

参考资料

有关我的设计模式之旅、09 工厂方法、简单工厂的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby - Facter::Util::Uptime:Module 的未定义方法 get_uptime (NoMethodError) - 2

    我正在尝试设置一个puppet节点,但ruby​​gems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由ruby​​gems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby

  5. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  6. ruby-on-rails - Railstutorial : db:populate vs. 工厂女孩 - 2

    在railstutorial中,作者为什么选择使用这个(代码list10.25):http://ruby.railstutorial.org/chapters/updating-showing-and-deleting-usersnamespace:dbdodesc"Filldatabasewithsampledata"task:populate=>:environmentdoRake::Task['db:reset'].invokeUser.create!(:name=>"ExampleUser",:email=>"example@railstutorial.org",:passwo

  7. Ruby 方法() 方法 - 2

    我想了解Ruby方法methods()是如何工作的。我尝试使用“ruby方法”在Google上搜索,但这不是我需要的。我也看过ruby​​-doc.org,但我没有找到这种方法。你能详细解释一下它是如何工作的或者给我一个链接吗?更新我用methods()方法做了实验,得到了这样的结果:'labrat'代码classFirstdeffirst_instance_mymethodenddefself.first_class_mymethodendendclassSecond使用类#returnsavailablemethodslistforclassandancestorsputsSeco

  8. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  9. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  10. ruby - Highline 询问方法不会使用同一行 - 2

    设置:狂欢ruby1.9.2高线(1.6.13)描述:我已经相当习惯在其他一些项目中使用highline,但已经有几个月没有使用它了。现在,在Ruby1.9.2上全新安装时,它似乎不允许在同一行回答提示。所以以前我会看到类似的东西:require"highline/import"ask"Whatisyourfavoritecolor?"并得到:Whatisyourfavoritecolor?|现在我看到类似的东西:Whatisyourfavoritecolor?|竖线(|)符号是我的终端光标。知道为什么会发生这种变化吗? 最佳答案

随机推荐