Когда должен существовать тип, содержащий другой тип, и когда он должен просто "расширять" (?) Этот тип?
В настоящее время я изучаю 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))
}