[GenericAdvanced]Goジェネリックアプリケーションのファクトリメソッド+ジェネリック使用の進化

  みなさん、こんにちは。私はコーダーです。今日はジェネリックについてお話します。ジェネリックは10年で研ぎ澄まされ、10年はそれほど長くはありません。結局のところ、コナンはまだ小学生であり、そうではありません。短すぎますはい、ははは。

インターネット上でジェネリックスの使用に関する記事が多すぎるため、ここではジェネリックスの使用方法については説明しません。今日は、ファクトリメソッドとジェネリックスメソッドを組み合わせて、ビジネスシナリオでジェネリックスがどのように使用されるかを確認します。この記事で取り上げるポイントは次のとおりです。

  1. インターフェイスは一般化されたプログラミングをどのように実装しますか?
  2. ジェネリックがインターフェースの制限をどのように解決するか。
  3. ジェネリックを使用するのに最適な時期。
  4. 機能設計に関する簡単なアドバイス。

余計な手間をかけずに始めましょう。ジェネリックスとその構文の使用方法については、関連情報を自分で確認してください。

一般化されたプログラミングを実現するためのインターフェース

  通常、構造体とメソッドを作成するときは、通常、基本型またはカスタム型のいずれかの特定の型を使用します。ただし、複数の種類のコードに適用できるコードを記述したい場合、この制限はプログラムに対してより制限されます。

  それで、いくつかの一般化されたメソッドとインターフェースをコンパイルしたいのですが、どうすればよいでしょうか?今回はインターフェースを考えましたが、メソッドのパラメータが構造体ではなくインターフェースの場合、プログラムの制限が大幅に解除されます。このインターフェイスを実装する任意の構造をこのメソッドのインターフェイスパラメータとして使用できるため、後で他の同様の機能を追加するときに、要件を満たすためにこのインターフェイスのみを実装できるようにすることができます。

たとえば、次の要件:

HuaweiとAppleの2つの携帯電話のブランド構造を定義し、それぞれのブランドの名前を印刷します。プログラムのスケーラビリティを確保するために、後でXiaomiを追加することもあります

import "fmt"
//手机统一接口
type Phone interface {
  PrintBrand()
}
​
type HuaweiPhone struct {
}
func (hw *HuaweiPhone) PrintBrand() {
  fmt.Printf("品牌名字:华为")
}
​
type Iphone struct {
}
func (ip *Iphone) PrintBrand() {
  fmt.Printf("品牌名字:苹果")
}
//统一打印方法
func PrintBrand(phone Phone) {
  phone.PrintBrand()
}
​
func main() {
  hw := HuaweiPhone{}
  ip := Iphone{}
  PrintBrand(ip)
  PrintBrand(hw)
}
复制代码

上記のコードに示すように、2つの携帯電話ブランドの構造を定義します。各携帯電話のブランド名を印刷する場合は、統一印刷方法を呼び出す必要があります。後で他のブランドを追加する場合は、次のように、このインターフェイスを実装Phoneします。Xiaomi電話ブランドを追加します。

type XiaomiPhone struct {
}
func (xm XiaomiPhone) PrintBrand() {
  fmt.Printf("品牌名字:小米")
}
复制代码

上記のコードから、いくつかの一般化された動作もインターフェースを介して定義できることがわかります。

より一般化されたプログラミングのためのファクトリー+ジェネリック

  可是有时候,即便我们使用了接口,对程序的约束依然还是很强,因为一旦我们指明了具体的接口,就会要求我们必须使用特定的接口。而我们希望编写更通用的代码,要使代码能够应用于某种不具体的类型,而不是具体的一个接口或者结构体。这个要怎么办呢?

比如,我们基于以上的需求继续加需求

由于我们对接的品牌增大到20种,除了上面三种还有 魅族、三星、诺基亚、中兴。。。。等等

这个时候我们基于当前的代码已经不能满足,那么我们想到了工厂设计模式,在工厂中用泛型来泛化所有的类型,我们通过传入类型名字来打印出具体的品牌名。我们引入工厂模式继续优化我们的代码,如下:

var cache sync.Map
//工厂方法 可以传入任意的类型
func PhoneFactory[T any]() (t *T) {
  target := reflect.TypeOf(t)
  v, ok := cache.Load(t)
  if ok {
    return v.(*T)
  }
  v = new(T)
  v, _ = cache.LoadOrStore(target, v)
  return v.(*T)
}
func main() {
  PrintBrand(PhoneFactory[Iphone]())
  PrintBrand(PhoneFactory[HuaweiPhone]())
  PrintBrand(PhoneFactory[XiaomiPhone]())
}
复制代码

代码中我们编写了个工厂方法,泛型类型为 any, 接收任意的类型,在工厂中我们创建对象返回相应的类型并缓存类型对象防止重复创建。这样我们后面再加其他类别的时候可以通过这个工厂方法来统一的创建,我们还可以通过反射在创建前后根据业务需要做一些操作。

泛型使用的最佳时机

  泛型的加入,无疑增加了代码的复杂度,那么我们使用泛型的最佳时机是什么时候呢?

  Go 泛型主要设计者 Ian Lance Taylor 给出了简要的泛型使用方针,当开发者发现自己多次编写完全相同的代码,而这些副本之间的唯一区别仅在于使用了不同类型,这时候便可以考虑使用类型参数。换句话说,即开发者应避免使用类型参数,直到发现自己要多次编写完全相同的代码。

关于功能设计的简单建议

  比如说上面的业务,其实我们开始设计的时候设计到接口层面就可以了,如果一开始就引入工厂方法,其实这算是过度设计,我们设计一个功能的原则是,抓住上下文,适度设计,因为一旦我们投入了过多的精力到灵活设计上,势必会影响本应该完成的需求。同时,过多的功能会引入更多潜在的问题,而修复问题也会耗费我们的时间和精力。而且在当前这个敏捷开发的时代,更是如此。

最后

  感谢各位能看到最后,希望本篇的内容对你有帮助,有什么意见或者建议可以留言一起讨论,看到后第一时间回复,也希望大家能给个赞,你的赞就是我写文章的动力,再次感谢。

为了提高可阅读性,以上代码都是以最简单的方式呈现的,实际业务远比这要复杂的多,这里只是提供一种方向。

本文正在参加技术专题18期-聊聊Go语言框架

おすすめ

転載: juejin.im/post/7120104935953285134
おすすめ