design OO em um pipeline de processamento de dados

5

Eu estou querendo saber como projetar uma classe bastante simples, cujas propriedades são complexas para computar. Além disso, as propriedades dependem umas das outras para computação.

Um exemplo usando gráficos e processamento de gráficos (pense nos nós e nas bordas, não em gráficos ou gráficos de dispersão) para motivar o problema:

A classe CommunityGraphSet é uma coleção de instâncias de Community . CommunityGraphSet é inicializado com um gráfico de entrada (algum grande gráfico de rede social), juntamente com alguns parâmetros básicos que descrevem o que é conhecido sobre o gráfico. Um Community é um subgráfico do gráfico de entrada, juntamente com alguns descritores estruturais.

Após a inicialização, o CommunityGraphSet não contém Community subgrafos. Após várias etapas principais de processamento, ele contém um conjunto de Community subgráficos. Cada etapa de processamento principal (ou seja, chamada de função) aciona uma GUI para um ser humano ajustar os parâmetros do algoritmo que está sendo executado nessa etapa. O humano define parâmetros, os resultados do processamento são visualizados e eles continuam a refinar os parâmetros até que os resultados sejam aceitáveis. Depois que eles "aceitam os parâmetros" com um clique no botão, os resultados que viram são retornados pelo método que acionou a GUI.

input_graph = read("graph_file.csv")

number_communities = 3
sparseness_thresh = (0.05, 0.10)


# MAJOR PROCESSING STEP
# get parameters for key community member identification based on overall graph structure
key_ident_params = compute_key_identification_params(input_graph)

# initialize the graph set
graph_set = CommunityGraphSet(input_graph, number_communities, sparseness_thresh)

# MAJOR PROCESSING STEP
# mark where community subgraphs definitely are not.
partition_mask = isolate_potential_communities(graph_set)

# use mask to extract subgraphs for refinement by different algorithms.
potential_community_subgraphs = graph_set.extract_subgraphs(partition_mask)

for community in community_list:

    # MAJOR PROCESSING STEP
    # key community member extraction 
    key_members = extract_key_members(community, key_ident_params)

    # MAJOR PROCESSING STEP
    # use identified key members and community sparseness to trim community to final subgraph
    final_community = refine_community(community, key_members, sparseness_thresh)

    # put the refined community in the final set
    graph_set.add_community(final_community)

save(graph_set.serialize(), "communities.json")

Meus problemas com esse design:

  • CommunityGraphSet é stateful. Ele contém Community subgrafos somente depois que a função certa é chamada na ordem correta.
  • O pipeline de processamento é monolítico. À medida que o pipeline de processamento cresce e muda, mantê-lo se tornará desarrazoado à medida que me deparo com um número crescente de variáveis usadas em cada vez mais lugares.

Estes são realmente problemas? Ou são inerentes aos pipelines de processamento de dados?

Se forem problemas, como posso resolvê-los?

Notas:

  • Nenhum de CommunityGraphSet não está acoplado à lógica da GUI, ele é acoplado a uma interface IterativeOptimizer para cada etapa. A GUI descrita ou um algoritmo de otimização inteligente poderia implementar essa interface.
por kdbanman 13.05.2015 / 00:38
fonte

1 resposta

2

Eu acho que os dois problemas que você listou surgem não do seu design, mas sim do problema que você está resolvendo.

Dependendo de como você deseja criar seu design genérico, você pode ter alguns metadados que definem cada etapa principal do processamento e, em seguida, alguns métodos que você chama para "empurrar" o pipeline para a próxima etapa. É claro, já que parece que cada etapa requer seus próprios parâmetros específicos do usuário, você teria que definir os parâmetros genericamente também, usando mais metadados.

Eu não acho que o que acabei de descrever seja um design particularmente bom, baseado na descrição do seu problema. Ele adiciona muita complexidade e sobrecarga de meta-dados sem nenhum benefício real. Às vezes, um problema é tão específico de domínio que o código que você desenhou para resolvê-lo será igualmente especializado. Isso não é necessariamente uma coisa ruim. Você pode atenuá-lo escrevendo um código que seja tão claro e simples quanto possível e bem documentado: -)

Atualização: Quanto à sua pergunta específica sobre se é "OK ter uma turma projetada para ser incompleta na inicialização e concluída por etapas de processamento necessárias", você pode estar pensando na conselhos mais gerais que são mencionados em algumas diretrizes de design OO :

A properly written constructor leaves the resulting object in a valid state.

Lembre-se de que você decide o que significa "válido" no contexto. No seu exemplo, eu diria que o CommunityGraphSet é em um estado válido, mesmo no começo, porque seu propósito não é servir como uma representação imutável estática dos gráficos, mas como um pipeline de processamento para gerar os gráficos baseados em etapas sucessivas de entrada do usuário. Em outras palavras, não é realmente "incompleto" porque os requisitos determinam que o processamento requer vários estágios de entrada do usuário. Talvez um nome melhor seja CommunityGraph Generator ou CommunityGraph Processor . Além disso, você poderia definir uma classe separada CommunityGraph Set que manteria os resultados de todo o processo assim que o pipeline fosse concluído. Mesmo que essa classe esteja apenas contida na maior, ela pode tornar o design mais compreensível para um purista da OO.

    
por 13.05.2015 / 02:18
fonte