Qual projeto OO usar (existe um padrão de design)?

11

Eu tenho dois objetos que representam um 'Bar / Club' (um lugar onde você bebe / socializa).

Em um cenário, preciso do nome da barra, endereço, distância, slogon

Em outro cenário, preciso do nome da barra, endereço, URL do site, logotipo

Então, eu tenho dois objetos representando a mesma coisa, mas com campos diferentes.

Eu gosto de usar objetos imutáveis, então todos os campos são definidos pelo construtor .

Uma opção é ter dois construtores e anular os outros campos, ou seja:

class Bar {
     private final String name;
     private final Distance distance;
     private final Url url;

     public Bar(String name, Distance distance){
          this.name = name;
          this.distance = distance;
          this.url = null;
     }

     public Bar(String name, Url url){
          this.name = name;
          this.distance = null;
          this.url = url;
     }

     // getters
}

Eu não gosto disso, pois você precisaria verificar nulo quando usar os getters

No meu exemplo real, o primeiro cenário possui 3 campos e o segundo cenário, cerca de 10; portanto, seria uma dor real ter dois construtores , a quantidade de campos que eu precisaria declarar nula e, quando o objeto estiver em uso, você não você não sabe qual Barvocê está usando e quais campos seriam nulos e quais não.

Que outras opções eu tenho?

Duas classes chamadas BarPreviewe Bar?

Algum tipo de herança / interface?

Outra coisa que é incrível?

Blundell
fonte
29
Uau, você realmente criou um uso legítimo de Barcomo identificador!
Mason Wheeler
1
se você estiver compartilhando algumas propriedades, uma opção seria implementar uma classe base.
Yusubov 5/07
1
Eu nunca pensei nisso. Escrever qualquer tipo de código para a minha sala de estar Bar / Foo pode ficar realmente confuso.
precisa
4
@gnat Como as pessoas estão adivinhando. Desde a sua cotação link: You should only ask practical, answerable questions based on actual problems that you face.E isso é exatamente o que está acontecendo aqui
Blundell

Respostas:

9

Meus pensamentos:

Uma "barra", como representada no seu domínio, possui tudo o que é necessário em qualquer lugar: nome, endereço, URL, logotipo, slogan e "distância" (suponho que seja da localização do solicitante). Portanto, em seu domínio, deve haver uma classe "Bar" que seja a fonte autorizada de dados para uma barra, independentemente de onde os dados serão usados ​​posteriormente. Essa classe deve ser mutável, para que alterações nos dados da barra possam ser feitas e salvas quando necessário.

No entanto, você tem dois locais nos quais os dados desse objeto Bar são necessários e os dois precisam apenas de um subconjunto (e você não deseja que esses dados sejam alterados). A resposta usual é um "objeto de transferência de dados" ou DTO; um POJO (objeto simples do Java) contendo os getters de propriedades imutáveis. Esses DTOs podem ser produzidos chamando um método no objeto principal do domínio Bar: "toScenario1DTO ()" e "toScenario2DTO ()"; os resultados são um DTO hidratado (o que significa que você só precisa usar o construtor longo e complicado em um único local).

Se você precisar enviar dados de volta para a classe de domínio principal (para atualizá-los; qual é o sentido dos dados se você não puder alterá-los conforme necessário para refletir o estado atual do mundo real?), Poderá criar um dos seguintes DTOs ou use um novo DTO mutável e devolva-o à classe Bar usando o método "updateFromDto ()".

EDIT: para fornecer um exemplo:

public class Bar {
     private String name;
     private Address address; 
     private Distance distance;
     private Url url;
     private Image logo;
     private string Slogan;

     public OnlineBarDto ToOnlineDto()
     {
         return new OnlineBarDto(name, address, url, logo);
     }

     public PhysicalBarDto ToPhysicalDto()
     {
         return new PhysicalBarDto(name, address, distance, slogan);
     }

     public void UpdateFromDto(PhysicalBarDto dto)
     {
         //validation logic here, or mixed into assignments

         name = dto.Name;
         address = dto.Address;
         distance = dto.Distance;
         slogan = dto.Slogan;
     }

     public void UpdateFromDto(OnlineBarDto dto)
     {
         //Validate DTO fields before performing assignments

         name = dto.Name;
         address = dto.Address;
         url= dto.Url;
         logo = dto.Logo;
     }

     // getters/setters - As necessary within the model and data access layers;
     // other classes can update the model using DTOs, forcing validation.
}

public class PhysicalBarDto
{
     public final String Name;
     public final Address Address;
     public final Distance Distance;
     public final String Slogan;

     public PhysicalBarDto(string Name, Address address, Distance distance, string slogan) 
     { //set instance fields using parameter fields; you know the drill }
}

public class OnlineBarDto
{
     public final String Name;
     public final Address Address;
     public final Image Logo;
     public final Url Url;

