Incluindo Campo na 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 no 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, criando um sistema de tipo próprio em cima do existente. Parece-me que isso poderia ser visto como a criação de uma linguagem específica de domínio ou não?

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

Obrigado pelo seu tempo :).

Filip
fonte
Não sei qual o idioma que você está usando, mas se for C #, você poderá usar um tipo dinâmico que basicamente armazena um KVP em um dicionário como o que você está fazendo em produtos e permite que você adote propriedades sem precisar adicioná-las diretamente a a coleção como uma coleção. Você não terá digitação forte. Sei que você pediu um padrão de design, mas não acho que você precise de algo complexo para usá-los. msdn.microsoft.com/pt-br/magazine/gg598922.aspx
Tony
Tony: Estou usando Java aqui, mas considere pseudo-coude :). O C # me permitiria persistir esse objeto dinâmico no banco de dados? Duvido, pois o banco de dados precisa conhecer a estrutura dos dados antecipadamente.
Filip
Existe o Padrão de Projeto do Estado na Gangue dos Quatro, que faz um objeto parecer mudar seu tipo ou classe em tempo de execução. Outras alternativas são Observer Design Pattern ou um teste padrão do Proxy
Nikos M.
1
Por que não usar apenas o tipo de dados do mapa? No banco de dados, ele pode ser representado como {id} + {id, chave, valor}, se você não estiver solicitando desempenho.
Shadows In Rain

Respostas:

4

Parabéns! Você acabou de circunavegar o globo do sistema de linguagem / tipo de programação, chegando ao outro lado do mundo de onde partiu. Você acabou de desembarcar na fronteira da linguagem dinâmica / do objeto baseado em protótipo!

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

A forma extrema disso é uma linguagem baseada em protótipo como Self ou JavaScript. Eles não têm aulas, estritamente falando. Você pode fazer coisas que se parecem com programação baseada em classe e orientada a objetos com herança, mas as regras são bastante flexíveis em comparação com linguagens baseadas em classes mais definidas, como Java e C #.

Idiomas como PHP e Python vivem no meio do caminho. Eles têm sistemas regulares baseados em classe idiomáticos. Porém, os atributos do objeto podem ser adicionados, alterados ou excluídos no tempo de execução - embora com algumas restrições (como "exceto tipos internos") que você não encontra no JavaScript.

A grande desvantagem desse dinamismo é o desempenho. Esqueça o quão forte ou fracamente digitada é a linguagem ou quão bem ela pode ser compilada no código da máquina. 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 envidam grandes esforços para reduzir essa sobrecarga (por exemplo, com atribuição de phantom kwarg e classes baseadas em slot no Python), mas a sobrecarga extra geralmente é apenas par para o curso e o preço da entrada.

Voltando ao seu design, você está enxertando a capacidade de ter propriedades dinâmicas em um subconjunto de suas classes. A Productpode ter atributos variáveis; presumivelmente um Invoiceou um Orderfaria e não poderia. Não é um mau caminho a percorrer. Ele oferece a flexibilidade de variar onde você precisar, enquanto permanece em um sistema de idioma e tipo estrito e disciplinado. No lado negativo, você é responsável por gerenciar essas propriedades flexíveis e provavelmente precisará 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 em p.set_prop('tensile_strength', 104.4)vez dep.tensile_strength = 104.4. Mas trabalhei e construí muitos programas em Pascal, Ada, C, Java e até mesmo linguagens dinâmicas que usavam exatamente esse 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 é frequentemente visto ao projetar o esquema do banco de dados, especialmente para armazenamentos de dados relacionais e pré-relacionais. Às vezes, isso é resolvido através da criação de "super-linhas" que contêm flexibilidade suficiente para conter ou definir a união de todas as variações imaginadas e, em seguida, o preenchimento de todos os dados que aparecem nesses campos. O WordPress wp_postsmesa , por exemplo, tem campos como comment_count, ping_status, post_parente post_date_gmtque só são interessantes em algumas circunstâncias, e que, na prática, muitas vezes ficar em branco. Outra abordagem é uma tabela normalizada muito sobressalente, wp_optionssemelhante à suaPropertyclasse. Embora exija gerenciamento mais explícito, os itens raramente ficam em branco. Os bancos de dados orientados a objetos e de documentos (por exemplo, MongoDB) costumam ter mais facilidade para lidar com as opções de alteração, porque eles podem criar e definir atributos praticamente à vontade.

