Golang_generics
Golang_generics
package main
import (
"log"
"time"
)
func main() {
localHostServer := &Server{
host: "127.0.0.1",
port: 8080,
timeout: 3 * time.Second,
}
localHostServer.Run()
}
This Go code snippet outlines a Server struct, equipped with host, port, and
timeout configurations. It includes methods to kickstart the server, coupled with
logging functionalities to track its actions. The main function showcases how to
initiate the server, providing a practical example of its startup process
Now, consider a scenario where we frequently create a Server instance with the same
attribute values. Unfortunately, we can’t directly encode these default values into
the type itself. This is where the Factory Method comes into play. In essence, the
Factory Method is a design pattern that involves a method dedicated to creating and
returning an instance of an object, pre-populated with default values. For example
func main() {
localHostServer := NewLocalHost()
localHostServer.Run()
}
Notice that we now have a method to generate a Server object with predefined
values, which is incredibly useful given that these values are common across my
application. Thus, we’ve introduced a function, NewLocalHost, which provides us
with an object that’s ready to use
The dilemma often boils down to ‘can’t’ versus ‘shouldn’t’. Despite the
constraints, a practical workaround involves passing arguments to a Factory Method
// NewLocalHost creates a new Server instance with optional port and timeout
parameters.
// If port or timeout are not provided (nil), default values are used.
func NewLocalHost(port interface{}, timeout interface{}) *Server {
defaultPort := 8080
defaultTimeout := 3 * time.Second
return &Server{
host: "127.0.0.1",
port: actualPort,
timeout: actualTimeout,
}
}
func main() {
// Example usage of NewLocalHost without parameters, using default values
localHostServer := NewLocalHost(9090, nil)
localHostServer.Run()
Limited Flexibility for Defaults: Managing default values becomes cumbersome. With
direct parameter passing, you either force callers to specify all values
explicitly, including those that should often just be defaults, or you create
multiple constructors for different scenarios, which leads to cluttered and less
maintainable code.\
Long Parameter List: As the number of server parameters grows, the factory method
signature become unwildy.
Compromised Readability: When a function is called with multiple parameters,
especially if they are of the same type, it's hard to tell what each parameter
represents without looking up the function definition. This makes the code less
readable and more error-prone.
Reduced Encapsilation and Flexibility: Directly passing parameters requires
exposing the internal structure and implementation details of your objects. This
can reduce the dlexibility to change the internal implementation.
Inconsistent Object State: Without a clear mechanism to enforce the setting of
necessary fields or validate the configuration, it's easy to end up with objects in
an inconsistent or invalid state.
How Function Options Pattern solve all of those issues?
This pattern are functions that you can agregate to a Factory Method an Example
func main() {
localHostServer, err := NewLocalHost(WithPort(9090))
if err != nil {
log.Fatal(err)
}
localHostServer.Run()
}
The code snipped above has used Function Option Pattern, now it’s possible
personalise the values through Generic Helper Functions WithTimeOut or WithPort.
Notice that we just have showed the main using the Options Pattern not the over all
implementation. I've wanted point out how easy is use this methods instead pass the
value directly. Couples advantages of use that Pattern:
Flexible: It allows for easy addition of new options without breaking existing
code.
Scalable: The pattern scales well with complex configurations and evolving software
requirements.
Readable: Code using functional options is often more readable than alternatives,
making it easier to understand what options are being set.
Intuitive: The pattern leverages Go's first-class functions and closures, making it
intuitive for those familiar with these concepts.
Customizable: Offers a high degree of customization, allowing developers to define
options that can precisely control the behavior of their objects.
Maintainable: The pattern promotes maintainability by keeping configuration logic
centralized and decoupled from the object's core functionality.
But is missing the full implementation, how create a Generic Helper Function and
how implement them into a Factory Method?
Let’s code
func main() {
fmt.Println(sum(1, 2))
fmt.Println(sum(1, 2, 3))
// You can also pass a slice of ints by using the ellipsis suffix
numbers := []int{1, 2, 3, 4}
fmt.Println(sum(numbers...))
}
Now we’ve already know how use the variadic mechanism we need iterate with the list
of Generic Help Function. We can use a range to do it, ignore the index and pass
the objected created as argument.
Let’s code
}
The code snippet features a loop within the NewLocalHost function that iterates
over a slice of OptionsServerFunc. Each opt within the slice is a function
accepting a pointer to a Server as its argument, and it returns an error. As the
loop progresses, each opt is invoked with server as its parameter. Should any opt
return an error, the loop halts prematurely, and the NewLocalHost function returns
both nil and the encountered error.