     public OnlineBarDto(string Name, Address address, Url url, Image logo) 
     { //ditto }
}

As classes Endereço, Distância e URL devem ser imutáveis ​​ou, quando usadas nos DTOs, devem ser substituídas por equivalentes imutáveis.

KeithS
fonte
o que significa o acrônimo DTO? Não entendi bem o que você está dizendo, você poderia colocá-lo em um exemplo elaborado, por favor. fyi Os dados vêm de um servidor assim que uma vez qualquer forma desta classe é 'hidratado' os campos não terão de ser mudada, apenas usado para exibição na UI
Blundell
1
DTO significa "objeto de transferência de dados" e refere-se a uma classe de dados de estrutura muito simples usada para mover dados da camada de domínio "rica" ​​para camadas superiores como a interface do usuário, sem expor a camada de domínio real à interface do usuário (permitindo alterações a ser feito no domínio sem afetar a interface do usuário, desde que o DTO não precise ser alterado).
Keiths
a mutabilidade não tem qualquer influência sobre modificação ou persistência.
4
@JarrodRoberson - você está brincando? Se uma classe é imutável (não pode ser alterada no local após a instanciação), a única maneira de fazer uma alteração nos dados na camada de dados é construir uma nova instância representando o mesmo registro (a mesma PK) com membros diferentes. Embora os métodos de "mutação" que produzem uma nova instância possam facilitar, ela ainda tem uma grande influência na modificação e persistência.
KeithS
1
@JarrodRoberson Ouça a comunidade. Você está errado. . Na verdade metade dos comentários neste resposta completa mostram que precisamos de alguma OO básico escolaridade em torno da placa - É nojento ..
David Cowden
5

Se você se importa apenas com um subconjunto das propriedades e deseja garantir que elas não se misturem, crie duas interfaces e use isso para conversar com seu objeto base.

jmoreno
fonte
1
Você diz isso, mas poderia dar um exemplo usando a Barclasse
Blundell
3

O Padrão do Construtor (ou algo parecido com ele) pode ser útil aqui.

Ter objetos imutáveis ​​é algo admirável, mas a realidade é que, com o Reflection em Java, nada é verdadeiramente seguro ;-).

Martijn Verburg
fonte
Eu posso ver como isso HawaiianPizzaBuilderfunciona, porque os valores de que ele precisa são codificados. No entanto, como você pode usar esse padrão se os valores são recuperados e passados ​​para um construtor? O HawaiianPizzaBuilderainda teria todos os getters que o SpicyPizzaBuildernulo é possível. A menos que você combine isso com @Jarrods Null Object Pattern. Um exemplo de código com Baro seu ponto de vista
Blundell
+1 eu uso construtor em casos como este, funciona como um encanto - incluindo, mas não limitado a definir padrões razoáveis em vez de nulo quando eu quero isso
mosquito
3

O ponto chave aqui é a diferença entre o que é uma "barra" e como você a usa em um ou outro contexto.

O Bar é uma entidade única no mundo real (ou um mundo artificial, como um jogo), e apenas UMA instância de objeto deve representá-lo. A qualquer momento, quando você não criar essa instância a partir de um segmento de código, mas carregá-la de um arquivo de configuração ou de um banco de dados, isso será mais evidente.

(Para ser ainda mais esotérico: cada instância do Bar tem um ciclo de vida diferente do objeto que o representa quando o programa é executado. Mesmo se você tiver um código-fonte que cria essa instância, significa que a entidade do Bar, conforme descrita, "existe "em um estado inativo no seu código-fonte e" desperte "quando esse código realmente o criar na memória ...)

Desculpe pelo longo começo, mas espero que isso esclareça meu argumento. Você tem uma classe Bar com todos os atributos que você precisaria e uma instância Bar representando cada entidade Bar. Isso está correto no seu código e independente de como você deseja ver a mesma instância em diferentes contextos .

O último pode ser representado por duas interfaces diferentes , que contêm os métodos de acesso necessários (getName (), getURL (), getDistance ()), e a classe Bar deve implementar os dois. (E talvez a "distância" mude para "local" e getDistance () se torne um cálculo a partir de outro local :-))

Mas a criação é para a entidade Bar e não para a maneira como você deseja usar essa entidade: um construtor, todos os campos.

EDITADO: Eu posso escrever código! :-)

public interface Place {
  String getName();
  Address getAddress();
}

public interface WebPlace extends Place {
   URL getUrl();
   Image getLogo();
}

public interface PhysicalPlace extends Place {
  Double getDistance();
  Slogon getSlogon();
}

