I am writing this to see what everyone thinks about a solution I have for the composite pattern.
The composite pattern is when 0 or more instances of some interface X can act together as a single instance of interface X. For instance you may have a Filter interface that filters instances of class Foo like so. type Filter interface { IsIncluded(ptr *Foo) bool } Filter can follow the composite pattern like so: type sliceFilter []Filter func (s sliceFilter) IsIncluded(ptr *Foo) bool { for _, f := range s { if !f.IsIncluded(ptr) { return false } } return true } The sliceFilter returns true for a Foo instance if and only if all the Filters in the slice filter return true for that Foo instance. Notice that sliceFilter lets a collection of Filters act as a single Filter instance. You can also have a Filter instance the represents 0 filters like this. type nilFilter struct { } func (n nilFilter) IsIncluded(ptr *Foo) bool { return true } When designing an API around Filters you may include a method called Compose that creates a Filter instance out of a bunch of existing Filter instances. A naive implementation might look like this func Compose(filters ...Filter) Filter { return sliceFilter(filters) } While this works, it is not great because if the caller passes a Filter slice to Compose and later changes that slice, they unwittingly change the returned composite Filter as a side effect. A better implementation may look like this. func Compose(filter ...Filter) Filter { result := make(sliceFilter, len(filter)) copy(result, filter) return result } This is better because it makes a defensive copy of the slice. If the caller passes a []Filter to Compose and changes it, the returned composite Filter works as expected. But this solution isn't optimal either because it always allocates and returns a slice no matter what the caller passes to it. If the caller passes a single Filter to Compose, Compose should return that Filter as is, not a slice. If the caller passes no arguments to Compose, Compose should return the nilFilter instance whose IsIncluded method always returns true. If the caller passes a bunch of nil Filters to Compose, Compose should return the nil Filter instance. If the caller passes a bunch of nilFilters and one non nil Filter to Compose, Compose should return the one non nil Filter. The only time Compose should allocate and return a slice is if it is passed 2 or more non nil Filters. If caller passes 2 or more Filters that are slices, Compose should flatten those out into a single slice rather than returning a slice of slices. common.Join in github.com/keep94/common handles all these edge cases automatically. In addition to a slice type, common.Join requires a nil instance which represents 0 of some interface X. Compose can be written like this func Compose(filter ...Filter) Filter { return common.Join(filter, sliceFilter(nil), nilFilter{}).(Filter) } When written like this, Compose will always do the right thing. It will only return a newly allocated slice if 2 or more arguments passed to it are non nil Filters. If it gets just one non-nil Filter, it simply returns that filter unchanged. If it gets 0 filters, it just returns the nilFilter{} instance. In conclusion, there are some edge cases to consider when implementing the composite pattern. common.Join can address all these edge cases. -- You received this message because you are subscribed to the Google Groups "golang-nuts" group. To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/b859c089-ff6d-4f56-a444-9d477eb8d721n%40googlegroups.com.