Creando tipos de ubicación sólidos inmutables y mutables -- java campo con object-oriented campo con coordinate-system campo con immutability camp codereview Relacionados El problema

Creating SOLID immutable and mutable Location types


6
vote

problema

Español

Tengo un tipo de ubicación:

  public abstract class Location implements Locatable {     private int x, y;      protected final void setX(int x) {          this.x = x;     }      protected final void setY(int y) {          this.y = y;     }      public final int getX() {         return x;     }      public final int getY() {         return y;     } }  interface Locatable {     int getX();     int getY(); }   

Location8 tiene métodos mutadores protegidos, para asegurarse de que pueda proporcionar un tipo 9988777666555443339 , pero no estoy seguro de si esto viola el principio de la filgación de la interfaz al crear un 99887766555443310 .

Siento que no lo siento, ya que el cliente nunca tendrá acceso a los colonos a través de P1 (no se puede llamar desde una instancia, no puede llamarlo desde una subclase de P2 ). Así que este fue mi intento de un tipo de ubicación sólida, que planeo usar en un proyecto.

  P3  

Por favor, dale sus pensamientos. Mis principales preocupaciones son:

  1. No estoy seguro de si el Enum es una exageración
  2. si los colonos son una violación de sólidos; No estoy seguro de qué otra cosa debería hacerlo.
  3. Si P4 es el mejor ajuste para el método de ajuste

  P5  

P6 se implementaría por cualquier cosa con una ubicación, como P7 :

  P8  
Original en ingles

I have a Location type:

public abstract class Location implements Locatable {     private int x, y;      protected final void setX(int x) {          this.x = x;     }      protected final void setY(int y) {          this.y = y;     }      public final int getX() {         return x;     }      public final int getY() {         return y;     } }  interface Locatable {     int getX();     int getY(); } 

Location has protected mutator methods, to ensure I can provide an MutableLocation type, but I'm not sure if this violates the Interface Seggregation principle when creating an ImmutableLocation.

I feel it doesn't, since the client will never have access to the setters through ImmutableLocation (cannot call it from an instance, cannot call it from a subclass of ImmutableLocation). So this was my attempt at a SOLID location type, which I plan on using in a project.

class ImmutableLocation extends Location {     //...  }  class MutableLocation extends Location {     public void adjustX(int amount, IncremeantOperation op) {         int x = op.adjust(x, amount);          setX(x);     }      public void adjustY(int amount, IncremeantOperation op) {         int y = op.adjust(getY(), amount);          setY(y);     } }  enum IncremeantOperation {     INCREASE {         public int adjust(int target, int amount) {              return target + amount;         }     },     DECREASE {         public int adjust(int target, int amount) {             return target - amount;         }     };      public int adjust(int target, int amount) {         throw new UnsupportedOperationException("Cannot call adjust(int, int) on unknown operation");     } } 

Please give your thoughts. My main concerns are:

  1. I'm not sure if the enum is an overkill
  2. If the setters are a violation of SOLID; I'm not sure how else I should do it.
  3. If UnsupportedOperationException is best fit for the adjust method

interface Locatable {     Location getLocation(); }  public abstract class Location {     //may add fields, which explains the class      public abstract int getX();     public abstract int getY(); }  public final class ImmutableLocation extends Location {     private int x, y;      public ImmutableLocation(int x, int y) {         this.x = x;         this.y = y;     }      public int getX() {         return x;     }      public int getY() {         return y;     } }  public final class MutableLocation extends Location {     private int x, y;      public MutableLocation(int x, int y) {         this.x = x;         this.y = y;     }      public void adjustX(int x) {          //adjust x     }      public void adjustY(int y) {          //adjust y     }      public int getX() {         return x;     }      public int getY() {         return y;     } } 

Locatable would be implemented by anything with a location, such as Entity:

abstract class Entity implements Locatable {     private Location location;      public Location getLocation() {          return location;     } } 
           

Lista de respuestas

4
 
vote
vote
La mejor respuesta
 

No tiene sentido tener colonos en una ubicación inmutable. Considere esta alternativa:

  interface Location {     int getX();     int getY(); }  class MutableLocation implements Location {      private int x;     private int y;      public MutableLocation(int x, int y) {         this.x = x;         this.y = y;     }      public int getX() {         return x;     }      public int getY() {         return y;     }      public void adjustX(int x) {         //adjust x     }      public void adjustY(int y) {         //adjust y     } }  final class ImmutableLocation implements Location {     private final Location location;      public ImmutableLocation(int x, int y) {         location = new MutableLocation(x, y);     }      @Override     public int getX() {         return location.getX();     }      @Override     public int getY() {         return location.getY();     } }   

Sus preguntas

  1. No estoy seguro de si el Enum es una exageración

no necesariamente. Tal vez sea, tal vez no lo sea. Dependerá del resto del código en su proyecto. Desde aquí puede ir en ambos sentidos.

  1. si los colonos son una violación de sólidos; No estoy seguro de qué otra cosa debería hacerlo.

Violan sólidos, en particular el principio de segregación de la interfaz: La clase inmutable hereda los constantes, pero no los necesita. Vea mis alternativas de limpieza propuestas anteriores.

  1. SI NO SOBREPPORTEDOPEException es el mejor ajuste para el método de ajuste

Para operaciones no compatibles, esto está bien, pero su propósito no está claro. ¿Tiene la intención de agregar valores en este enum sin implementar adjust ? Si es así, entonces su solución está bien. Pero si nunca tiene la intención de agregar valores sin implementar adjust , Entonces sería mejor hacer el método abstract . También debe usar @Override en las implementaciones:

  enum IncrementOperation {     INCREASE {         @Override         public int adjust(int target, int amount) {             return target + amount;         }     },     DECREASE {         @Override         public int adjust(int target, int amount) {             return target - amount;         }     };      public abstract int adjust(int target, int amount); }   
 

It doesn't make sense to have setters in an immutable location. Consider this alternative:

interface Location {     int getX();     int getY(); }  class MutableLocation implements Location {      private int x;     private int y;      public MutableLocation(int x, int y) {         this.x = x;         this.y = y;     }      public int getX() {         return x;     }      public int getY() {         return y;     }      public void adjustX(int x) {         //adjust x     }      public void adjustY(int y) {         //adjust y     } }  final class ImmutableLocation implements Location {     private final Location location;      public ImmutableLocation(int x, int y) {         location = new MutableLocation(x, y);     }      @Override     public int getX() {         return location.getX();     }      @Override     public int getY() {         return location.getY();     } } 

Your questions

  1. I'm not sure if the enum is an overkill

Not necessarily. Maybe it is, maybe it isn't. It will depend on the rest of the code in your project. From here it can go both ways.

  1. If the setters are a violation of SOLID; I'm not sure how else I should do it.

They violate SOLID, in particular the interface segregation principle: the immutable class inherits setters but doesn't need them. See my proposed cleaner alternatives above.

  1. If UnsupportedOperationException is best fit for the adjust method

For unsupported operations, this is fine, but your purpose is not clear. Do you intend to add values in this enum without implementing adjust? If yes, then your solution is fine. But if you never intend to add values without implementing adjust, then it would be better to make the method abstract. Also you should use @Override in the implementations:

enum IncrementOperation {     INCREASE {         @Override         public int adjust(int target, int amount) {             return target + amount;         }     },     DECREASE {         @Override         public int adjust(int target, int amount) {             return target - amount;         }     };      public abstract int adjust(int target, int amount); } 
 
 
         
         
3
 
vote

Para garantizar la inmutabilidad, debe hacer la final de la clase o definir un constructor de paquete-privado (se asume que alguien que se escabre en su paquete es un pecado que le libera de todas sus promesas).

El gran problema con su ImmutableLocation es que no es seguro de hilo como una clase inmutable. No hay campos finales, sin publicación segura.

