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

本程序实现收银员对顾客收银时可以采用不同的促销策略,支持原价,按折扣促销,满多少返利多少三种策略。使用策略模式与简单工厂模式。简单工厂使用依赖注入方法,通过配置文件 config.json 能够动态实例化对象。
PS C:\Users\小能喵喵喵\Desktop\设计模式\策略模式_简单工厂_反射> go run .
商品数量 10
单价 100
当前商品总额¥700
--------------------------------
商品数量 30
单价 50
当前商品总额¥1700
--------------------------------
商品数量 -1
顾客需要支付¥1700
package main
import (
"errors"
"reflect"
"runtime"
)
var TypeReg = make(TypeRegister)
func init() {
TypeReg.Set(Discount{})
TypeReg.Set(MoneyOff{})
TypeReg.Set(Normal{})
runtime.GC()
}
type TypeRegister map[string]reflect.Type
func (t TypeRegister) Set(i interface{}) {
t[reflect.TypeOf(i).Name()] = reflect.TypeOf(i)
}
func (t TypeRegister) Get(name string) (interface{}, error) {
if typ, ok := t[name]; ok {
return reflect.New(typ).Interface(), nil // ^ 新建对象获取指针并以空接口类型返回
}
return nil, errors.New("no one")
}
TypeRegister字典结构是为了实现依赖注入,什么是依赖注入?var TypeReg = make(TypeRegister)
首先介绍一种设计思想,控制反转。正常情况下,对函数或方法的调用是调用方主动的行为,调用方清楚地知道被调的函数名是什么,参数有哪些类型直接主动调用,包括对象的初始化也是显式直接初始化。控制反转就是将主动行为变为间接行为,调用方需要通过框架代码进行间接调用和初始化。
这样的好处就是能够解耦调用方和被调方,调用者的代码不用写死,可以让控制反转的框架代码读取配置,动态构建对象。依赖注入是实现控制反转的一种方法,通过注入参数或实例的方式实现控制反转。通常这两者是同一个东西。
golang没有java的class.forName动态生成类实例的方法。需要自行维护一套类型注册字典。该字典类型有添加类和生成类实例两大方法。init函数会在main函数之前运行,在函数体创建各个类型的实例来进行注册,使字典保存各个类型的类名和对应的reflect.Type结构。reflect.Type通过的New函数创建一个新的实例并返回它的指针。这样我们可以实现依赖注入,控制反转(通过外部的 config.json 配置文件,动态生成实例)
return reflect.New(typ).Interface(), nil
New出来的是reflect.Value类型,不是原有的具体类型,转换成空接口,该接口内部存放具体类型实例,可以使用接口类型查询去还原为具体类型。
package main
// 加载 config.json 文件并创建维护策略实例的上下文实例对象
// by 小能喵喵喵 2022年9月8日
import (
"encoding/json"
"io/ioutil"
"log"
"strings"
)
const (
configPath = "./config.json" // 配置文件绝对路径
)
type Config struct {
Promotion string `json:"promotion"` // 从json字符串转换成结构体
}
func loadConfig() (c Context) {
config := getConfig(configPath)
params := strings.Split(config.Promotion, " ")
c.set(params[0], params[1:]) // 动态生成结构体实例并调用实例的config函数填入参数
return
}
func getConfig(path string) Config {
f, err := ioutil.ReadFile(path)
if err != nil {
log.Fatal("Error when opening file: ", err)
}
var config Config
err = json.Unmarshal(f, &config)
if err != nil {
log.Fatal("Error during Unmarshal(): ", err)
}
return config
}
package main
import (
"math"
"strconv"
)
// ^ 策略接口定义所有支持的算法的公共接口
type IStrategy interface {
acceptCash(money float64) float64
config(args []string)
}
type Normal struct{}
type Discount struct {
Percent float64
}
type MoneyOff struct {
Threshold float64
Back float64
}
func (d Normal) acceptCash(money float64) float64 {
return money
}
func (d *Normal) config(args []string) {}
func (d Discount) acceptCash(money float64) float64 {
return money * d.Percent
}
func (d *Discount) config(args []string) {
d.Percent = GetFloat(args[0])
}
func (m MoneyOff) acceptCash(money float64) float64 {
if money >= m.Threshold {
money -= math.Floor(money/m.Threshold) * m.Back
}
return money
}
func (m *MoneyOff) config(args []string) {
m.Threshold = GetFloat(args[0])
m.Back = GetFloat(args[1])
}
// ^ 字符串转float64
func GetFloat(s string) float64 {
f, _ := strconv.ParseFloat(s, 64)
return f
}
/* -------------------------------------------------------------------------- */
// ^ 上下文对象用于生成策略实例
type Context struct {
strategy IStrategy
}
// ^ 依赖注入生成策略实例
func (c *Context) set(str string, args []string) {
var strategy IStrategy
s, err := TypeReg.Get(str)
if err != nil {
return
}
strategy = s.(IStrategy)
strategy.config(args)
c.strategy = strategy
}
// ^ 上下文执行策略
func (c *Context) cal(f float64) float64 {
if c.strategy == nil {
return f
}
return c.strategy.acceptCash(f)
}
package main
// 策略模式_简单工厂_反射
// by 小能喵喵喵 2022年9月8日
import (
"fmt"
"strings"
)
var (
cost float64
quantity int
price float64
)
func main() {
c := loadConfig()
for {
fmt.Print("商品数量 ")
fmt.Scanln(&quantity)
if quantity <= 0 {
break
}
fmt.Print("单价 ")
fmt.Scanln(&price)
// ^ 使用策略
cost += c.cal(price * float64(quantity))
fmt.Printf("当前商品总额¥%v\n", cost)
fmt.Println(strings.Repeat("-", 32))
}
fmt.Printf("顾客需要支付¥%v\n", cost)
}
{
"promotion": "MoneyOff 300 100"
}
可以改成 Normal,也可以改成 Discount 0.5 打五折
PS C:\Users\小能喵喵喵\Desktop\设计模式\策略模式_简单工厂_反射> go run .
商品数量 10
单价 100
当前商品总额¥700
--------------------------------
商品数量 30
单价 50
当前商品总额¥1700
--------------------------------
商品数量 -1
顾客需要支付¥1700
一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。

