2015-05-07 3 views
2

У меня есть следующий вид:Как абстрагироваться от деталей цикла для идентичных типов в Go?

type entity struct { 
    components []Component 
} 

func NewEntity(componentsLength ComponentType) Entity { 
    return &entity{ 
     components: make([]Component, componentsLength), 
    } 
} 

При кодировании, я заметил следующий повторяющийся рисунок:

func (e *entity) HasComponent(components ...Component) bool { 
    for _, c := range components { 
     if e.components[c.Type()] == nil { 
      return false 
     } 
    } 
    return true 
} 

func (e *entity) HasAnyComponent(components ...Component) bool { 
    for _, c := range components { 
     if e.components[c.Type()] != nil { 
      return true 
     } 
    } 
    return false 
} 

Go кажется хорошо с большим количеством дублированного кода, когда вы пишете аналогичные функции для различных типы. Однако в этом случае типы одинаковы. У меня все еще возникают проблемы с рефакторингом кода, чтобы обобщить использование цикла for и if.

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

+0

Ого - игнорировать мой комментарий, который должен был для совершенно другой вопрос:/ –

+1

Вы хотите, чтобы реорганизовать методы, которые состоят из * один простой цикл * Если это единственное, что беспокоит вас в? код, который должен быть чертовски хорошим кодом. –

+0

Я уверен, что цикл появится в большем количестве, поскольку я добавлю запланированные функции. И если нет, это все равно будет ценным опытом для меня, поскольку я учусь Go. –

ответ

0

Да, я думаю, вы должны реорганизовать этот код, а не просто сделать его более компактным, но лучше выразить теоретико-множественный характер операций, которые вы выполняете. Обе функции задают вопрос о пересечении e.components и components.

Что вы, по сути говоря, с HasComponent —, который, вероятно, должен быть назван HasAllComponents — является то, что пересечение e.components и components равно components. Другими словами, размер пересечения совпадает с размером components.

Что касается HasAnyComponent, то вы говорите, что размер пересечения по крайней мере один.

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

func (e *entity) CountComponents(components ...Component) int 

Однако, исходный код имеет логику короткого замыкания который перестает смотреть на дополнительные компоненты, как только он знает ответ. Вы можете включить такое короткое замыкание цикла в функции с этой подписью:

func (e *entity) HasMinimumComponents(minimum int, components ...Component) bool 

В цикле увеличиваем на count переменную всякий раз, когда e.components[c.Type()] != nil. Возврат true как только count >= minimum.

Теперь вы можете эффективно реализовать HasAllComponents с телом одной строки:

return entity.HasMinimumComponents(len(components), components...) 

И телом HasAnyComponent становится:

return entity.HasMinimumComponents(1, components...) 

Ниже приводится программой, которая реализует эту идею с немного другими данными типы. Я определил более абстрактный вид Entity, который содержит map[int]bool. Вам не составит труда адаптировать идею к вашей собственной программе.

package main 

import (
     "fmt" 
) 

type Entity struct { 
     ValueMap map[int]bool 
} 

func MakeEntity(values ...int) *Entity { 
     entity := Entity{map[int]bool{}} 
     for _, value := range values { 
       entity.ValueMap[value] = true 
     } 
     return &entity 
} 

func (entity *Entity) HasMinimumValues(minimum int, values ...int) bool { 
     count := 0 
     if minimum == 0 { 
       return true 
     } 
     for _, value := range values { 
       if entity.ValueMap[value] { 
         count++ 
         if count >= minimum { 
           return true 
         } 
       } 
     } 
     return false 
} 

func (entity *Entity) HasAllValues(values ...int) bool { 
     return entity.HasMinimumValues(len(values), values...) 
} 

func (entity *Entity) HasAnyValue(values ...int) bool { 
     return entity.HasMinimumValues(1, values...) 
} 

func main() { 
     entity := MakeEntity(1, 3, 5, 7, 9) 
     fmt.Printf("%t\n", entity.HasAllValues(3, 9)) 
     fmt.Printf("%t\n", entity.HasAllValues(3, 9, 12)) 
     fmt.Printf("%t\n", entity.HasAnyValue(9, 12, 15)) 
     fmt.Printf("%t\n", entity.HasAnyValue(12, 15)) 
} 
+2

Вы объединили оператор if и цикл, но теперь это больше кода, чем раньше, и его сложнее понять. Дублирования лучше избегать, но это тоже сложность и абстракция. –

+0

Согласитесь, что абстракция вам не нужна. Я думаю, что полезно иметь все варианты, но авторы приложений должны много думать о том, что им нужно для выполнения поставленной задачи. – twotwotwo

Смежные вопросы