public class Bar implements WebPlace, PhysicalPlace {
  private final String name;
  private final Address address;
  private final URL url;
  private final Image logo;
  private final Double distance;
  private final Slogon slogon;

  public Bar(String name, Address address, URL url, Image logo, Double distance, Slogon slogon) {
    this.name = name;
    this.address = address;
    this.url = url;
    this.logo = logo;
    this.distance = distance;
    this.slogon = slogon;
  }

  public String getName() { return name; }
  public Address getAddress() { return address; }
  public Double getDistance() { return distance; }
  public Slogon getSlogon() { return slogon; }
  public URL getUrl() { return url; }
  public Image getLogo() { return logo; } 
}
Lorand Kedves
fonte
1

Padrão apropriado

O que você está procurando é mais comumente chamado de Null Object Pattern. Se você não gostar do nome, pode chamá-lo de Undefined Value Pattern, o mesmo rótulo semântico diferente. Às vezes, esse padrão é chamado Poison Pill Pattern.

Em todos esses casos, o objeto é uma substituição ou substitui um nulo em Default Valuevez de null. It doesn't replace the semantic ofnulo nulo but makes it easier to work with the data model in a more predictable way becauseagora nunca deve ser um estado válido.

É um padrão em que você reserva uma instância especial de uma determinada classe para representar uma nullopção diferente como a Default Value. Dessa forma, você não precisa verificar null, pode verificar a identidade em sua NullObjectinstância conhecida . Você pode chamar métodos com segurança e afins sem se preocupar NullPointerExceptions.

Dessa forma, você substitui suas nullatribuições pelas NullObjectinstâncias representativas e está pronto.

Análise Orientada a Objetos Adequada

Dessa forma, você pode ter um Interfacepolimorfismo comum e ainda ter proteção contra preocupações com a ausência de dados nas implementações específicas da interface. Portanto, alguns Barpodem não ter presença na Web e outros podem não ter dados de localização no momento da construção. Null Object Patterpermite que você forneça um valor padrão para cada um desses markerdados, que diz a mesma coisa, nada foi fornecido aqui, sem ter que lidar com a verificação de NullPointerExceptiontodos os lugares.

Design Orientado a Objetos Adequado

Primeiro ter uma abstractimplementação que é um conjunto de super de todos os atributos que tanto Bare Clubação.

class abstract Establishment 
{
     private final String name;
     private final Distance distance;
     private final Url url;

     public Bar(final String name, final Distance distance, final Url url)
     {
          this.name = name;
          this.distance = distance;
          this.url = url;
     }

     public Bar(final String name, final Distance distance)
     {
          this(name, distance, Url.UNKOWN_VALUE);
     }

     public Bar(final String name, final Url url)
     {
          this(name, Distance.UNKNOWN_VALUE, url);
     }

     // other code
}

Em seguida, você pode implementar subclasses dessa Establishmentclasse e adicionar apenas as coisas específicas necessárias para cada uma das classes Bare Clubque não se aplicam à outra.

Persistência

Esses objetos de espaço reservado, se construídos corretamente, podem ser armazenados de forma transparente em um banco de dados sem nenhum tratamento especial também.

Prova futura

Se você decidiu pular na onda de Inversão de Controle / Injeção de Dependência mais adiante, esse padrão facilita a injeção desses objetos marcadores.


fonte
0

Eu acho que o problema é que você não está modelando uma barra em nenhum desses cenários (e você está modelando dois problemas diferentes, objetos etc.). Se eu vir uma classe Bar, esperaria alguma funcionalidade relacionada a bebidas, menus, assentos disponíveis e seu objeto não tem nada disso. Se eu vir o comportamento dos seus objetos, você está modelando informações sobre um estabelecimento. Bar é o que você está usando para esse momento, mas não é o comportamento intrinsecamente que eles estão implementando. (Em outro contexto: se você estiver modelando um casamento, terá duas variáveis ​​de instância Pessoa esposa; Pessoa marido; esposa é a função atual que você está atribuindo a esse objeto naquele momento, mas o objeto ainda é uma Pessoa). Eu faria algo assim:

class EstablishmentInformation {
     private final String name;

     public EstablishmentInformation(String name){
          this.name = name;
     }

     // getters
}

class EstablishmentLocationInformation {
    EstablishmentInformation establishmentInformation;
     private final Distance distance;

     public EstablishmentLocationInformation (String name, Distance distance){
          this.establishmentInformation = new EstablishmentInformation(name)
          this.distance = distance;
     }
}

class EstablishmentWebSiteInformation {
    EstablishmentInformation establishmentInformation;
     private final Url url;

     public EstablishmentWebSiteInformation(String name, Url url){
          this.establishmentInformation = new EstablishmentInformation(name)
          this.url = url;
     }
}
user1494736
fonte
-1

