0
var response Response
switch wrapper.Domain {
case "":
    response = new(TypeA)
case "TypeB":
    response = new(TypeB)
case "TypeC":
    response = new(TypeC)
case "TypeD":
    response = new(TypeD)
}
_ = decoder.Decode(response)

As shown in the code snippet, I got enough information from the Domain filed of wrapper to determine the type of response, and for each type, the following operations are performed:

  1. create a new instance of that type using new
  2. use the decoder to decode the byte slice to the instance created in step 1 I am wondering if there is a way to make the first step more generic and get rid of the switch statement.
Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
SolskGaer
  • 71
  • 7
  • 1
    Build a map from type name to the type (e.g. `reflect.Type`), and you can get a new instance like `reflect.New(t).Interface()`. – icza Jan 08 '21 at 10:39
  • @icza But how do I get `t` without an instance – SolskGaer Jan 08 '21 at 11:04
  • Like this: [Golang TypeOf without an instance and passing result to a func](https://stackoverflow.com/questions/48939416/golang-typeof-without-an-instance-and-passing-result-to-a-func/48944430#48944430) – icza Jan 08 '21 at 11:21
  • @SolskGaer https://play.golang.org/p/kTVvrW_echt – mkopriva Jan 08 '21 at 11:31
  • 2
    This is elegant. Nothing wrong about a clear and simple switch statement. – Volker Jan 08 '21 at 12:49
  • @icza I do not think to put reflection to the production code is good idea(at least in most of scenarios). It backs to you at some point of time and shoot you in the foot all the time. – Daniel Hornik Jan 08 '21 at 16:27
  • @DanielHornik I'm not saying to use reflection always, but reflection is quite common, even in the standard lib, e.g. `encoding/json`, `encoding/xml` and most (if not all) database drivers. Those packages are used everywhere in prod environments. – icza Jan 09 '21 at 08:03
  • Yes. I aggre with you @icza, sometimes this is the best way to implement thing, sometimes it is the only way to implement thing, but in this particular scenario it is not the best option. I covered this theory in the answer to the question – Daniel Hornik Jan 09 '21 at 19:23

1 Answers1

1

A bit about your code

As per discussion in comments, I would like to share some experience.

I do not see nothing bad in your solution, but there are few options to improve it, depends what you want to do.

Your code looks like classic Factory. The Factory is a pattern, that create object of a single family, based on some input parameters.

In Golang this is commonly used in simpler way as a Factory Method, sometimes called Factory function.

Example:

type Vehicle interface {};
type Car struct {}

func NewCar() Vehicle {
   return &Car{}
}

But you can easily expand it to do something like you:

package main

import (
    "fmt"
    "strings"
)

type Vehicle interface {}
type Car struct {}
type Bike struct {}
type Motorbike struct {}

// NewDrivingLicenseCar returns a car for a user, to perform 
// the driving license exam.
func NewDrivingLicenseCar(drivingLicense string) (Vehicle, error) {
    switch strings.ToLower(drivingLicense) {
    case "car":
        return &Car{}, nil
    case "motorbike":
        return &Motorbike{}, nil
    case "bike":
        return &Bike{}, nil
    default:
        return nil, fmt.Errorf("Sorry, We are not allowed to make exam for your type of car: \"%s\"", drivingLicense)
    }
}

func main() {
    fmt.Println(NewDrivingLicenseCar("Car"))
    fmt.Println(NewDrivingLicenseCar("Tank"))
}

Above code produces output:

&{} <nil>
<nil> Sorry, We are not allowed to make exam for your type of car: "Tank"

So probably you can improve your code by:

  • Closing into a single function, that takes a string and produces the Response object
  • Adding some validation and the error handling
  • Giving it some reasonable name.

There are few related patterns to the Factory, which can replace this pattern:

  • Chain of responsibility
  • Dispatcher
  • Visitor
  • Dependency injection

Reflection?

There is also comment from @icza about Reflection. I agree with him, this is used commonly, and We cannot avoid the reflection in our code, because sometimes things are so dynamic.

But in your scenario it is bad solution because:

  • You lose compile-time type checking
  • You have to modify code when you are adding new type, so why not to add new line in this Factory function?
  • You make your code slower(see references), it adds 50%-100% lose of performance.
  • You make your code so unreadable and complex
  • You have to add a much more error handling to cover not trivial errors from reflection.

Of course, you can add a lot of tests to cover a huge number of scenarios. You can support TypeA, TypeB, TypeC in your code and you can cover it with tests, but in production code sometime you can pass TypeXYZ and you will get runtime error if you do not catch it.


Conclusion

There is nothing bad with your switch/case scenario, probably this is the most readable and the easiest way to do what you want to do.

Reference

Daniel Hornik
  • 1,957
  • 1
  • 14
  • 33