Pular para o conteúdo principal

Private Class Data

Descrição​

O Private Class Data restringe o acesso e a modificação dos atributos de uma classe ao separá-los em um objeto de dados próprio (a classe Data). A classe principal deixa de expor ou manipular diretamente seus campos e passa a guardar uma referência a esse objeto, que só oferece leitura, ou leitura e alterações controladas.

Normalmente os dados são passados no construtor da classe de dados e ficam imutáveis depois disso. Assim, nem os próprios métodos da classe principal conseguem alterar por engano valores que deveriam ser fixados uma única vez, como na inicialização. O padrão reduz a superfície de escrita do estado e deixa claro quais atributos são estáveis e quais variam.

Não é um dos 23 padrões do livro do Gang of Four. Ele aparece em catálogos complementares como uma técnica de encapsulamento, e em várias linguagens modernas se resume ao uso de campos final/readonly, records ou value objects.

Problemas que resolve​

  • Atributos que deveriam ser definidos só na construção acabam sendo alterados por métodos da própria classe.
  • Classes com muitos campos mutáveis, nas quais é difícil saber quem altera o quê e quando.
  • Exposição desnecessária do estado interno por getters e setters genéricos.
  • Dificuldade de manter invariantes quando qualquer método pode reatribuir campos.

Quando usar​

  • Quando um conjunto de atributos deve ser imutável depois da inicialização.
  • Quando se quer separar dados (estado) de comportamento, deixando explícito o que é somente leitura.
  • Quando a linguagem não oferece bons recursos nativos de imutabilidade (final, readonly, const).
  • Quando o objeto é compartilhado entre threads e precisa de estado seguro para leitura concorrente.

Quando evitar​

  • Quando a linguagem já oferece imutabilidade simples (records, readonly, data class, frozen dataclass) que resolve o problema sem uma classe extra.
  • Em classes pequenas, com poucos campos, nas quais a indireção só acrescenta código.
  • Quando os dados precisam mudar com frequência; a imutabilidade obrigaria a recriar objetos o tempo todo.

Trade-offs​

GanhoCusto / Risco
Encapsulamento mais forte e menos escrita acidental do estadoMais uma classe e uma indireção no acesso aos dados
Invariantes garantidas desde a construçãoAlterar um valor exige criar um novo objeto de dados
Estado imutável, seguro para compartilhar entre threadsPode duplicar o que a linguagem já oferece nativamente
Separação clara entre dados e comportamentoRisco de virar um "saco de getters" sem significado de domínio
  • Acoplamento: a classe principal depende só da interface de leitura da classe de dados.
  • Testabilidade: objetos de dados imutáveis são fáceis de criar e comparar nos testes.
  • Desempenho: recriar objetos de dados a cada alteração pode gerar alocações extras em caminhos críticos.

Diagrama​

O Client usa a MainClass, que compõe internamente uma DataClass. A DataClass recebe os valores no construtor e expõe apenas métodos de leitura, de modo que a MainClass usa os dados sem poder reatribuí-los.

Exemplo​

Uma classe Circle recebe raio, cor e origem na criação, e esses valores não podem mudar depois. Os dados ficam em CircleData, que só permite leitura.

public final class CircleData {
private final double radius;
private final String color;
private final Point origin;

public CircleData(double radius, String color, Point origin) {
this.radius = radius;
this.color = color;
this.origin = origin;
}

public double getRadius() { return radius; }
public String getColor() { return color; }
public Point getOrigin() { return origin; }
}

public class Circle {
private final CircleData data;

public Circle(double radius, String color, Point origin) {
this.data = new CircleData(radius, color, origin);
}

public double circumference() { return 2 * Math.PI * data.getRadius(); }
public double area() { return Math.PI * data.getRadius() * data.getRadius(); }
public void draw() { System.out.println("Desenhando círculo " + data.getColor()); }
}

Padrões relacionados​

  • Proxy — também controla o acesso a um objeto, mas por meio de um substituto, e não pela separação do estado.
  • Facade — ambos reduzem o que é exposto aos clientes; a Facade simplifica um subsistema, o Private Class Data protege os atributos.
  • Builder — costuma montar objetos de dados imutáveis quando há muitos parâmetros de inicialização.
  • Telescoping Constructor — problema que aparece quando a classe de dados recebe muitos atributos no construtor.
  • Memento — também guarda o estado em um objeto separado e opaco, mas para salvá-lo e restaurá-lo depois.
  • Flyweight — o estado intrínseco compartilhado precisa ser imutável, que é justamente o que o Private Class Data garante.

Referência​