Realmente não há necessidade de complicar demais isso. Você precisa de dois tipos diferentes de objetos? Faça duas aulas.

class OnlineBar {
     private final String name;
     private final Url url;
     public OnlineBar(String name, Url url){
          this.name = name;
          this.url = url;
     }

     // ...
}
class PhysicalBar {
     private final String name;
     private final Distance distance;
     public PhysicalBar(String name, Distance distance){
          this.name = name;
          this.distance = distance;
     }
     //...
}

Se você precisar operar neles igualmente, considere adicionar uma interface ou usar reflexão.

DeadMG
fonte
@ David: Oh não. Eles têm um membro de dados inteiro em comum. EMERGÊNCIA!
DeadMG
sem alguma interface comum, não há polimorfismo nesta solução, nenhuma dessas classes poderia ser substituída pela outra com essa má decisão de design. Não é que eles não tenham o mesmo superconjunto de atributos, é que, por padrão, alguns desses atributos são null. Lembrar nullsignifica a ausência de dados , não a ausência do atributo.
1
@DeadMG Esse é possivelmente um exercício exatamente da idéia de agrupar valores compartilhados em objetos pai. Sua solução não receberia crédito total se você a propusesse nesse contexto.
David Cowden
O OP não especifica nenhuma necessidade de substituibilidade. E como eu disse, você pode simplesmente adicionar uma interface ou usar a reflexão, se quiser.
DeadMG
@DeadMG Mas refletir é como tentar escrever um aplicativo móvel de plataforma cruzada usando um ambiente - pode funcionar, mas não está correto . A penalidade por chamar um método usando reflexão é entre 2 e 50 vezes mais lenta que uma chamada de método normal. Reflexão não é uma cura para tudo ..
David Cowden
-1

Minha resposta para qualquer pessoa com esse tipo de problema é dividi-lo em etapas gerenciáveis .

  1. Primeiro, crie duas classes BarOnee BarTwo(ou chame as duas, Barmas em pacotes diferentes)
  2. Comece a usar seus objetos como classes separadas, não se preocupe com a duplicação de código por enquanto. Você notará quando cruzar (métodos duplicados) de um para o outro
  3. Você pode descobrir que eles não têm relação alguma e, portanto, deve se perguntar se os dois são realmente umbar renomeie a classe ofensiva para o que ela representa agora
  4. Se você encontrar campos ou comportamentos comuns, poderá extrair um interfaceou superclasscom o comportamento comum
  5. Depois de ter um interfaceou superclassvocê pode criar um builderou factorypara criar / recuperar seus objetos de implementação

(4 e 5 são sobre o que as outras respostas a esta pergunta são)

Blundell
fonte
-2

Você precisa de uma classe base, por exemplo, Local que tenha um nome e um endereço . Agora, você tem duas classes Bar e BarPreview estender a classe base de Localização . Em cada classe, você inicializa as variáveis ​​comuns da superclasse e, em seguida, suas variáveis ​​exclusivas:

public class Location {
    protected final String name;
    protected final String address:

    public Location (String locName, String locAddress) {
    name = locName;
    address = locAddress
    }

}

public class Bar extends Location {
    private final int dist;
    private final String slogan;

    public Bar(String barName, String barAddress,
               int distance, String barSlogan) {
    super(locName, locAddress);
    dist = distance;
    slogan = barSlogan;
    }
}

E semelhante para a classe BarPreview ..

Se você dormir melhor à noite, substitua todas as instâncias de Location no meu código por AnythingYouThinkWouldBeAnAproprNameNameForAThingThatABarExtendsSuchAsFoodServicePlace - ffs.

David Cowden
fonte
o OP quer que a instância seja imutável , isso significa que tudo tem que ser final.
1
Isso pode funcionar, você precisa finaldos campos @ David. Barnão deveria, extend Locationporém, que isso não faz sentido. Talvez Bar extends BaseBare BarPreview extends BaseBaresses nomes realmente não parecer bom demais, quer, porém, eu estava esperando por algo mais elegante
Blundell
@JarrodRoberson Estou apenas desenhando para ele .. basta adicionar final para que seja imutável. É um acéfalo. O problema fundamental da pergunta do OP é que ele não sabe como ter uma classe base e duas classes separadas que estendem uma classe base. Estou simplesmente detalhando isso.
precisa
@ Blundell do que você está falando? Um bar é um local . Apenas substitua meu local pelo seu BaseBar e é exatamente a mesma coisa. Você sabe que quando uma classe estende outra, a classe estendida não precisa ser denominada Base [ClassThatWillExtend], certo?
precisa
1
@DavidCowden, você pode parar para anunciar sua resposta como um comentário abaixo de qualquer outra resposta, por favor?
21968 Marktani