Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from PhakornKiong/struct-type
struct pattern and example
- Loading branch information
Showing
8 changed files
with
442 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package main | ||
|
||
// shipping by freighter | ||
type airStrategy struct { | ||
distance int | ||
weight int | ||
volume int | ||
} | ||
|
||
func (s *airStrategy) CalculateCost() int { | ||
return s.distance * s.weight * s.volume * 5 | ||
} | ||
|
||
func NewAirStrategy(distance, weight, volume int) ShippingStrategy { | ||
return &airStrategy{distance, weight, volume} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package main | ||
|
||
// default strategy, just take flat rate of 25 | ||
type defaultStrategy struct{} | ||
|
||
func (s *defaultStrategy) CalculateCost() int { | ||
return 25 | ||
} | ||
|
||
func NewDefaultStrategy() ShippingStrategy { | ||
return &defaultStrategy{} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package main | ||
|
||
// shipping by freighter | ||
type freightStrategy struct { | ||
distance int | ||
weight int | ||
volume int | ||
} | ||
|
||
func (s *freightStrategy) CalculateCost() int { | ||
return s.distance * s.weight * s.volume | ||
} | ||
|
||
func NewFreightStrategy(distance, weight, volume int) ShippingStrategy { | ||
return &freightStrategy{distance, weight, volume} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package main | ||
|
||
// shopping by local partner | ||
type localStrategy struct { | ||
distance int | ||
weight int | ||
} | ||
|
||
func (s *localStrategy) CalculateCost() int { | ||
return s.distance * s.weight * 2 | ||
} | ||
|
||
func NewLocalStrategy(distance, weight int) ShippingStrategy { | ||
return &localStrategy{distance, weight} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/phakornkiong/go-pattern-match/pattern" | ||
) | ||
|
||
type ShippingStrategy interface { | ||
CalculateCost() int | ||
} | ||
|
||
type Order struct { | ||
Country string | ||
Distance int | ||
Weight int | ||
Volume int | ||
} | ||
|
||
const ( | ||
MY = "Malaysia" | ||
AU = "Australia" | ||
US = "US" | ||
CN = "China" | ||
) | ||
|
||
func shippingStrategyFactory(o Order) ShippingStrategy { | ||
switch o.Country { | ||
case MY: | ||
return NewLocalStrategy(o.Distance, o.Weight) | ||
case US, AU, CN: | ||
if o.Volume > 100 || o.Weight > 250 { | ||
return NewFreightStrategy(o.Distance, o.Weight, o.Volume) | ||
} | ||
return NewAirStrategy(o.Distance, o.Weight, o.Volume) | ||
default: | ||
return NewDefaultStrategy() | ||
} | ||
} | ||
|
||
// This is more declarative, and can be unit tested separately | ||
// Albeit it is much verbose | ||
|
||
var LargeVolumePattern = pattern.Int().Gt(100) | ||
var LargeWeightPattern = pattern.Int().Gt(250) | ||
var IsOverseasPattern = pattern.Union[string](US, AU, CN) | ||
|
||
var airPattern = pattern.Struct(). | ||
FieldPattern("Country", IsOverseasPattern) | ||
|
||
var freightVolPattern = pattern.Struct(). | ||
FieldPattern("Country", IsOverseasPattern). | ||
FieldPattern("Volume", LargeVolumePattern) | ||
|
||
var freightWeightPattern = pattern.Struct(). | ||
FieldPattern("Country", IsOverseasPattern). | ||
FieldPattern("Weight", LargeWeightPattern) | ||
|
||
var freightPattern = pattern.UnionPattern(freightVolPattern, freightWeightPattern) | ||
|
||
func shippingStrategyFactoryPattern(o Order) ShippingStrategy { | ||
return pattern.NewMatcher[ShippingStrategy](o). | ||
WithPattern( | ||
pattern.Struct().FieldValue("Country", MY), | ||
func() ShippingStrategy { | ||
return NewLocalStrategy(o.Distance, o.Weight) | ||
}, | ||
). | ||
WithPattern( | ||
freightPattern, | ||
func() ShippingStrategy { | ||
return NewFreightStrategy(o.Distance, o.Weight, o.Volume) | ||
}, | ||
). | ||
WithPattern( | ||
airPattern, | ||
func() ShippingStrategy { | ||
return NewAirStrategy(o.Distance, o.Weight, o.Volume) | ||
}, | ||
). | ||
Otherwise(func() ShippingStrategy { return NewDefaultStrategy() }) | ||
} | ||
|
||
// o.Volume > 100 || o.Weight > 250 | ||
func main() { | ||
freightWeightOrder := Order{AU, 100, 251, 99} | ||
freightVolOrder := Order{AU, 100, 249, 101} | ||
airOrder := Order{US, 1, 1, 1} | ||
localOrder := Order{Country: MY, Distance: 2, Weight: 5} | ||
defaultOrder := Order{Country: "Singapore"} | ||
|
||
// *main.FreightStrategy | ||
fmt.Printf("%T\n", shippingStrategyFactory(freightWeightOrder)) | ||
// *main.FreightStrategy | ||
fmt.Printf("%T\n", shippingStrategyFactory(freightVolOrder)) | ||
// *main.AirStrategy | ||
fmt.Printf("%T\n", shippingStrategyFactory(airOrder)) | ||
// *main.LocalStrategy | ||
fmt.Printf("%T\n", shippingStrategyFactory(localOrder)) | ||
// *main.DefaultStrategy | ||
fmt.Printf("%T\n", shippingStrategyFactory(defaultOrder)) | ||
|
||
// *main.FreightStrategy | ||
fmt.Printf("%T\n", shippingStrategyFactoryPattern(freightWeightOrder)) | ||
// *main.FreightStrategy | ||
fmt.Printf("%T\n", shippingStrategyFactoryPattern(freightVolOrder)) | ||
// *main.AirStrategy | ||
fmt.Printf("%T\n", shippingStrategyFactoryPattern(airOrder)) | ||
// *main.LocalStrategy | ||
fmt.Printf("%T\n", shippingStrategyFactoryPattern(localOrder)) | ||
// *main.DefaultStrategy | ||
fmt.Printf("%T\n", shippingStrategyFactoryPattern(defaultOrder)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package pattern | ||
|
||
import ( | ||
"reflect" | ||
) | ||
|
||
type fieldVal struct { | ||
field string | ||
val any | ||
} | ||
|
||
type fieldPattern struct { | ||
field string | ||
pattern Patterner | ||
} | ||
|
||
type structPattern struct { | ||
fieldValues []fieldVal | ||
fieldPatterns []fieldPattern | ||
} | ||
|
||
func Struct() structPattern { | ||
return structPattern{} | ||
} | ||
|
||
func (s structPattern) clone() structPattern { | ||
return structPattern{ | ||
fieldValues: s.fieldValues, | ||
fieldPatterns: s.fieldPatterns, | ||
} | ||
} | ||
|
||
func (m structPattern) FieldValue(fieldName string, v any) structPattern { | ||
newPattern := m.clone() | ||
newPattern.fieldValues = append(newPattern.fieldValues, fieldVal{fieldName, v}) | ||
return newPattern | ||
} | ||
|
||
func (m structPattern) FieldPattern(fieldName string, p Patterner) structPattern { | ||
newPattern := m.clone() | ||
newPattern.fieldPatterns = append(newPattern.fieldPatterns, fieldPattern{fieldName, p}) | ||
return newPattern | ||
} | ||
|
||
func (m structPattern) Match(value any) bool { | ||
v := reflect.ValueOf(value) | ||
|
||
// Check if it is struct | ||
if v.Kind() != reflect.Struct { | ||
return false | ||
} | ||
|
||
for _, fv := range m.fieldValues { | ||
value, ok := getFieldValue(v, fv.field) | ||
|
||
if !ok { | ||
return false | ||
} | ||
|
||
if !reflect.DeepEqual(value, fv.val) { | ||
return false | ||
} | ||
} | ||
|
||
for _, fp := range m.fieldPatterns { | ||
value, ok := getFieldValue(v, fp.field) | ||
|
||
if !ok { | ||
return false | ||
} | ||
|
||
if !fp.pattern.Match(value) { | ||
return false | ||
} | ||
} | ||
return true | ||
} | ||
|
||
func getFieldValue(v reflect.Value, fieldName string) (any, bool) { | ||
field := v.FieldByName(fieldName) | ||
|
||
// Check if field exists | ||
if !field.IsValid() { | ||
return nil, false | ||
} | ||
|
||
// Check if field is exported | ||
if !field.CanInterface() { | ||
return nil, false | ||
} | ||
|
||
return field.Interface(), true | ||
|
||
} |
Oops, something went wrong.