OOP的代码味道,在这里如何使用可组合性?

First - I have a Node struct, which represents a network connection to a node.

type Node struct {
    *apiclient.Node
}
func (c *Node) GetStatus() (status *models.Status, err error)
func (c *Node) PostTransaction(signedEncodedTx, signedEncodedTxHash string) (err error)
func (c *Node) GetTopBlock() (kb *models.KeyBlockOrMicroBlockHeader, err error)
func (c *Node) GetHeight() (height uint64, err error)
func (c *Node) GetCurrentKeyBlock() (kb *models.KeyBlock, err error)
func (c *Node) GetAccount(accountID string) (account *models.Account, err error)
func (c *Node) GetNameEntryByName(name string) (nameEntry *models.NameEntry, err error)
func (c *Node) GetGenerationByHeight(height uint64) (g *models.Generation, err error)
func (c *Node) GetMicroBlockTransactionsByHash(microBlockID string) (txs *models.GenericTxs, err error)
func (c *Node) GetMicroBlockHeaderByHash(microBlockID string) (txs *models.MicroBlockHeader, err error)
func (c *Node) GetKeyBlockByHash(keyBlockID string) (txs *models.KeyBlock, err error)
func (c *Node) GetTransactionByHash(txHash string) (tx *models.GenericSignedTx, err error)
func (c *Node) GetOracleByPubkey(pubkey string) (oracle *models.RegisteredOracle, err error)
func (c *Node) GetOracleQueriesByPubkey(pubkey string) (oracleQueries *models.OracleQueries, err error)
func (c *Node) GetContractByID(ctID string) (contract *models.ContractObject, err error)

I want to be able to mock out the node's methods, so for every Node method, I make an interface.

type GetStatuser interface
type PostTransactioner interface
type GetTopBlocker interface
type GetHeighter interface
type GetAccounter interface
type GetNameEntryByNamer interface
type GetGenerationByHeighter interface
type GetMicroBlockTransactionsByHasher interface
type GetMicroBlockHeaderByHasher interface
type GetKeyBlockByHasher interface
type GetTransactionByHasher interface
type GetOracleByPubkeyer interface

Sometimes, I just need to return a Node instance. However, to make that code testable without an actual Node connection, I have to introduce this large interface, which is only used once.

type NodeInterface interface {
    GetAccounter
    GetTopBlocker
    GetStatuser
    GetHeighter
    GetKeyBlockByHasher
    GetOracleByPubkeyer
    GetGenerationByHeighter
    GetTransactionByHasher
    GetNameEntryByNamer
    GetMicroBlockHeaderByHasher
    GetMicroBlockTransactionsByHasher
    PostTransactioner
}

Some functions that build upon Node methods are used quite often, so I put them in a Helper struct to avoid having to specify the Node instance every time.

Again, to make code above independent from Helpers or an actual Node connection, I have to introduce a large interface.

type Helpers struct {
    Node GetHeightAccountNamer
}
func (h Helpers) GetTTL(offset uint64) (ttl uint64, err error)
func (h Helpers) GetNextNonce(accountID string) (nextNonce uint64, err error)
func (h Helpers) GetTTLNonce(accountID string, offset uint64) (height uint64, nonce uint64, err error)
func (h Helpers) getAnythingByName(name string, key string) (results []string, err error)
func (h Helpers) GetAccountsByName(name string) (addresses []string, err error)
func (h Helpers) GetOraclesByName(name string) (oracleIDs []string, err error)
func (h Helpers) GetContractsByName(name string) (contracts []string, err error)
func (h Helpers) GetChannelsByName(name string) (channels []string, err error)
type HelpersInterface interface {
    GetTTL(offset uint64) (ttl uint64, err error)
    GetNextNonce(accountID string) (nextNonce uint64, err error)
    GetTTLNonce(accountID string, offset uint64) (height uint64, nonce uint64, err error)
    GetAccountsByName(name string) (addresses []string, err error)
    GetOraclesByName(name string) (oracleIDs []string, err error)
    GetContractsByName(name string) (contracts []string, err error)
    GetChannelsByName(name string) (channels []string, err error)
}

The functions that use the Helper struct are always executed in the context of a particular address and node, so I make a Context struct that combines Helpers and an Address.

// Context stores relevant context (node connection, account address) that one might not want to spell out each time one creates a transaction
type Context struct {
    Address string
    Helpers HelpersInterface
}
func (c *Context) SpendTx(senderID string, recipientID string, amount, fee big.Int, payload []byte) (tx SpendTx, err error)
func (c *Context) NamePreclaimTx(name string, fee big.Int) (tx NamePreclaimTx, nameSalt *big.Int, err error)
func (c *Context) NameClaimTx(name string, nameSalt big.Int, fee big.Int) (tx NameClaimTx, err error)
func (c *Context) NameUpdateTx(name string, targetAddress string) (tx NameUpdateTx, err error)
...

Problems: 1. After using the Context methods to create a Transaction, it is necessary to node.BroadcastTransaction(). However, because of HelpersInterface, the code above cannot access the underlying Node instance. So I either create a new Node above, or return one from Context's constructor

func NewContextFromURL(url string, address string, debug bool) (ctx *Context, node *Node)

which seems inelegant.

Of course I could add Helpers.BroadcastTransaction() but unlike the other helper methods, BroadcastTransaction() does not need any extra code around it.

  1. After writing the question it seems like eliminating the Helpers struct will solve the problem. But I don't know if it is really THE solution, because I used the same pattern with NodeInterface, and sometimes I really do need to say that I will pass in a full-featured Node, so I have to have this large interface.

  2. Somehow I feel like the whole idea of making code easier to write by putting frequently used variables into a class/struct (yes I know Go has no classes) and calling the methods of that struct is not how Go is supposed to be used, but I am not sure.