Как применять паттерн Фабрика в Go без использования наследования?

Как можно исправить код в приведенном примере, чтобы при вызове метода `Do()` в типе `Working` метод `DoFirst()` переопределялся корректно и возвращал `Call DoFirst Working type`, не изменяя при этом реализацию метода `Do()`?
  • 5 февраля 2025 г. 6:54
Ответы на вопрос 3
В языке Go нет прямого наследования, как в других языках, таких как Java или C#. Вместо этого Go использует составление и интерфейсы для достижения аналогичного поведения. Если вы хотите, чтобы метод `DoFirst()` в типе `Working` переопределялся, а метод `Do()` оставался неизменным, вы можете использовать интерфейсы и разные структуры, реализующие один и тот же интерфейс.

Давайте рассмотрим пример, в котором мы создаем интерфейс для метода `DoFirst()`, и для каждого типа создаем свой вариант реализации:

```go
package main

import (
	"fmt"
)

// Интерфейс с методом DoFirst
type Doer interface {
	DoFirst() string
}

// Структура, которая реализует интерфейс
type Working struct{}

func (w Working) DoFirst() string {
	return "Call DoFirst Working type"
}

// Структура, которая также реализует интерфейс
type AnotherType struct{}

func (a AnotherType) DoFirst() string {
	return "Call DoFirst AnotherType"
}

// Метод Do, принимающий интерфейсный тип
func Do(d Doer) {
	fmt.Println(d.DoFirst())
}

func main() {
	var work Doer = Working{}
	var another Doer = AnotherType{}

	Do(work)     // Вывод: Call DoFirst Working type
	Do(another)  // Вывод: Call DoFirst AnotherType
}
```

В данном примере:

- Определяется интерфейс `Doer`, который содержит метод `DoFirst()`.
- Создаются структуры `Working` и `AnotherType`, каждая из которых реализует метод `DoFirst()`.
- Метод `Do()` принимает значение интерфейсного типа `Doer`, что позволяет использовать любой тип, который реализует этот интерфейс.

Таким образом, когда вы вызываете `Do(work)`, вы получаете правильную реализацию метода `DoFirst()` для типа `Working`. Аналогично, для другого типа можно использовать другую реализацию без изменений в методе `Do()`. 

Такой подход позволяет вам использовать паттерн Фабрика и при этом поддерживать полиморфизм без необходимости в классовом наследовании.
Вынести Do в отдельный класс или функцию, на входе принимать (или зависимость в случае объекта) интерфейс с методами DoPrepare, w.DoFirst, DoSecond. 
И воспользоваться паттерном "декоратор" когда будете объявлять зависимости, например в main.go.

Бонусом получите тестируемость. Так как сможете легко подсовывать моки.
Фабрику вообще не принято использовать в Го. Используйте вместо нее обычные конструкторы. Функцию New() в пакете, где у вас описан пользователь. 

Когда с пользователем нужно что-то сделать, делайте это не методом самого типа пользователя, а функцией в пакете, который будет отвечать за проведение действий с пользователями. Если у вас разные пользователи и надо делать какую-то общую логику с ними, то пусть функции пакета, где будут действия с пользователем, принимают интерфейс, а не конкретный тип.

В интерфейсе перечисляйте только нужные для действий методы, а не все методы пользователя.
Похожие вопросы