  interface Locatable {   

No veo lo que lo necesitas. Un Location como una clase abstracta es lo suficientemente buena, a menos que quiera que las personas agreguen sus propias implementaciones.

  1. No estoy seguro de si el Enum es una exageración

y no estarás seguro de que se usa mucho. Hay typo en su nombre (increíble a ntoperation).

  1. si los colonos son una violación de sólidos; No estoy seguro de qué otra cosa debería hacerlo.

No tenerlos en la clase inmutable es la mejor opción. Podría hacer que los campos protegidos y agreguen los colonos en la subclase mutable ... pero esto tampoco es mejor.

  1. Si no es la mejor opción para el método de ajuste.

Debe ser abstracto, a menos que haya una muy buena razón para no implementarlo en un miembro. Pero, ¿cuál debería ser un miembro de este tipo para si no implementa la única operación que debería implementar?

Sin embargo, enum0 es para mí siempre un signo de una imperfección. Pero no vivimos en un mundo perfecto y las alternativas a enum1 a menudo son peores (la jerarquía de clase explota). Otoh con meras dos clases de hormigón, debe haber una mejor manera.


Cuando escribí una clase similar, dejé todo fuera de la superclase, lo que no era realmente común. Una clase inmutable tiene campos finales, mientras que un mutable tiene campos no finales, por lo que no hay ninguno en común.

