Modo pulito per utilizzare l'implementazione mutabile delle interfacce Immutable per l'incapsulamento

1

Il mio codice sta lavorando su una relazione di compost che crea una struttura ad albero, la classe A ha molti bambini di tipo B, che ha molti figli di tipo C ecc. La classe di livello più basso, chiamala bar, punta anche a una barra collegata classe. Ciò rende praticamente interconnessi quasi tutti gli oggetti nel mio dominio. Oggetti immutabili sarebbero problematici a causa delle spese di ricostruzione di quasi tutto il mio dominio per apportare una singola modifica a una classe.

Ho scelto di adottare un approccio all'interfaccia. Ogni oggetto ha un'interfaccia Immutable che pubblica solo i metodi getter. Ho oggetti controller che costruiscono gli oggetti dominio e quindi hanno riferimento agli oggetti completi, quindi in grado di chiamare i metodi setter; ma pubblica sempre e solo l'interfaccia immutabile. Qualsiasi modifica richiesta passerà attraverso il controller. Quindi qualcosa di simile:

public interface ImmutableFoo{

    public Bar getBar();
    public Location getLocation();
}

public class Foo implements ImmutableFoo{

    private Bar bar;
    private Location location;

    @Override
    public Bar getBar(){
        return Bar;
    }

    public void setBar(Bar bar){
        this.bar=bar;
    }

    @Override
    public Location getLocation(){
        return Location;
    }
 }

 public class Controller{
     Private Map<Location, Foo> fooMap;

     public ImmutableFoo addBar(Bar bar){
         Foo foo=fooMap.get(bar.getLocation());

         if(foo!=null)
             foo.addBar(bar);

         return foo;
     }
}

Ho sentito che l'approccio di base sembra ragionevole, tuttavia, quando parlo con gli altri, sembrano sempre avere problemi a immaginare ciò che sto descrivendo, il che mi lascia preoccupato di avere un problema di progettazione più ampio di cui sono a conoscenza. È problematico avere oggetti di dominio così strettamente accoppiati o usare l'approccio quasi mutevole per modificarli?

Supponendo che l'approccio alla progettazione di per sé non sia intrinsecamente imperfetto, la particolare discussione che mi ha fatto riflettere sul mio approccio ha a che fare con la presenza della logica di business negli oggetti del dominio.

Attualmente i miei metodi setter negli oggetti mutabili eseguono il controllo degli errori e tutte le altre logiche necessarie per verificare e apportare modifiche all'oggetto. È stato suggerito che questo dovrebbe essere estratto in una classe di servizio, che applica tutta la logica aziendale, per semplificare i miei oggetti di dominio. Comprendo il vantaggio del derisione / test e della separazione generale della logica in due classi.

Tuttavia, con un metodo / oggetto di servizio Sembra che perdo il vantaggio del polimorfismo, non posso sovrascrivere una classe base per aggiungere nuovi controlli degli errori o logica aziendale. Sembra che, se le mie classi polimorfiche fossero abbastanza complicate, avrei finito con un metodo di servizio che doveva controllare una dozzina di flag per decidere quale controllo degli errori e logica aziendale si applica. Quindi, per esempio, se volessi avere un figlio che avesse anche un campo di dimensioni che dovrebbe essere paragonato alla battuta prima di aggiungere il mio attuale approccio sarebbe simile a questo.

 public class Foo implements ImmutableFoo{

     public void addBar(Bar bar){
         if(!getLocation().equals(bar.getLocation())
            throw new LocationException();

         this.bar=bar;
     }
 }

 public interface ImmutableChildFoo extends ImmutableFoo{
     public int getSize();
 }

 public ChildFoo extends Foo implements ImmutableChildFoo{

      private int size;

      @Override
      public int getSize(){
          return size;
      }

      @Override
      public void addBar(Bar bar){
          if(getSize()<bar.getSize()){
              throw new LocationException();

          super.addBar(bar);
      }

Il mio collega stava suggerendo di avere un oggetto di servizio che assomigliava a qualcosa del genere (più semplice, l'oggetto 'servizio' sarebbe probabilmente più complesso).

 public interface ImmutableFoo{
     ///original interface, presumably used in other methods
     public Location getLocation();
     public boolean isChildFoo();
 }
 public interface ImmutableSizedFoo implements ImmutableFoo{
     public int getSize();
 }

public class Foo implements ImmutableSizedFoo{
     public Bar bar;

     @Override
     public void addBar(Bar bar){
         this.bar=bar;
     }

     @Override
     public int getSize(){
         //default size if no size is known
         return 0;
     }

      @Override
      public boolean isChildFoo
         return false;
      }
 }

 public ChildFoo extends Foo{

      private int size;

      @Override
      public int getSize(){
          return size;
      }

      @Override
      public boolean isChildFoo();
         return true;
      }
 }

 public class Controller{
     Private Map<Location, Foo> fooMap;

     public ImmutableSizedFoo addBar(Bar bar){
         Foo foo=fooMap.get(bar.getLocation());
         service.addBarToFoo(foo, bar);

         returned foo;
     }

     public class Service{

         public static void addBarToFoo(Foo foo, Bar bar){

            if(foo==null)
               return;
            if(!foo.getLocation().equals(bar.getLocation()))
                throw new LocationException();
            if(foo.isChildFoo() && foo.getSize()<bar.getSize())
                throw new LocationException();

            foo.setBar(bar);
         }
   }
}

L'approccio raccomandato all'uso dei servizi e l'inversione del controllo sono intrinsecamente superiori, o superiori in certi casi, ai metodi di override direttamente? Se è così, c'è un buon modo di seguire l'approccio al servizio senza perdere la potenza del polimorfismo per ignorare alcuni dei comportamenti?

    
posta dsollen 28.05.2014 - 02:33
fonte

1 risposta

4

Penso che chiamare queste interfacce Immutable sia fonte di confusione. Non sono Immutabili, sono solo viste di oggetti possibilmente muttabili. Questo da solo può portare al tuo progetto di essere frainteso. Chiamali ReadOnlySomething o ReadableSomething (questa convenzione consente di scrivere WritableSomething e ReadWriteableSomething (che è brutto a lungo, quindi forse non è un'idea così gud)), o SomethingView (o qualsiasi altra cosa che indica che queste cose non sono necessariamente immutabili, ma non consente la mutazione loro))

Informazioni sull'ereditarietà: utilizzalo se esiste effettivamente una rellationship "IS A" e il chiamante non ha bisogno di conoscere il tipo effettivo per utilizzarlo in modo significativo. Se il chiamante deve ispezionarlo, usa la composizione.

    
risposta data 28.05.2014 - 12:17
fonte

Leggi altre domande sui tag