Adicionando campo à classe em tempo de execução - padrão de design

15

Imagine que seu cliente deseja ter a possibilidade de adicionar uma nova propriedade (por exemplo, cor) ao produto em sua loja em seu CMS.

Em vez de ter propriedades como campos:

class Car extends Product {
   protected String type;
   protected int seats;
}

Você provavelmente acabaria fazendo algo como:

class Product {
   protected String productName;
   protected Map<String, Property> properties;
}

class Property {
   protected String name;
   protected String value;
}

Ou seja, criar um sistema de tipos próprio em cima do existente. Parece-me que isso poderia ser visto como a criação de um idioma específico ou não poderia?

Esta abordagem é um padrão de design conhecido? Você resolveria o problema de forma diferente? Eu sei que existem idiomas onde eu posso adicionar um campo em tempo de execução, mas e quanto ao banco de dados? Você prefere adicionar / alterar colunas ou usar algo como mostrado acima?

Obrigado pelo seu tempo :).

    
por Filip 18.04.2014 / 02:02
fonte

4 respostas

4

Parabéns! Você acabou de circunavegar o globo do sistema de linguagem de programação / tipo, chegando do outro lado do mundo de onde você partiu. Você acabou de pousar na fronteira da linguagem dinâmica / terra de objetos baseada em protótipos!

Muitas linguagens dinâmicas (por exemplo, JavaScript, PHP, Python) permitem estender ou alterar as propriedades do objeto no tempo de execução.

A forma extrema disso é um idioma baseado em protótipo como Self ou JavaScript. Eles não tem aulas, estritamente falando. Você pode fazer coisas que se pareçam com programação orientada a objetos e baseada em classes com herança, mas as regras são bastante relaxadas em comparação com linguagens baseadas em classes mais definidas, como Java e C #.

Linguagens como PHP e Python vivem no meio termo. Eles têm sistemas regulares baseados em classes idiomáticas. Mas atributos de objeto podem ser adicionados, alterados ou excluídos em tempo de execução - embora com algumas restrições (como "exceto para tipos internos") que você não encontra em JavaScript.

A grande desvantagem desse dinamismo é o desempenho. Esqueça o quão strong ou fracamente digitado é o idioma, ou quão bem ele pode ser compilado em código de máquina. Os objetos dinâmicos devem ser representados como mapas / dicionários flexíveis, em vez de estruturas simples. Isso adiciona sobrecarga a todos os acessos a objetos. Alguns programas fazem um grande esforço para reduzir essa sobrecarga (por exemplo, com a atribuição de kwarg fantasma e classes baseadas em slots no Python), mas a sobrecarga extra é geralmente par apenas para o curso e o preço de admissão.

Voltando ao seu design, você está enxertando a capacidade de ter propriedades dinâmicas em um subconjunto de suas classes. Um Product pode ter atributos variáveis; presumivelmente, um Invoice ou um Order seria e não poderia. Não é um mau caminho a percorrer. Ele oferece a flexibilidade de ter variações onde você precisa, enquanto permanece em um sistema de linguagem e tipo rigoroso e disciplinado. No lado negativo, você é responsável por gerenciar essas propriedades flexíveis e provavelmente terá que fazê-lo por meio de mecanismos que parecem um pouco diferentes dos atributos mais nativos. p.prop('tensile_strength') em vez de p.tensile_strength , por exemplo, e p.set_prop('tensile_strength', 104.4) em vez de p.tensile_strength = 104.4 . Mas eu trabalhei com e construí muitos programas em Pascal, Ada, C, Java e até linguagens dinâmicas que usavam exatamente tal acesso getter-setter para tipos de atributos não-padrão; a abordagem é claramente viável.

A propósito, essa tensão entre tipos estáticos e um mundo altamente variado é extremamente comum. Um problema análogo é visto frequentemente ao projetar o esquema do banco de dados, especialmente para armazenamentos de dados relacionais e pré-relacionais. Às vezes, trata-se de criar "superlinhas" que contêm flexibilidade suficiente para conter ou definir a união de todas as variações imaginadas e, em seguida, inserir todos os dados que chegam nesses campos. A WordPress wp_posts table , por exemplo, possui campos como comment_count , ping_status , post_parent e post_date_gmt que são interessantes apenas sob algumas circunstâncias e que, na prática, muitas vezes ficam em branco. Outra abordagem é uma tabela normalizada , como wp_options , muito parecida com a sua classe Property . Embora exija gerenciamento mais explícito, os itens raramente ficam em branco. Bancos de dados orientados a objeto e de documentos (por exemplo, MongoDB) geralmente têm mais facilidade em lidar com a alteração de opções, porque podem criar e configurar atributos praticamente à vontade.

    
por 16.08.2014 / 16:50
fonte
0

