Когда должен существовать тип, содержащий другой тип, и когда он должен просто "расширять" (?) Этот тип?

В настоящее время я изучаю Go, выполняя проблемы с росалиндом (в основном это куча каталогов каталогов, связанных с биоинформатикой).

В настоящее время я представляю цепочку ДНК с типом:

type DNAStrand struct {
    dna byte[]
}

Моя первоначальная причина заключалась в том, чтобы инкапсулировать байтовый срез, чтобы я знал, что он содержит только байты, представляющие нуклеотиды: 'A', 'C', 'G', 'T'. Я понял, что это явно не было гарантировано, поскольку я мог просто сделать:

DNAStrand{[]byte("foo bar")}

И больше нет никакой гарантии, что моя строка dna содержит байтовый массив с только элементами из этих четырех байтов.

Поскольку моя структура содержит только массив байтов, лучше/более идеоматично:

type DNAStrand []byte

Или лучше, чтобы тип содержал нить dna? Существуют ли какие-либо эмпирические правила для использования любого из двух подходов?

Ответ 1

Удобно использовать конструкцию с нулевыми полями. Структуры со многими полями еще удобнее. Структуры с точно одним полем немного особенны, и я не могу придумать разумный "хороший" случай, где их использовать - даже если они регулярно встречаются "в дикой природе". Я, например, не использую их.

В любом случае, если вам действительно нужна более жесткая/пуленепробиваемая безопасность в содержимом среза DNAStrand - тогда можно использовать единственную структуру поля и определить метод проверки параметров для этого/такого именованного типа.

В этом случае, если определение позднее будет использовано из какого-либо другого пакета, нет способа, по модулю package unsafe, чтобы обойти проверки и получите результат, эквивалентный вашему примеру DNAStrand{[]byte("foo bar")}.

Ответ 2

Взяв ваш конкретный пример, я бы, вероятно, сделал что-то вроде этого:

type neucleotide char // unexported type users can't construct their own.

type DNAStrand []neucleotide // because users can't construct their own
                             // nucleotides they also can't construct their
                             // own DNAStrands.

const (
  // These are exported values so they can use these nucleotides to construct a
  // DNAStrand with.
  A nucleotide = 'A'
  C nucleotide = 'C'
  G nudleotide = 'G'
  T nucleotide = 'T'
)

// This function allows them to actually construct a DNAstrand with a list of
//  nucleotides from the constants above.
func New(nts ...nucleotide) DNAStrand {
    return nts
}

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

Ответ 3

Я бы использовал type DNAStrand []byte, потому что это просто, и потому что я могу использовать регулярные выражения на нем. Я бы, вероятно, использовал функцию инициализации, которая проверяет, что каждый байт находится в ACGT.

var validDNAStrandPat = regexp.MustCompile("[ACTG]*")

func DNAStrandForString(s string) DNAStrand {
    if !validDNAStrandPat.Match(s) {
        panic("Invalid DNA Strand.")
    }
    return DNAStrand([]byte(s))
}