策略模式:定义了算法家族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化,不会影响到使用算法的客户。
可能有点抽象,晦涩难懂,用自己的话来说就是
策略模式(白话文):完成一件事有多种方法,比如刷碗可以人工刷也可以机器刷,做的都是刷碗的工作。把各个方法封装到类里面去,每个类都能完成同样的工作,我们可以抽象出行为共性,即接口,接口内有这个公共方法,各个子类实现这个接口。客户端(使用方)声明一个接口接收一个具体的子类方法实例,然后调用声明接口的公共方法(里氏替换原则)。如果未来需要添加新的方法,只需要添加子类,原来的客户端不会受到影响(开放-封闭原则)。如果需要修改原来的方法,只需要修改客户端new实例的地方(最小的改动)。
使用策略模式能够降低具体算法与使用者之间耦合程度。封装的算法完成的是同一份工作,只是实现不同。这些算法随时都可能相互替换的,策略模式封装了变化点。虽然严格定义上策略模式是用来封装算法的,但实践中可以用来封装任何类型的规则(需要在不同时间应用不同的业务规划)。
完成一个工作有多个方法,如果不用策略模式,而是直接在单个类中使用方法,如果每个方法的执行有一定的条件要求,那么肯定会导致方法在这个类的堆积(大量的switch,if判断),这既不灵活,也不好维护。如果有了新的方法,拓展了子类,却还要修改客户端的判断,这显然违背了开放-封闭原则。
通过里氏代换原则,子类必须能够替换父类而不影响代码的正常运行;迪米特法则,如果两个类不直接通信,尽量让两个类之间保持松耦合。策略模式的设计,客户端使用context对象,该对象维护了一个策略实例,实际上变量声明的是抽象父类或抽象接口(里氏代换原则),用户通过context对象调用具体策略的方法,而不再通过各个分支判断new出具体策略实例调用方法。
基本策略模式优点
基本策略模式缺点
开放-封闭原则。可以用反射解决。有人说为啥要 context ,干脆在客户端声明接口然后new具体策略不就行了?既然要context肯定有它设计的原因。我认为主要有两点
简单工厂模式属于创建型模式的一种。创建型模式隐藏了这些类的实例是如何被创建和放在一起,整个系统关于这些对象所知道的是由抽象类所定义的接口。
案例程序中Context使用了改进后的简单工厂,客户端调用set函数,使用了反射技术和依赖注入,Context可以动态生成实例对象。
简单工厂模式优点
简单工厂模式缺点
开放-封闭原则,每一次更改都要更改工厂类。
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
在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
我主要使用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
鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende
我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数
我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R
有没有办法在这个简单的get方法中添加超时选项?我正在使用法拉第3.3。Faraday.get(url)四处寻找,我只能先发起连接后应用超时选项,然后应用超时选项。或者有什么简单的方法?这就是我现在正在做的:conn=Faraday.newresponse=conn.getdo|req|req.urlurlreq.options.timeout=2#2secondsend 最佳答案 试试这个:conn=Faraday.newdo|conn|conn.options.timeout=20endresponse=conn.get(url
我喜欢使用Textile或Markdown为我的项目编写自述文件,但是当我生成RDoc时,自述文件被解释为RDoc并且看起来非常糟糕。有没有办法让RDoc通过RedCloth或BlueCloth而不是它自己的格式化程序运行文件?它可以配置为自动检测文件后缀的格式吗?(例如README.textile通过RedCloth运行,但README.mdown通过BlueCloth运行) 最佳答案 使用YARD直接代替RDoc将允许您包含Textile或Markdown文件,只要它们的文件后缀是合理的。我经常使用类似于以下Rake任务的东西:
我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b
rails中是否有任何规定允许站点的所有AJAXPOST请求在没有authenticity_token的情况下通过?我有一个调用Controller方法的JqueryPOSTajax调用,但我没有在其中放置任何真实性代码,但调用成功。我的ApplicationController确实有'request_forgery_protection'并且我已经改变了config.action_controller.consider_all_requests_local在我的environments/development.rb中为false我还搜索了我的代码以确保我没有重载ajaxSend来发送