Eu gosto da pergunta, meus dois centavos:

Suas duas abordagens são radicalmente diferentes:

  • O primeiro é OO ans strongmente tipado - mas não extensível
  • O segundo é fracamente tipado (string encapsula qualquer coisa)

Em C ++, muitos usariam um std :: map de boost :: variant para conseguir uma mistura de ambos.

Disgressão: Observe que alguns idiomas, como C #, permitem a criação dinâmica de tipos. O que poderia ser uma boa solução para a questão geral de adicionar membros dinamicamente. No entanto, "modificar / adicionar" tipos após a compilação corrompe o próprio sistema de tipos e torna seus tipos "modificados" quase inúteis (por exemplo, como você acessaria essas propriedades adicionadas, já que você nem sabe que elas existem? ser uma reflexão sistemática sobre cada objeto ... terminando com uma linguagem dinâmica pura _ você pode se referir à palavra-chave 'dinâmica' .NET)

    
por 18.04.2014 / 05:12
fonte
0

Criar um tipo em tempo de execução soa muito mais complicado do que criar uma camada de abstração. É muito comum criar uma abstração para desacoplar sistemas.

Deixe-me mostrar um exemplo da minha prática. A bolsa de Moscou tem o centro comercial chamado Plaza2 com a API do trader. Os comerciantes escrevem seus programas para trabalhar com dados financeiros. O problema é que esses dados são muito grandes, complexos e altamente expostos a mudanças. Pode mudar depois que um novo produto financeiro é introduzido ou os rools de compensação são alterados. A natureza das mudanças futuras não pode ser prevista. Literalmente, isso pode mudar todos os dias e os programadores pobres devem editar o código e lançar uma nova versão, e os comerciantes raivosos devem revisar seus sistemas.

A decisão óbvia é esconder todo o inferno financeiro por trás de uma abstração. Eles usaram abstração de tabelas SQL bem conhecidas. A maior parte de seu núcleo pode trabalhar com qualquer esquema válido, da mesma forma que o software do trader pode analisar dinamicamente o esquema e descobrir se ele é compatível com o sistema.

Voltando ao seu exemplo, é normal criar uma linguagem abstrata na frente de alguma lógica. Pode ser "propriedade", "tabela", "mensagem" ou mesmo linguagem humana, mas preste atenção às desvantagens dessa abordagem:

  • Mais código para análise e validação (e mais tempo fora do curso). Tudo o que o compilador fez para você com a digitação estática que você deve fazer no tempo de execução.
  • Mais documentação de mensagens, tabelas ou outras primitivas. Todos a complexidade vai do código para algum tipo de esquema ou padrão. Aqui está um exemplo do esquema do inferno financeiro acima mencionado: link (dezenas de páginas de tabelas)
por 23.05.2014 / 12:29
fonte
0

Is this approach a known design pattern?

Em XML e HTML, esses seriam os atributos de um nó / elemento. Eu também os ouvi chamados de propriedades estendidas, pares nome / valor e parâmetros.

Would you solve the problem differently?

É assim que eu resolveria o problema, sim.

I know there are languages where I can add a field in runtime, but what about database?

Um banco de dados seria como Java, em alguns sentidos. Em pesudo-sql:

TABLE products
(
    product_name VARCHAR(50),
    product_id INTEGER AUTOINCREMENT
)

TABLE attributes
(
    product_id INTEGER,
    name VARCHAR(50),
    value VARCHAR(2000)
)

Isso corresponderia ao Java

class Product {
   protected String productName;
   protected Map<String, String> properties;
}

Observe que não há necessidade de uma classe Property, pois o Mapa armazena o nome como a chave.

Would you rather add/alter columns or used someting as shown above?

Eu tentei adicionar / alterar a coluna, e foi um pesadelo. Isso pode ser feito, mas as coisas continuaram a ficar fora de sincronia e eu nunca consegui que funcionasse bem. A estrutura da tabela que descrevi acima tem sido muito mais bem sucedida. Se você precisar fazer pesquisas e ordenar por, considere usar uma tabela de atributos para cada tipo de dados ( date_attributes , currency_attributes etc.) ou adicionar algumas das propriedades como boas colunas de banco de dados antigas na tabela de produtos. Geralmente, é muito mais fácil escrever relatórios em colunas de banco de dados e em sub-tabelas.

    
por 09.07.2014 / 06:39
fonte