  enum2  

The Lombok Las anotaciones hacen exactamente lo que crees que hacen. Si no quiere usarlos, puede escribir la placa de calderas manualmente.

Puede reemplazar la superclase abstracta por una interfaz si lo prefiere. Pero supongo, no necesitas ambos.

 

To ensure immutability, you must make the class final or define a package-private constructor (it's assumed that somebody sneaking in your package is a sin freeing you from all your promises).

The big problem with your ImmutableLocation is that it's not thread-safe as an immutable class should be. No final fields, no safe publishing.

interface Locatable { 

I don't see what you need it for. A Location as an abstract class is good enough, unless you want to let people add their own implementations.

  1. I'm not sure if the enum is an overkill

And you won't be sure until it's used a lot. There's typo in its name (IncremeantOperation).

  1. If the setters are a violation of SOLID; I'm not sure how else I should do it.

Not having them in the immutable class is the best option. You could make the fields protected and add the setters in the mutable subclass... but this isn't better either.

  1. If UnsupportedOperationException is best fit for the adjust method.

It should be abstract, unless there's a very good reason for not implementing it in a member. But what should be such a member good for if it does not implement the only operation it should implement?

However, UOE is for me always a sign of an imperfection. But we don't live in a perfect world and the alternatives to UOE are often worse (class hierarchy blow up). OTOH with mere two concrete classes, there should be a better way.


When I wrote a similar class, I left everything out of the superclass what wasn't really common. An immutable class has final fields while a mutable one has non-final fields, so there are none in common.

abstract class Location {     abstract int getX();     abstract int getY(); }  @Getter @Setter final class MutableLocation extends Location {     private int x;     private int y;      adjust...      // I find this pretty useful....     ImmutableLocation toImmutableLocation() {...} }  @Getter @RequiredArgsConstructor final class ImmutableLocation extends Location {     private final int x;     private final int y;      // ... and this too.     MutableLocation toMutableLocation() {...} } 

The Lombok annotations do exactly what you think they do. If you don't want to use them, you can write the boilerplate manually.

You can replace the abstract superclass by an interface if you prefer. But I guess, you don't need both.

 
 
   
   
3
 
vote

Tu incrementoperación enum es una excesiva total. Recuerde que los enteros pueden ser negativos, por lo que location.adjustX(5, IncrementOperation.DECREASE) es exactamente equivalente a location.adjustX(-5) cuando se implementa ese método

  void adjustX(int delta) {     setX(getX() + delta); }   

¿Son estas clases sólidas? Bueno, podemos mirar la lista:

  • Principio de responsabilidad única: Sí, sus clases representan una coordenada posiblemente mutable.

  • Principio cerrado abierto: Esto es más difícil ya que dos ideas diferentes se esconden detrás de ese nombre:

    (1) Puede modificar una clase subclusándola y heredando la mayor parte de su comportamiento. Oh Dios, por favor no! Vea el "problema de clase base frágil" sobre por qué esta podría ser una idea morónica. Esto podría ser una consideración para las bibliotecas de C ++, pero debería desanimarse en todas partes.

    (2) Puede cambiar una clase en un sistema al reimplementar su interfaz. Esto es posible aquí desde que escribió una interfaz localable. ¿Pero está siguiendo el OCP sensible en su caso? Mi opinión es que los requisitos de arquitectura para una API publicada y para el Código de Internales son drásticamente diferentes: con el código interno, usted controla todas las dependencias y, por lo tanto, puede hacer cambios arbitrarios de comportamiento arbitrarios. Pero cuando no controla todo el código que puede depender de la ubicación, podría tener sentido apoyar el OCP para que otras personas puedan escribir una ubicación personalizada que se adapte a sus necesidades. En ese caso, también debe publicar una interfaz separada para los métodos de MutableLocation .

  • Principio de sustitución de Liskov: Sus dos subclases son subtipos adecuados de ubicación. Sin embargo, es posible crear una subclase de la inmutabilidad que viole el LSP. Tendría mucho sentido hacer esa clase final.

  • Principio de segregación de la interfaz: Todas las interfaces son lo suficientemente pequeñas.

  • Principio de inversión de la dependencia: Su código no tiene dependencias, por lo tanto, no se pueden invertir dependencias. Se ve bien!

Para resumir, sus clases generalmente pasan las pruebas sólidas, aunque hay algunos detalles en los que tiene que pensar en lo que realmente desea.

Otro punto que noté es que no usa @Override anotaciones al implementar métodos de interfaz. Considere hacerlo, especialmente hace que sea fácil ver de un vistazo si esto permite que los consumidores de la clase dependa de las interfaces en lugar de en las clases de concreto.

 

Your IncrementOperation enum is total overkill. Remember that integers can be negative, so location.adjustX(5, IncrementOperation.DECREASE) is exactly equivalent to location.adjustX(-5) when that method is implemented as

void adjustX(int delta) {     setX(getX() + delta); } 

Are these classes SOLID? Well, we can look at the list:

  • Single Responsibility Principle: Yes, your classes represent a possibly mutable coordinate.

  • Openxe2x80x93Closed Principle: This is more difficult since two different ideas hide behind that name:

    (1) You can modify a class by subclassing it and inheriting most of its behaviour. Oh God, please not! See the xe2x80x9cFragile Base Class Problemxe2x80x9d on why this might be a moronic idea. This might be a consideration for C++ libraries, but it ought to be discouraged everywhere else.

    (2) You can switch out a class in a system by reimplementing its interface. This is possible here since you wrote a Locatable interface. But is following the OCP sensible in your case? My take is that architecture requirements for a published API and for internals code is drastically different: With internal code, you control all dependencies and can therefore safely do arbitrary changes of behaviour. But when you don't control all code that may depend on Location, it might make sense to support the OCP so that other people can write a CustomLocation that suits their needs. In that case, you should also publish a separate interface for the methods of MutableLocation.

  • Liskov Substitution Principle: Your two subclasses are proper subtypes of Location. However, it is possible to create a subclass of the ImmutableLocation that violates the LSP. It would make very much sense to make that class final.

  • Interface Segregation Principle: All your interfaces are sufficiently small.

  • Dependency Inversion Principle: Your code has no dependencies, therefore no dependencies could be inverted. Looks good!

So to summarize, your classes generally pass the SOLID tests, although there are some details where you have to think about what you actually want.

Another point I noticed is that you do not use @Override annotations when implementing interface methods. Consider doing that, it especially makes it easy to see at a glance whether this allows consumers of the class to depend on interfaces rather than on concrete classes.

 
 
 
 
1
 
vote

Así es como implementa objetos de valor casi mutables, sin generar la jerarquía de clase de desorden:

  public class Location {     private final int x;     private final int y;      public Location(int x, int y) {         this.x = x;         this.y = y;     }      public int getX() {         return x;     }      public int getY() {         return y;     }      public Location withX(int newX) {         return new Location(newX, this.y);     }      public Location withY(int newY) {         return new Location(this.x, newY);     } }   

Por supuesto, reemplace withFoo Métodos con nombres más significativos del dominio, cuando esté disponible. Piense en BigDecimal . Es inmutable, pero puede add otro BigDecimal a it.

 

This is how you implement quasi-mutable value objects, without generating class hierarchy clutter:

public class Location {     private final int x;     private final int y;      public Location(int x, int y) {         this.x = x;         this.y = y;     }      public int getX() {         return x;     }      public int getY() {         return y;     }      public Location withX(int newX) {         return new Location(newX, this.y);     }      public Location withY(int newY) {         return new Location(this.x, newY);     } } 

Of course replace withFoo methods with more meaningful names from the domain, whenever available. Think of BigDecimal. It is immutable but you can add another BigDecimal to it.

 
 

Relacionados problema

9  Lista vinculada inmutable en VBA  ( Immutable linked list in vba ) 
Hice una clase de lista inmutable utilizando el head-tail Idiom. Si hice esto correctamente, implementa estructuras de datos persistentes. Lamentablemente, ...

12  Clase de objeto inmutable en VBA: creable solo a través del constructor y no a través de la palabra clave "nueva"  ( Immutable object class in vba creatable only through constructor and not via ) 
Objetivos para la clase Crear objetos inmutables - I.E. SOLAMENTE GETERS - NINGÚN SENTOS Creación de objetos Solo posible a través de un constructor, no ...

3  Óxido: Aplanando la estructura anidada a VEC <T>  ( Rust flattening nested struct to vect ) 
Tengo una estructura que anida otras estructuras como seguir: #[derive(Debug)] pub struct Rankings { conferences: Vec<Conference>, } #[derive(Debug)] ...

1  Implementaciones de rasiones de óxido para un juego de blackjack  ( Rust trait implementations for a blackjack game ) 
Estoy aprendiendo a Rust. He construido un juego simple (Blackjack) para familiarizarse. El siguiente código es el código que ejecuta la lógica del juego. El ...

4  Clases de datos puros inmutables con artículos públicos en propiedades  ( Immutable pure data classes with public setters on properties ) 
Estoy reuniendo algunas clases como modelo para alguna información (que actualmente estoy tirando de un sitio web). Las clases se implementan en C #, porque...

2  Observable  ( Observablequeue ) 
Estoy buscando comentarios sobre esto. public sealed class ObservableQueue<T> : IObservable<T>, IDisposable { private readonly object _lock = new objec...

5  Pila de C ++ inmutable - pensamientos y rendimiento  ( Immutable c stack thoughts and performance ) 
¿Cuáles son sus pensamientos sobre la implementación de la pila inmutable que reduce? Se implementa, teniendo como base, una pila inmutable de C # (donde la a...

2  Un generador de laberinto Scala en estilo funcional  ( A scala maze generator in functional style ) 
Me pregunto si hay más que puedo hacer para incorporar más principios de programación de Scala y funcionales idiomáticos. Sé que el laberinto es silencioso, p...

6  ¿Debo copiar la lista tanto en constructor como en Getter?  ( Should i copy list both in constructor and in getter ) 
Tengo una clase simple inmutable : j=173 ¿Debo guardar siempre una copia de la lista aprobada en constructor y devolver copia de la lista guardada ...

1  Clase de cadena C ++ inmutable  ( Immutable c string class ) 
Tengo proyecto donde necesitaré crear muchas cadenas inmutables. Si estoy usando std::string , que tiene enormes sobrecargas, aproximadamente 60-70% en contr...




© 2022 respuesta.top Reservados todos los derechos. Centro de preguntas y respuestas reservados todos los derechos