Jonathan Eunice
fonte
0

Eu gosto da pergunta, meus dois centavos:

Suas duas abordagens são radicalmente diferentes:

  • O primeiro é OO e fortemente tipado - mas não extensível
  • O segundo é de tipo fraco (a string encapsula qualquer coisa)

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

Disgressão: observe que alguns idiomas, como C #, permitem a criação dinâmica de tipos. Qual 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, pois você nem sabe que elas existem? A única maneira razoável seria seja uma reflexão sistemática sobre cada objeto ... terminando com uma linguagem dinâmica pura _ você pode consultar a palavra-chave .NET 'dinâmica')

quantdev
fonte
Criar tipos em tempo de execução é interessante, mas exótico demais para mim (estou programando em Java). Essa solução não funcionaria, se eu desejasse armazenar objetos no banco de dados, que é sempre fortemente digitado, acredito. A solução de tipo fraco que propus pode ser facilmente armazenada no banco de dados.
18714 Filip
0

Criar um tipo em tempo de execução parece 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 núcleo 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. Ele pode mudar após a introdução de um novo produto financeiro ou a alteração dos recursos de compensação. A natureza das mudanças futuras não pode ser prevista. Literalmente, isso pode mudar todos os dias e programadores ruins devem editar o código e lançar uma nova versão, e comerciantes irritados devem revisar seus sistemas.

A decisão óbvia é esconder todo o inferno financeiro atrás de uma abstração. Eles usaram a conhecida abstração de tabelas SQL. A maior parte de seu núcleo pode funcionar com qualquer esquema válido, assim como 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 diante de alguma lógica. Pode ser "propriedade", "tabela", "mensagem" ou até mesmo linguagem humana, mas preste atenção às desvantagens dessa abordagem:

  • Mais código para analisar e validar (e mais tempo fora do curso). Tudo o que o compilador fez por você com a digitação estática, você deve fazer no tempo de execução.
  • Mais documentação de mensagens, tabelas ou outras primitivas. Toda a complexidade vai do código a algum tipo de esquema ou padrão. Aqui está um exemplo do esquema do inferno financeiro acima mencionado: http://ftp.moex.com/pub/FORTS/Plaza2/p2gate_en.pdf (dezenas de páginas de tabelas)
astef
fonte
0

Essa abordagem é um padrão de design conhecido?

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

Você resolveria o problema de maneira diferente?

É assim que eu resolveria o problema, sim.

Eu sei que existem idiomas nos quais posso adicionar um campo em tempo de execução, mas e o banco de dados?

Um banco de dados seria como Java, em alguns sentidos. No 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.

Você prefere adicionar / alterar colunas ou usar algo como mostrado acima?

Eu tentei a coisa de adicionar / alterar coluna, e foi um pesadelo. Isso pode ser feito, mas as coisas continuaram ficando fora de sincronia e eu nunca consegui que funcionasse bem. A estrutura da tabela que descrevi acima foi muito mais bem-sucedida. Se você precisa fazer pesquisas e ORDER BY de, considerar o uso de uma tabela de atributos para cada tipo de dados ( date_attributes, currency_attributes, etc.) ou a adição de algumas das propriedades como boas colunas de banco de dados antigos na tabela de produtos. Os relatórios geralmente são muito mais fáceis de escrever nas colunas do banco de dados do que nas sub-tabelas.

Guy Schalnat
fonte